diff options
author | Pau Espin Pedrol <pespin@sysmocom.de> | 2020-03-24 17:19:27 +0100 |
---|---|---|
committer | Eric <ewild@sysmocom.de> | 2020-04-15 22:43:15 +0200 |
commit | 87d6f3c8e1d0fc9d39520f7f2d25552d497c883b (patch) | |
tree | 84e26fb58578b9e197789af3312e411ef055cf80 | |
parent | a7143bf7a12e6794253dd970c41badbbae8ec0b7 (diff) |
WIP: osmo-trx-ipc
Change-Id: Ice63d3499026293ade8aad675ff7a883bcdd5756
-rw-r--r-- | Transceiver52M/Makefile.am | 7 | ||||
-rw-r--r-- | Transceiver52M/device/Makefile.am | 4 | ||||
-rw-r--r-- | Transceiver52M/device/ipc/IPCDevice.cpp | 741 | ||||
-rw-r--r-- | Transceiver52M/device/ipc/IPCDevice.h | 218 | ||||
-rw-r--r-- | Transceiver52M/device/ipc/Makefile.am | 25 | ||||
-rw-r--r-- | Transceiver52M/device/ipc/ipc-driver-test.c | 507 | ||||
-rw-r--r-- | Transceiver52M/device/ipc/shm.c | 121 | ||||
-rw-r--r-- | Transceiver52M/device/ipc/shm.h | 207 | ||||
-rw-r--r-- | configure.ac | 1 |
9 files changed, 1831 insertions, 0 deletions
diff --git a/Transceiver52M/Makefile.am b/Transceiver52M/Makefile.am index ade4e30..4e01e9c 100644 --- a/Transceiver52M/Makefile.am +++ b/Transceiver52M/Makefile.am @@ -104,3 +104,10 @@ osmo_trx_lms_LDADD = \ $(LMS_LIBS) osmo_trx_lms_CPPFLAGS = $(AM_CPPFLAGS) $(LMS_CFLAGS) endif + +bin_PROGRAMS += osmo-trx-ipc +osmo_trx_ipc_SOURCES = osmo-trx.cpp +osmo_trx_ipc_LDADD = \ + $(builddir)/device/ipc/libdevice.la \ + $(COMMON_LDADD) +oosmo_trx_ipc_CPPFLAGS = $(AM_CPPFLAGS) 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 @@ include $(top_srcdir)/Makefile.common SUBDIRS = common +#if DEVICE_IPC +SUBDIRS += ipc +#endif + if DEVICE_USRP1 SUBDIRS += usrp1 endif diff --git a/Transceiver52M/device/ipc/IPCDevice.cpp b/Transceiver52M/device/ipc/IPCDevice.cpp new file mode 100644 index 0000000..5b05e3d --- /dev/null +++ b/Transceiver52M/device/ipc/IPCDevice.cpp @@ -0,0 +1,741 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de> +* Author: Pau Espin Pedrol <pespin@sysmocom.de> +* +* SPDX-License-Identifier: AGPL-3.0+ +* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <sys/stat.h> /* For mode constants */ +#include <fcntl.h> /* For O_* constants */ + +#include <map> + +#include "trx_vty.h" +#include "Logger.h" +#include "Threads.h" +#include "Utils.h" +#include "IPCDevice.h" + +extern "C" { +#include "osmo_signal.h" +#include <osmocom/core/application.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +} + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define SAMPLE_BUF_SZ (1 << 20) + +using namespace std; + +IPCDevice::IPCDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset, + const std::vector<std::string>& tx_paths, + const std::vector<std::string>& rx_paths): + RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths), + tmp_state(IPC_IF_MSG_GREETING_REQ), shm(NULL), started(false) +{ + LOGC(DDEV, INFO) << "creating IPC device..."; + + //m_IPC_stream_rx.resize(chans); + //m_IPC_stream_tx.resize(chans); + rx_gains.resize(chans); + tx_gains.resize(chans); + + rx_buffers.resize(chans); + + /* Set up per-channel Rx timestamp based Ring buffers */ + for (size_t i = 0; i < rx_buffers.size(); i++) + rx_buffers[i] = new smpl_buf(SAMPLE_BUF_SZ / sizeof(uint32_t)); + +} + +IPCDevice::~IPCDevice() +{ + //unsigned int i; + LOGC(DDEV, INFO) << "Closing IPC device"; + /* disable all channels */ + + for (size_t i = 0; i < rx_buffers.size(); i++) + delete rx_buffers[i]; +} + +int IPCDevice::ipc_shm_connect(const char *shm_name) +{ + int fd; + size_t shm_len; + int rc; + + LOGP(DDEV, LOGL_NOTICE, "Opening shm path %s\n", shm_name); + if ((fd = shm_open(shm_name, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR)) < 0) { + LOGP(DDEV, LOGL_ERROR, "shm_open %d: %s\n", errno, strerror(errno)); + rc = -errno; + goto err_shm_open; + } + + // Get size of the allocated memory + struct stat shm_stat; + if (fstat(fd, &shm_stat) < 0) { + LOGP(DDEV, LOGL_ERROR, "fstat %d: %s\n", errno, strerror(errno)); + rc = -errno; + goto err_mmap; + } + + shm_len = shm_stat.st_size; + + LOGP(DDEV, LOGL_NOTICE, "mmaping shared memory fd %d (size=%zu)\n", fd,shm_len); + if ((shm = mmap(NULL, shm_len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { + LOGP(DDEV, LOGL_ERROR, "mmap %d: %s\n", errno, strerror(errno)); + rc = -errno; + goto err_mmap; + } + LOGP(DDEV, LOGL_NOTICE, "mmap'ed shared memory at addr %p\n", shm); + LOGP(DDEV, LOGL_NOTICE, "%s\n", osmo_hexdump((const unsigned char *) shm, 80)); + /* After a call to mmap(2) the file descriptor may be closed without affecting the memory mapping. */ + close(fd); + return 0; +err_mmap: + shm_unlink(shm_name); + close(fd); +err_shm_open: + return rc; +} + +static int ipc_sock_send(struct ipc_sock_state* state, struct msgb *msg); + +struct msgb *ipc_msgb_alloc(uint8_t msg_type) +{ + struct msgb *msg; + struct ipc_sk_if *ipc_prim; + + msg = msgb_alloc(sizeof(struct ipc_sk_if) + 1000, "ipc_sock_tx"); + if (!msg) + return NULL; + msgb_put(msg, sizeof(struct ipc_sk_if) + 1000); + ipc_prim = (struct ipc_sk_if *) msg->data; + ipc_prim->msg_type = msg_type; + + return msg; +} + +int ipc_tx_greeting_req(struct ipc_sock_state* state, uint8_t req_version) +{ + struct msgb *msg; + struct ipc_sk_if *ipc_prim; + + LOGC(DDEV, NOTICE) << "Tx Greeting Req (" << IPC_IF_MSG_GREETING_REQ << ")\n"; + + msg = ipc_msgb_alloc(IPC_IF_MSG_GREETING_REQ); + if (!msg) { + LOGC(DDEV, INFO) << "ipc_msgb_alloc() returns NULL!"; + return -ENOMEM; + } + ipc_prim = (struct ipc_sk_if *) msg->data; + ipc_prim->u.greeting_req.req_version = req_version; + + return ipc_sock_send(state, msg); +} + +int ipc_tx_info_req(struct ipc_sock_state* state) +{ + struct msgb *msg; + //struct ipc_sk_if *ipc_prim; + + LOGC(DDEV, NOTICE) << "Tx INFO Req\n"; + + msg = ipc_msgb_alloc(IPC_IF_MSG_INFO_REQ); + if (!msg) + return -ENOMEM; + + //ipc_prim = (struct ipc_sk_if *) msg->data; + + return ipc_sock_send(state, msg); +} + +int ipc_tx_open_req(struct ipc_sock_state* state, uint32_t num_chans) +{ + struct msgb *msg; + struct ipc_sk_if *ipc_prim; + struct ipc_sk_if_open_req_chan *chan_info; + + LOGC(DDEV, NOTICE) << "Tx Open Req\n"; + + msg = ipc_msgb_alloc(IPC_IF_MSG_OPEN_REQ); + if (!msg) { + return -ENOMEM; + } + ipc_prim = (struct ipc_sk_if *) msg->data; + ipc_prim->u.open_req.num_chans = num_chans; + chan_info = ipc_prim->u.open_req.chan_info; + OSMO_STRLCPY_ARRAY(chan_info->rx_path, "RxAntenna1"); + OSMO_STRLCPY_ARRAY(chan_info->tx_path, "TxAntenna2"); + + return ipc_sock_send(state, msg); +} + +static void ipc_sock_timeout(void *_priv) +{ + LOGC(DDEV, INFO) << "UNIX SOCKET TIMEOUT!"; + exit(1); +} + +int IPCDevice::ipc_rx_greeting_cnf(const struct ipc_sk_if_greeting *greeting_cnf) +{ + if (greeting_cnf->req_version == IPC_SOCK_API_VERSION) { + LOGC(DDEV, NOTICE) << "Rx Greeting CNF: correct sock API version" << greeting_cnf->req_version; + tmp_state = IPC_IF_MSG_GREETING_CNF; + } else { + LOGC(DDEV, ERROR) << "Wrong IPC SOCK API VERSION RECEIVED!" << greeting_cnf->req_version; + exit(1); + } + return 0; +} + +int IPCDevice::ipc_rx_info_cnf(const struct ipc_sk_if_info_cnf *info_cnf) +{ + /* Here: + * verify info_cnf->max_num_chans >= requested chans + * verify supports setting reflock as asked by user looking in info_cnf->feature_mask + * cache locally min/max tx/rxGain values from info_cnf + * do whatever validations or print info_cnf->dev_desc + * cache rx/tx paths per channel, and make sure it matches the one the user wants to set + */ + unsigned int i; + LOGC(DDEV, NOTICE) << "Rx Info CNF:" + << " max_num_chans=" << info_cnf->max_num_chans + << " feature_mask=" << info_cnf->feature_mask + << " min_rx_gain=" << info_cnf->min_rx_gain + << " max_rx_gain=" << info_cnf->max_rx_gain; + for (i = 0; i < info_cnf->max_num_chans; i++) { + int j = 0; + while (strcmp(info_cnf->chan_info[i].rx_path[j],"")!=0) { + LOGC(DDEV, NOTICE) << "chan " << i << ": RxPath[" << j <<"]: " << info_cnf->chan_info[i].rx_path[j]; + j++; + } + j = 0; + while (strcmp(info_cnf->chan_info[i].tx_path[j],"")!=0) { + LOGC(DDEV, NOTICE) << "chan " << i << ": TxPath[" << j <<"]: " << info_cnf->chan_info[i].tx_path[j]; + j++; + } + } + tmp_state = IPC_IF_MSG_INFO_CNF; + return 0; +} + +int IPCDevice::ipc_rx_open_cnf(const struct ipc_sk_if_open_cnf *open_cnf) +{ + unsigned int i; + LOGC(DDEV, NOTICE) << "Rx Open CNF:" + << " return_code=" << (unsigned int)open_cnf->return_code + << " shm_name=" << open_cnf->shm_name; + for (i = 0; i < chans; i++) { + LOGC(DDEV, NOTICE) << "chan " << i << ": sk_path=" << open_cnf->chan_info[i].chan_ipc_sk_path; + } + + OSMO_STRLCPY_ARRAY(shm_name, open_cnf->shm_name); + if (ipc_shm_connect(shm_name) < 0) + return -1; + shm_dec = ipc_shm_decode_region(NULL, (ipc_shm_raw_region*)shm); + LOGC(DDEV, NOTICE) << "shm: num_chans=" << shm_dec->num_chans; + LOGC(DDEV, NOTICE) << "shm: chan0/dl: num_buffers=" << shm_dec->channels[0]->dl_stream->num_buffers; + LOGC(DDEV, NOTICE) << "shm: chan0/dl: buffer_size=" << shm_dec->channels[0]->dl_stream->buffer_size; + + tmp_state = IPC_IF_MSG_OPEN_CNF; + return 0; +} + +int IPCDevice::ipc_rx(uint8_t msg_type, struct ipc_sk_if *ipc_prim) +{ + int rc = 0; + + switch (msg_type) { + case IPC_IF_MSG_GREETING_CNF: + rc = ipc_rx_greeting_cnf(&ipc_prim->u.greeting_cnf); + break; + case IPC_IF_MSG_INFO_CNF: + rc = ipc_rx_info_cnf(&ipc_prim->u.info_cnf); + break; + case IPC_IF_MSG_OPEN_CNF: + rc = ipc_rx_open_cnf(&ipc_prim->u.open_cnf); + break; + default: + LOGP(DDEV, LOGL_ERROR, "Received unknown PCU msg type %d\n", + msg_type); + rc = -EINVAL; + } + + return rc; +} + +static int ipc_sock_send(struct ipc_sock_state* state, struct msgb *msg) +{ + struct osmo_fd *conn_bfd; + //struct ipc_sk_if *ipc_prim = (struct ipc_sk_if *) msg->data; + + if (!state) { + LOGP(DMAIN, LOGL_INFO, "PCU socket not created, " + "dropping message\n"); + msgb_free(msg); + return -EINVAL; + } + conn_bfd = &state->conn_bfd; + if (conn_bfd->fd <= 0) { + LOGP(DMAIN, LOGL_NOTICE, "PCU socket not connected, " + "dropping message\n"); + msgb_free(msg); + return -EIO; + } + msgb_enqueue(&state->upqueue, msg); + conn_bfd->when |= BSC_FD_WRITE; + + return 0; +} + +void IPCDevice::ipc_sock_close() +{ + struct osmo_fd *bfd = &sk_state.conn_bfd; + + LOGP(DDEV, LOGL_NOTICE, "PCU socket has LOST connection\n"); + + close(bfd->fd); + bfd->fd = -1; + osmo_fd_unregister(bfd); + + /* flush the queue */ + while (!llist_empty(&sk_state.upqueue)) { + struct msgb *msg = msgb_dequeue(&sk_state.upqueue); + msgb_free(msg); + } +} + +int IPCDevice::ipc_sock_read(struct osmo_fd *bfd) +{ + struct ipc_sk_if *ipc_prim; + struct msgb *msg; + int rc; + + msg = msgb_alloc(sizeof(*ipc_prim) + 1000, "ipc_sock_rx"); + if (!msg) + return -ENOMEM; + + ipc_prim = (struct ipc_sk_if *) msg->tail; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + + if (rc < 0) { + if (errno == EAGAIN) { + msgb_free(msg); + return 0; + } + goto close; + } + + if ((size_t)rc < sizeof(*ipc_prim)) { + LOGP(DDEV, LOGL_ERROR, "Received %d bytes on Unix Socket, but primitive size " + "is %zu, discarding\n", rc, sizeof(*ipc_prim)); + msgb_free(msg); + return 0; + } + + rc = ipc_rx(ipc_prim->msg_type, ipc_prim); + + /* as we always synchronously process the message in pcu_rx() and + * its callbacks, we can free the message here. */ + msgb_free(msg); + + return rc; + +close: + msgb_free(msg); + ipc_sock_close(); + return -1; +} + +int IPCDevice::ipc_sock_write(struct osmo_fd *bfd) +{ + int rc; + + while (!llist_empty(&sk_state.upqueue)) { + struct msgb *msg, *msg2; + struct ipc_sk_if *ipc_prim; + + /* peek at the beginning of the queue */ + msg = llist_entry(sk_state.upqueue.next, struct msgb, list); + ipc_prim = (struct ipc_sk_if *)msg->data; + + bfd->when &= ~BSC_FD_WRITE; + + /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ + if (!msgb_length(msg)) { + LOGP(DDEV, LOGL_ERROR, "message type (%d) with ZERO " + "bytes!\n", ipc_prim->msg_type); + goto dontsend; + } + + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) { + bfd->when |= BSC_FD_WRITE; + break; + } + goto close; + } + +dontsend: + /* _after_ we send it, we can deueue */ + msg2 = msgb_dequeue(&sk_state.upqueue); + assert(msg == msg2); + msgb_free(msg); + } + return 0; + +close: + ipc_sock_close(); + return -1; +} + +static int ipc_sock_cb(struct osmo_fd *bfd, unsigned int flags) +{ + IPCDevice *device = (IPCDevice *)bfd->data; + int rc = 0; + + if (flags & BSC_FD_READ) + rc = device->ipc_sock_read(bfd); + if (rc < 0) + return rc; + + if (flags & BSC_FD_WRITE) + rc = device->ipc_sock_write(bfd); + + return rc; +} + +int IPCDevice::open(const std::string &args, int ref, bool swap_channels) +{ + //float_type sr_host, sr_rf; + //unsigned int i, n; + //int rc, dev_id; + int rc; + + LOGC(DDEV, INFO) << "Opening IPC device.."; + + memset(&sk_state, 0x00, sizeof(sk_state)); + INIT_LLIST_HEAD(&sk_state.upqueue); + rc = osmo_sock_unix_init_ofd(&sk_state.conn_bfd, SOCK_SEQPACKET, 0, + IPC_SOCK_PATH, OSMO_SOCK_F_CONNECT); + if (rc < 0) { + LOGC(DDEV, ERROR) << "Failed to connect to the BTS (" << IPC_SOCK_PATH << "). " << + "Retrying...\n"; + osmo_timer_setup(&sk_state.timer, ipc_sock_timeout, NULL); + osmo_timer_schedule(&sk_state.timer, 5, 0); + return -1; + } + sk_state.conn_bfd.cb = ipc_sock_cb; + sk_state.conn_bfd.data = this; + + ipc_tx_greeting_req(&sk_state, IPC_SOCK_API_VERSION); + /* Wait until confirmation is recieved */ + while(tmp_state != IPC_IF_MSG_GREETING_CNF) + osmo_select_main(0); + + ipc_tx_info_req(&sk_state); + /* Wait until confirmation is recieved */ + while(tmp_state != IPC_IF_MSG_INFO_CNF) + osmo_select_main(0); + + ipc_tx_open_req(&sk_state, chans); + /* Wait until confirmation is recieved */ + while(tmp_state != IPC_IF_MSG_OPEN_CNF) + osmo_select_main(0); + LOGC(DDEV, NOTICE) << "Device driver opened successfuly!"; + + /* configure antennas */ + if (!set_antennas()) { + LOGC(DDEV, FATAL) << "IPC antenna setting failed"; + goto out_close; + } + + return iface == MULTI_ARFCN ? MULTI_ARFCN : NORMAL; + +out_close: + LOGC(DDEV, FATAL) << "Error in IPC open, closing"; + return -1; +} + +bool IPCDevice::start() +{ + LOGC(DDEV, INFO) << "starting IPC..."; + + unsigned int i; + + if (started) { + LOGC(DDEV, ERR) << "Device already started"; + return false; + } + + /* configure the channels/streams */ + for (i=0; i<chans; i++) { + /* Set gains for calibration/filter setup */ + /* TX gain to maximum */ + setTxGain(maxTxGain(), i); + /* RX gain to midpoint */ + setRxGain((minRxGain() + maxRxGain()) / 2, i); + + /* set up Rx and Tx filters */ + if (!do_filters(i)) + return false; + /* Perform Rx and Tx calibration */ + if (!do_calib(i)) + return false; + + /* configure Streams */ + } + + /* now start the streams in a second loop, as we can no longer call + * IPC_SetupStream() after IPC_StartStream() of the first stream */ + //for (i = 0; i < chans; i++) { + //} + + flush_recv(10); + + started = true; + return true; +} + +bool IPCDevice::stop() +{ + //unsigned int i; + + if (!started) + return true; + + /* + for (i=0; i<chans; i++) { + IPC_StopStream(&m_IPC_stream_tx[i]); + IPC_StopStream(&m_IPC_stream_rx[i]); + } + + for (i=0; i<chans; i++) { + IPC_DestroyStream(m_IPC_dev, &m_IPC_stream_tx[i]); + IPC_DestroyStream(m_IPC_dev, &m_IPC_stream_rx[i]); + }*/ + + started = false; + return true; +} + +/* do rx/tx calibration - depends on gain, freq and bw */ +bool IPCDevice::do_calib(size_t chan) +{ + LOGCHAN(chan, DDEV, INFO) << "Calibrating"; + return true; +} + +/* do rx/tx filter config - depends on bw only? */ +bool IPCDevice::do_filters(size_t chan) +{ + LOGCHAN(chan, DDEV, INFO) << "Setting filters"; + return true; +} + + +double IPCDevice::maxTxGain() +{ + //return dev_param_map.at(m_dev_type).max_tx_gain; + return 70.0; +} + +double IPCDevice::minTxGain() +{ + return 0.0; +} + +double IPCDevice::maxRxGain() +{ + return 73.0; +} + +double IPCDevice::minRxGain() +{ + return 0.0; +} + +double IPCDevice::setTxGain(double dB, size_t chan) +{ + if (dB > maxTxGain()) + dB = maxTxGain(); + if (dB < minTxGain()) + dB = minTxGain(); + + LOGCHAN(chan, DDEV, NOTICE) << "Setting TX gain to " << dB << " dB"; + + //if (IPC_SetGaindB(m_IPC_dev, IPC_CH_TX, chan, dB) < 0) + // LOGCHAN(chan, DDEV, ERR) << "Error setting TX gain to " << dB << " dB"; + //else + tx_gains[chan] = dB; + return tx_gains[chan]; +} + +double IPCDevice::setRxGain(double dB, size_t chan) +{ + if (dB > maxRxGain()) + dB = maxRxGain(); + if (dB < minRxGain()) + dB = minRxGain(); + + LOGCHAN(chan, DDEV, NOTICE) << "Setting RX gain to " << dB << " dB"; + + //if (IPC_SetGaindB(m_IPC_dev, IPC_CH_RX, chan, dB) < 0) + // LOGCHAN(chan, DDEV, ERR) << "Error setting RX gain to " << dB << " dB"; + //else + rx_gains[chan] = dB; + return rx_gains[chan]; +} + + +void IPCDevice::log_ant_list(bool dir_tx, size_t chan, std::ostringstream& os) +{ + int num_names = 0; + int i; + for (i = 0; i < num_names; i++) { + if (i) + os << ", "; + os << "''"; + } +} + +bool IPCDevice::flush_recv(size_t num_pkts) +{/* + #define CHUNK 625 + int len = CHUNK * tx_sps; + short *buffer = (short*) alloca(sizeof(short) * len * 2); + int rc; + IPC_stream_meta_t rx_metadata = {}; + rx_metadata.flushPartialPacket = false; + rx_metadata.waitForTimestamp = false; + + ts_initial = 0; + + while (!ts_initial || (num_pkts-- > 0)) { + rc = IPC_RecvStream(&m_IPC_stream_rx[0], &buffer[0], len, &rx_metadata, 100); + LOGC(DDEV, DEBUG) << "Flush: Recv buffer of len " << rc << " at " << std::hex << rx_metadata.timestamp; + if (rc != len) { + LOGC(DDEV, ERROR) << "Flush: Device receive timed out"; + return false; + } + + ts_initial = rx_metadata.timestamp + len; + } +*/ + LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl; + return true; +} + +bool IPCDevice::setRxAntenna(const std::string & ant, size_t chan) +{ + return true; +} + +std::string IPCDevice::getRxAntenna(size_t chan) +{ + return ""; + +} + +bool IPCDevice::setTxAntenna(const std::string & ant, size_t chan) +{ + return true; +} + +std::string IPCDevice::getTxAntenna(size_t chan) +{ + return ""; +} + +bool IPCDevice::requiresRadioAlign() +{ + return false; +} + +GSM::Time IPCDevice::minLatency() { + /* UNUSED */ + return GSM::Time(0,0); +} + +// NOTE: Assumes sequential reads +int IPCDevice::readSamples(std::vector < short *>&bufs, int len, bool * overrun, + TIMESTAMP timestamp, bool * underrun) +{ + return len; +} + +int IPCDevice::writeSamples(std::vector < short *>&bufs, int len, + bool * underrun, unsigned long long timestamp) +{ + return len; +} + +bool IPCDevice::updateAlignment(TIMESTAMP timestamp) +{ + return true; +} + +bool IPCDevice::setTxFreq(double wFreq, size_t chan) +{ + LOGCHAN(chan, DDEV, NOTICE) << "Setting Tx Freq to " << wFreq << " Hz"; + + return true; +} + +bool IPCDevice::setRxFreq(double wFreq, size_t chan) +{ + LOGCHAN(chan, DDEV, NOTICE) << "Setting Rx Freq to " << wFreq << " Hz"; + + return true; +} + +RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps, + InterfaceType iface, size_t chans, double lo_offset, + const std::vector < std::string > &tx_paths, + const std::vector < std::string > &rx_paths) +{ + if (tx_sps != rx_sps) { + LOGC(DDEV, ERROR) << "IPC Requires tx_sps == rx_sps"; + return NULL; + } + if (lo_offset != 0.0) { + LOGC(DDEV, ERROR) << "IPC doesn't support lo_offset"; + return NULL; + } + return new IPCDevice(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths); +} diff --git a/Transceiver52M/device/ipc/IPCDevice.h b/Transceiver52M/device/ipc/IPCDevice.h new file mode 100644 index 0000000..934bc54 --- /dev/null +++ b/Transceiver52M/device/ipc/IPCDevice.h @@ -0,0 +1,218 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de> +* Author: Pau Espin Pedrol <pespin@sysmocom.de> +* +* SPDX-License-Identifier: AGPL-3.0+ +* +* This software is distributed under multiple licenses; see the COPYING file in +* the main directory for licensing information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ + +#ifndef _IPC_DEVICE_H_ +#define _IPC_DEVICE_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +extern "C" { +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include "shm.h" +} + +#include "radioDevice.h" +#include "smpl_buf.h" + +#include <sys/time.h> +#include <math.h> +#include <limits.h> +#include <string> +#include <iostream> +#include <lime/LimeSuite.h> + +struct ipc_sock_state { + struct osmo_fd conn_bfd; /* fd for connection to the BTS */ + struct osmo_timer_list timer; /* socket connect retry timer */ + struct llist_head upqueue; /* queue for sending messages */ +}; + + +/** A class to handle a LimeSuite supported device */ +class IPCDevice:public RadioDevice { + +private: + struct ipc_sock_state sk_state; + uint8_t tmp_state; + char shm_name[SHM_NAME_MAX]; + int ipc_shm_connect(const char *shm_name); + void *shm; + struct ipc_shm_region *shm_dec; + + std::vector<smpl_buf *> rx_buffers; + double actualSampleRate; ///< the actual USRP sampling rate + + bool started; ///< flag indicates LMS has started + bool skipRx; ///< set if LMS is transmit-only. + + TIMESTAMP ts_initial, ts_offset; + + std::vector<double> tx_gains, rx_gains; + + bool do_calib(size_t chan); + bool do_filters(size_t chan); + void log_ant_list(bool dir_tx, size_t chan, std::ostringstream& os); + int get_ant_idx(const std::string & name, bool dir_tx, size_t chan); + bool flush_recv(size_t num_pkts); + void update_stream_stats_rx(size_t chan, bool *overrun); + void update_stream_stats_tx(size_t chan, bool *underrun); + bool do_clock_src_freq(enum ReferenceType ref, double freq); + +public: + void ipc_sock_close(); + int ipc_sock_read(struct osmo_fd *bfd); + int ipc_sock_write(struct osmo_fd *bfd); + int ipc_rx(uint8_t msg_type, struct ipc_sk_if *ipc_prim); + int ipc_rx_greeting_cnf(const struct ipc_sk_if_greeting *greeting_cnf); + int ipc_rx_info_cnf(const struct ipc_sk_if_info_cnf *info_cnf); + int ipc_rx_open_cnf(const struct ipc_sk_if_open_cnf *open_cnf); + + /** Object constructor */ + IPCDevice(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset, + const std::vector<std::string>& tx_paths, + const std::vector<std::string>& rx_paths); + ~IPCDevice(); + + /** Instantiate the LMS */ + int open(const std::string &args, int ref, bool swap_channels); + + /** Start the LMS */ + bool start(); + + /** Stop the LMS */ + bool stop(); + + enum TxWindowType getWindowType() { + return TX_WINDOW_LMS1; + } + + /** + Read samples from the LMS. + @param buf preallocated buf to contain read result + @param len number of samples desired + @param overrun Set if read buffer has been overrun, e.g. data not being read fast enough + @param timestamp The timestamp of the first samples to be read + @param underrun Set if LMS does not have data to transmit, e.g. data not being sent fast enough + @return The number of samples actually read + */ + int readSamples(std::vector < short *>&buf, int len, bool * overrun, + TIMESTAMP timestamp = 0xffffffff, bool * underrun = + NULL); + /** + Write samples to the LMS. + @param buf Contains the data to be written. + @param len number of samples to write. + @param underrun Set if LMS does not have data to transmit, e.g. data not being sent fast enough + @param timestamp The timestamp of the first sample of the data buffer. + @return The number of samples actually written + */ + int writeSamples(std::vector < short *>&bufs, int len, bool * underrun, + TIMESTAMP timestamp = 0xffffffff); + + /** Update the alignment between the read and write timestamps */ + bool updateAlignment(TIMESTAMP timestamp); + + /** Set the transmitter frequency */ + bool setTxFreq(double wFreq, size_t chan = 0); + + /** Set the receiver frequency */ + bool setRxFreq(double wFreq, size_t chan = 0); + + /** Returns the starting write Timestamp*/ + TIMESTAMP initialWriteTimestamp(void) { + return ts_initial; + } + + /** Returns the starting read Timestamp*/ + TIMESTAMP initialReadTimestamp(void) { + return ts_initial; + } + + /** returns the full-scale transmit amplitude **/ + double fullScaleInputValue() { + #define LIMESDR_TX_AMPL 0.3 + return(double) SHRT_MAX * LIMESDR_TX_AMPL; + } + + /** returns the full-scale receive amplitude **/ + double fullScaleOutputValue() { + return (double) SHRT_MAX; + } + + /** sets the receive chan gain, returns the gain setting **/ + double setRxGain(double dB, size_t chan = 0); + + /** get the current receive gain */ + double getRxGain(size_t chan = 0) { + return rx_gains[chan]; + } + + /** return maximum Rx Gain **/ + double maxRxGain(void); + + /** return minimum Rx Gain **/ + double minRxGain(void); + + /** sets the transmit chan gain, returns the gain setting **/ + double setTxGain(double dB, size_t chan = 0); + + /** get transmit gain */ + double getTxGain(size_t chan = 0) { + return tx_gains[chan]; + } + + /** return maximum Tx Gain **/ + double maxTxGain(void); + + /** return minimum Rx Gain **/ + double minTxGain(void); + + /** sets the RX path to use, returns true if successful and false otherwise */ + bool setRxAntenna(const std::string & ant, size_t chan = 0); + + /* return the used RX path */ + std::string getRxAntenna(size_t chan = 0); + + /** sets the RX path to use, returns true if successful and false otherwise */ + bool setTxAntenna(const std::string & ant, size_t chan = 0); + + /* return the used RX path */ + std::string getTxAntenna(size_t chan = 0); + + /** return whether user drives synchronization of Tx/Rx of USRP */ + bool requiresRadioAlign(); + + /** return whether user drives synchronization of Tx/Rx of USRP */ + virtual GSM::Time minLatency(); + + /** Return internal status values */ + inline double getTxFreq(size_t chan = 0) { + return 0; + } + inline double getRxFreq(size_t chan = 0) { + return 0; + } + inline double getSampleRate() { + return actualSampleRate; + } +}; + +#endif // _IPC_DEVICE_H_ diff --git a/Transceiver52M/device/ipc/Makefile.am b/Transceiver52M/device/ipc/Makefile.am new file mode 100644 index 0000000..84f8602 --- /dev/null +++ b/Transceiver52M/device/ipc/Makefile.am @@ -0,0 +1,25 @@ +include $(top_srcdir)/Makefile.common + +AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common +AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) +AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) +AM_LDFLAGS = -lpthread -lrt + +noinst_HEADERS = IPCDevice.h shm.h + +noinst_LTLIBRARIES = libdevice.la + +libdevice_la_SOURCES = IPCDevice.cpp shm.c +libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la + + +bin_PROGRAMS = ipc-driver-test +ipc_driver_test_SOURCES = ipc-driver-test.c +ipc_driver_test_LDADD = \ + shm.lo \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMOVTY_LIBS) +ipc_driver_test_CPPFLAGS = $(AM_CPPFLAGS) +ipc_driver_test_CFLAGS = $(AM_CFLAGS) +ipc_driver_test_LDFLAGS = $(AM_LDFLAGS) diff --git a/Transceiver52M/device/ipc/ipc-driver-test.c b/Transceiver52M/device/ipc/ipc-driver-test.c new file mode 100644 index 0000000..ec04446 --- /dev/null +++ b/Transceiver52M/device/ipc/ipc-driver-test.c @@ -0,0 +1,507 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de> +* Author: Pau Espin Pedrol <pespin@sysmocom.de> +* +* SPDX-License-Identifier: AGPL-3.0+ +* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <inttypes.h> +#include <sys/mman.h> +#include <sys/stat.h> /* For mode constants */ +#include <fcntl.h> /* For O_* constants */ + +#include <osmocom/core/application.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> + +#include "shm.h" + +#define DEFAULT_SHM_NAME "/osmo-trx-ipc-driver-shm2" + +struct ipc_sock_state { + struct osmo_fd listen_bfd; /* fd for listen socket */ + struct osmo_fd conn_bfd; /* fd for connection to lcr */ + struct llist_head upqueue; /* queue for sending messages */ +}; + +static void *tall_ctx; +static struct ipc_sock_state *global_ipc_sock_state; + +void *shm; + + +/* Debug Areas of the code */ +enum { + DMAIN, +}; +static const struct log_info_cat default_categories[] = { + [DMAIN] = { + .name = "DMAIN", + .description = "Main generic category", + .color = NULL, + .enabled = 1, .loglevel = LOGL_DEBUG, + }, +}; + +const struct log_info log_info = { + .cat = default_categories, + .num_cat = ARRAY_SIZE(default_categories), +}; + +static int ipc_shm_setup(const char *shm_name, size_t shm_len) { + int fd; + int rc; + + LOGP(DMAIN, LOGL_NOTICE, "Opening shm path %s\n", shm_name); + if ((fd = shm_open(shm_name, O_CREAT|O_RDWR|O_TRUNC, S_IRUSR|S_IWUSR)) < 0) { + LOGP(DMAIN, LOGL_ERROR, "shm_open %d: %s\n", errno, strerror(errno)); + rc = -errno; + goto err_shm_open; + } + + LOGP(DMAIN, LOGL_NOTICE, "Truncating %d to size %zu\n", fd, shm_len); + if (ftruncate(fd, shm_len) < 0) { + LOGP(DMAIN, LOGL_ERROR, "ftruncate %d: %s\n", errno, strerror(errno)); + rc = -errno; + goto err_mmap; + } + + LOGP(DMAIN, LOGL_NOTICE, "mmaping shared memory fd %d\n", fd); + if ((shm = mmap(NULL, shm_len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { + LOGP(DMAIN, LOGL_ERROR, "mmap %d: %s\n", errno, strerror(errno)); + rc = -errno; + goto err_mmap; + } + + LOGP(DMAIN, LOGL_NOTICE, "mmap'ed shared memory at addr %p\n", shm); + /* After a call to mmap(2) the file descriptor may be closed without affecting the memory mapping. */ + close(fd); + return 0; +err_mmap: + shm_unlink(shm_name); + close(fd); +err_shm_open: + return rc; +} + +static int ipc_sock_send(struct msgb *msg); + +struct msgb *ipc_msgb_alloc(uint8_t msg_type) +{ + struct msgb *msg; + struct ipc_sk_if *ipc_prim; + + msg = msgb_alloc(sizeof(struct ipc_sk_if) + 1000, "ipc_sock_tx"); + if (!msg) + return NULL; + msgb_put(msg, sizeof(struct ipc_sk_if) + 1000); + ipc_prim = (struct ipc_sk_if *) msg->data; + ipc_prim->msg_type = msg_type; + + return msg; +} + +static int ipc_tx_greeting_cnf(uint8_t req_version) +{ + struct msgb *msg; + struct ipc_sk_if *ipc_prim; + + msg = ipc_msgb_alloc(IPC_IF_MSG_GREETING_CNF); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_if *) msg->data; + ipc_prim->u.greeting_cnf.req_version = req_version; + + return ipc_sock_send(msg); +} + +static int ipc_tx_info_cnf() +{ + struct msgb *msg; + struct ipc_sk_if *ipc_prim; + struct ipc_sk_if_info_chan *chan_info; + int i; + + msg = ipc_msgb_alloc(IPC_IF_MSG_INFO_CNF); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_if *) msg->data; + ipc_prim->u.info_cnf.feature_mask = FEATURE_MASK_CLOCKREF_INTERNAL | + FEATURE_MASK_CLOCKREF_EXTERNAL; + ipc_prim->u.info_cnf.min_rx_gain = 0.0; + ipc_prim->u.info_cnf.max_rx_gain = 70.0; + ipc_prim->u.info_cnf.min_tx_gain = 0.0; + ipc_prim->u.info_cnf.max_tx_gain = 63.0; + ipc_prim->u.info_cnf.max_num_chans = 2; + OSMO_STRLCPY_ARRAY(ipc_prim->u.info_cnf.dev_desc, "Hello To my Virtual device!"); + chan_info = ipc_prim->u.info_cnf.chan_info; + for (i = 0; i < ipc_prim->u.info_cnf.max_num_chans; i++) { + OSMO_STRLCPY_ARRAY(chan_info->tx_path[0], "TxAntenna1"); + OSMO_STRLCPY_ARRAY(chan_info->tx_path[1], "TxAntenna2"); + OSMO_STRLCPY_ARRAY(chan_info->tx_path[2], "TxAntenna3"); + OSMO_STRLCPY_ARRAY(chan_info->rx_path[0], "RxAntenna1"); + OSMO_STRLCPY_ARRAY(chan_info->rx_path[1], "RxAntenna2"); + chan_info++; + } + + return ipc_sock_send(msg); +} + +static int ipc_tx_open_cnf(int rc, uint32_t num_chans) +{ + struct msgb *msg; + struct ipc_sk_if *ipc_prim; + struct ipc_sk_if_open_cnf_chan *chan_info; + int i; + + msg = ipc_msgb_alloc(IPC_IF_MSG_OPEN_CNF); + if (!msg) + return -ENOMEM; + ipc_prim = (struct ipc_sk_if *) msg->data; + ipc_prim->u.open_cnf.return_code = rc; + OSMO_STRLCPY_ARRAY(ipc_prim->u.open_cnf.shm_name, DEFAULT_SHM_NAME); + + chan_info = ipc_prim->u.open_cnf.chan_info; + for (i = 0; i < num_chans; i++) { + snprintf(chan_info->chan_ipc_sk_path, sizeof(chan_info->chan_ipc_sk_path), "%s_%d", IPC_SOCK_PATH, i+1); + chan_info++; + } + + return ipc_sock_send(msg); +} + +static int ipc_rx_greeting_req(const struct ipc_sk_if_greeting *greeting_req) +{ + if (greeting_req->req_version == IPC_SOCK_API_VERSION) + ipc_tx_greeting_cnf(IPC_SOCK_API_VERSION); + else + ipc_tx_greeting_cnf(0); + return 0; +} + +static int ipc_rx_info_req(const struct ipc_sk_if_info_req *info_req) +{ + ipc_tx_info_cnf(); + return 0; +} + +static int ipc_rx_open_req(const struct ipc_sk_if_open_req *open_req) +{ + /* calculate size needed */ + unsigned int len; + len = ipc_shm_encode_region(NULL, open_req->num_chans, 4, 4096); + /* Here we verify num_chans, rx_path, tx_path, clockref, etc. */ + int rc = ipc_shm_setup(DEFAULT_SHM_NAME, len); + len = ipc_shm_encode_region((struct ipc_shm_raw_region *)shm, open_req->num_chans, 4, 4096); + LOGP(DMAIN, LOGL_NOTICE, "%s\n", osmo_hexdump((const unsigned char *) shm, 80)); + ipc_tx_open_cnf(-rc, open_req->num_chans); + return 0; +} + +static int ipc_rx(uint8_t msg_type, struct ipc_sk_if *ipc_prim) +{ + int rc = 0; + + switch (msg_type) { + case IPC_IF_MSG_GREETING_REQ: + rc = ipc_rx_greeting_req(&ipc_prim->u.greeting_req); + break; + case IPC_IF_MSG_INFO_REQ: + rc = ipc_rx_info_req(&ipc_prim->u.info_req); + break; + case IPC_IF_MSG_OPEN_REQ: + rc = ipc_rx_open_req(&ipc_prim->u.open_req); + break; + default: + LOGP(DMAIN, LOGL_ERROR, "Received unknown PCU msg type %d\n", + msg_type); + rc = -EINVAL; + } + + return rc; +} + +static int ipc_sock_send(struct msgb *msg) +{ + struct ipc_sock_state *state = global_ipc_sock_state; + struct osmo_fd *conn_bfd; + //struct ipc_sk_if *ipc_prim = (struct ipc_sk_if *) msg->data; + + if (!state) { + LOGP(DMAIN, LOGL_INFO, "PCU socket not created, " + "dropping message\n"); + msgb_free(msg); + return -EINVAL; + } + conn_bfd = &state->conn_bfd; + if (conn_bfd->fd <= 0) { + LOGP(DMAIN, LOGL_NOTICE, "PCU socket not connected, " + "dropping message\n"); + msgb_free(msg); + return -EIO; + } + msgb_enqueue(&state->upqueue, msg); + conn_bfd->when |= BSC_FD_WRITE; + + return 0; +} + +static void ipc_sock_close(struct ipc_sock_state *state) +{ + struct osmo_fd *bfd = &state->conn_bfd; + + LOGP(DMAIN, LOGL_NOTICE, "PCU socket has LOST connection\n"); + + close(bfd->fd); + bfd->fd = -1; + osmo_fd_unregister(bfd); + + /* re-enable the generation of ACCEPT for new connections */ + state->listen_bfd.when |= BSC_FD_READ; + + /* flush the queue */ + while (!llist_empty(&state->upqueue)) { + struct msgb *msg = msgb_dequeue(&state->upqueue); + msgb_free(msg); + } +} + +static int ipc_sock_read(struct osmo_fd *bfd) +{ + struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data; + struct ipc_sk_if *ipc_prim; + struct msgb *msg; + int rc; + + msg = msgb_alloc(sizeof(*ipc_prim) + 1000, "ipc_sock_rx"); + if (!msg) + return -ENOMEM; + + ipc_prim = (struct ipc_sk_if *) msg->tail; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + + if (rc < 0) { + if (errno == EAGAIN) { + msgb_free(msg); + return 0; + } + goto close; + } + + if (rc < sizeof(*ipc_prim)) { + LOGP(DMAIN, LOGL_ERROR, "Received %d bytes on Unix Socket, but primitive size " + "is %zu, discarding\n", rc, sizeof(*ipc_prim)); + msgb_free(msg); + return 0; + } + + rc = ipc_rx(ipc_prim->msg_type, ipc_prim); + + /* as we always synchronously process the message in pcu_rx() and + * its callbacks, we can free the message here. */ + msgb_free(msg); + + return rc; + +close: + msgb_free(msg); + ipc_sock_close(state); + return -1; +} + +static int ipc_sock_write(struct osmo_fd *bfd) +{ + struct ipc_sock_state *state = bfd->data; + int rc; + + while (!llist_empty(&state->upqueue)) { + struct msgb *msg, *msg2; + struct ipc_sk_if *ipc_prim; + + /* peek at the beginning of the queue */ + msg = llist_entry(state->upqueue.next, struct msgb, list); + ipc_prim = (struct ipc_sk_if *)msg->data; + + bfd->when &= ~BSC_FD_WRITE; + + /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ + if (!msgb_length(msg)) { + LOGP(DMAIN, LOGL_ERROR, "message type (%d) with ZERO " + "bytes!\n", ipc_prim->msg_type); + goto dontsend; + } + + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) { + bfd->when |= BSC_FD_WRITE; + break; + } + goto close; + } + +dontsend: + /* _after_ we send it, we can deueue */ + msg2 = msgb_dequeue(&state->upqueue); + assert(msg == msg2); + msgb_free(msg); + } + return 0; + +close: + ipc_sock_close(state); + return -1; +} + +static int ipc_sock_cb(struct osmo_fd *bfd, unsigned int flags) +{ + int rc = 0; + + if (flags & BSC_FD_READ) + rc = ipc_sock_read(bfd); + if (rc < 0) + return rc; + + if (flags & BSC_FD_WRITE) + rc = ipc_sock_write(bfd); + + return rc; +} + +/* accept connection coming from PCU */ +static int ipc_sock_accept(struct osmo_fd *bfd, unsigned int flags) +{ + struct ipc_sock_state *state = (struct ipc_sock_state *)bfd->data; + struct osmo_fd *conn_bfd = &state->conn_bfd; + struct sockaddr_un un_addr; + socklen_t len; + int rc; + + len = sizeof(un_addr); + rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len); + if (rc < 0) { + LOGP(DMAIN, LOGL_ERROR, "Failed to accept a new connection\n"); + return -1; + } + + if (conn_bfd->fd >= 0) { + LOGP(DMAIN, LOGL_NOTICE, "osmo-trx connects but we already have " + "another active connection ?!?\n"); + /* We already have one PCU connected, this is all we support */ + state->listen_bfd.when &= ~BSC_FD_READ; + close(rc); + return 0; + } + + conn_bfd->fd = rc; + conn_bfd->when = BSC_FD_READ; + conn_bfd->cb = ipc_sock_cb; + conn_bfd->data = state; + + if (osmo_fd_register(conn_bfd) != 0) { + LOGP(DMAIN, LOGL_ERROR, "Failed to register new connection " + "fd\n"); + close(conn_bfd->fd); + conn_bfd->fd = -1; + return -1; + } + + LOGP(DMAIN, LOGL_NOTICE, "Unix socket connected to external osmo-trx\n"); + + /* send current info */ + //pcu_tx_info_ind(); + + return 0; +} + +int ipc_sock_init(const char *path) +{ + struct ipc_sock_state *state; + struct osmo_fd *bfd; + int rc; + + state = talloc_zero(NULL, struct ipc_sock_state); + if (!state) + return -ENOMEM; + global_ipc_sock_state = state; + + INIT_LLIST_HEAD(&state->upqueue); + state->conn_bfd.fd = -1; + + bfd = &state->listen_bfd; + + bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path, + OSMO_SOCK_F_BIND); + if (bfd->fd < 0) { + LOGP(DMAIN, LOGL_ERROR, "Could not create %s unix socket: %s\n", + path, strerror(errno)); + talloc_free(state); + return -1; + } + + bfd->when = BSC_FD_READ; + bfd->cb = ipc_sock_accept; + bfd->data = state; + + rc = osmo_fd_register(bfd); + if (rc < 0) { + LOGP(DMAIN, LOGL_ERROR, "Could not register listen fd: %d\n", + rc); + close(bfd->fd); + talloc_free(state); + return rc; + } + + //osmo_signal_register_handler(SS_GLOBAL, pcu_if_signal_cb, NULL); + + LOGP(DMAIN, LOGL_INFO, "Started listening on IPC socket: %s\n", path); + + return 0; +} + + +int main(int argc, char** argv) { + tall_ctx = talloc_named_const(NULL, 0, "OsmoTRX"); + msgb_talloc_ctx_init(tall_ctx, 0); + osmo_init_logging2(tall_ctx, &log_info); + log_enable_multithread(); + LOGP(DMAIN, LOGL_INFO, "Starting %s\n", argv[0]); + ipc_sock_init(IPC_SOCK_PATH); + while (true) + osmo_select_main(0); + //ipc_sock_close() + return 0; +} diff --git a/Transceiver52M/device/ipc/shm.c b/Transceiver52M/device/ipc/shm.c new file mode 100644 index 0000000..cafb4f2 --- /dev/null +++ b/Transceiver52M/device/ipc/shm.c @@ -0,0 +1,121 @@ +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <osmocom/core/talloc.h> + +#include "shm.h" + +/* Convert offsets to pointers */ +struct ipc_shm_stream *ipc_shm_decode_stream(void *tall_ctx, struct ipc_shm_raw_region *root_raw, struct ipc_shm_raw_stream *stream_raw) +{ + int i; + struct ipc_shm_stream *stream; + stream = talloc_zero(tall_ctx, struct ipc_shm_stream); + stream = talloc_zero_size(tall_ctx, sizeof(struct ipc_shm_stream) + + sizeof(struct ipc_shm_raw_smpl_buf *) * stream_raw->num_buffers); + if (!stream) + return NULL; + stream->num_buffers = stream_raw->num_buffers; + stream->buffer_size = stream_raw->buffer_size; + stream->raw = stream_raw; + for (i = 0; i < stream->num_buffers; i++) { + fprintf(stderr, "decode: smpl_buf %d at offset %u\n", i, stream_raw->buffer_offset[i]); + stream->buffers[i] = (struct ipc_shm_raw_smpl_buf *)(((uint8_t*)root_raw) + stream_raw->buffer_offset[i]); + } + return stream; +} + +struct ipc_shm_channel *ipc_shm_decode_channel(void *tall_ctx, struct ipc_shm_raw_region *root_raw, struct ipc_shm_raw_channel *chan_raw) +{ + struct ipc_shm_channel *chan; + chan = talloc_zero(tall_ctx, struct ipc_shm_channel); + if (!chan) + return NULL; + fprintf(stderr, "decode: streams at offset %u and %u\n", chan_raw->dl_buf_offset, chan_raw->ul_buf_offset); + chan->dl_stream = ipc_shm_decode_stream(chan, root_raw, (struct ipc_shm_raw_stream *)(((uint8_t*)root_raw) + chan_raw->dl_buf_offset)); + chan->ul_stream = ipc_shm_decode_stream(chan, root_raw, (struct ipc_shm_raw_stream *)(((uint8_t*)root_raw) + chan_raw->ul_buf_offset)); + return chan; +} +struct ipc_shm_region *ipc_shm_decode_region(void *tall_ctx, struct ipc_shm_raw_region *root_raw) +{ + int i; + struct ipc_shm_region *root; + root = talloc_zero_size(tall_ctx, sizeof(struct ipc_shm_region) + + sizeof(struct ipc_shm_channel*) * root_raw->num_chans); + if (!root) + return NULL; + + root->num_chans = root_raw->num_chans; + for (i = 0; i < root->num_chans; i++) { + fprintf(stderr, "decode: channel %d at offset %u\n", i, root_raw->chan_offset[i]); + root->channels[i] = ipc_shm_decode_channel(root, root_raw, (struct ipc_shm_raw_channel *)(((uint8_t*)root_raw) + root_raw->chan_offset[i])); + } + return root; +} + +unsigned int ipc_shm_encode_smpl_buf(struct ipc_shm_raw_region *root_raw, struct ipc_shm_raw_smpl_buf *smpl_buf_raw, uint32_t buffer_size) +{ + uint8_t* start = (uint8_t*)smpl_buf_raw; + unsigned int offset = sizeof(struct ipc_shm_raw_smpl_buf); + + fprintf(stderr, "encode: smpl_buf at offset %lu\n", (start - (uint8_t*)root_raw)); + + offset += buffer_size * sizeof(uint16_t); /* samples */ + return offset; +} + +unsigned int ipc_shm_encode_stream(struct ipc_shm_raw_region *root_raw, struct ipc_shm_raw_stream *stream_raw, uint32_t num_buffers, uint32_t buffer_size) +{ + int i; + uint8_t* start = (uint8_t*)stream_raw; + unsigned int offset = sizeof(struct ipc_shm_raw_stream) + sizeof(uint32_t)*num_buffers; + + fprintf(stderr, "encode: stream at offset %lu\n", (start - (uint8_t*)root_raw)); + + if (root_raw) { + stream_raw->num_buffers = num_buffers; + stream_raw->buffer_size = buffer_size; + stream_raw->read_next = 0; + stream_raw->write_next = 0; + } + for (i = 0; i < num_buffers; i++) { + if (root_raw) + stream_raw->buffer_offset[i] = (start + offset - (uint8_t*)root_raw); + offset += ipc_shm_encode_smpl_buf(root_raw, (struct ipc_shm_raw_smpl_buf *)(start + offset), buffer_size); + } + return offset; +} +unsigned int ipc_shm_encode_channel(struct ipc_shm_raw_region *root_raw, struct ipc_shm_raw_channel *chan_raw, uint32_t num_buffers, uint32_t buffer_size) +{ + uint8_t* start = (uint8_t*)chan_raw; + unsigned int offset = sizeof(struct ipc_shm_raw_channel); + + fprintf(stderr, "encode: channel at offset %lu\n", (start - (uint8_t*)root_raw)); + + if (root_raw) + chan_raw->dl_buf_offset = (start + offset - (uint8_t*)root_raw); + offset += ipc_shm_encode_stream(root_raw, (struct ipc_shm_raw_stream *)(start + offset), num_buffers, buffer_size); + if (root_raw) + chan_raw->ul_buf_offset = (start + offset - (uint8_t*)root_raw); + offset += ipc_shm_encode_stream(root_raw, (struct ipc_shm_raw_stream *)(start + offset), num_buffers, buffer_size); + return offset; +} +/* if root_raw is NULL, then do a dry run, aka only calculate final offset */ +unsigned int ipc_shm_encode_region(struct ipc_shm_raw_region *root_raw, uint32_t num_chans, uint32_t num_buffers, uint32_t buffer_size) +{ + int i; + uint8_t* start = (uint8_t*)root_raw; + unsigned int offset = sizeof(struct ipc_shm_raw_region) + sizeof(uint32_t)*num_chans; + + if (root_raw) + root_raw->num_chans = num_chans; + for (i = 0; i < num_chans; i++) { + if (root_raw) { + root_raw->chan_offset[i] = (start + offset - (uint8_t*)root_raw); + fprintf(stderr, "encode: channel %d chan_offset[i]=%u\n", i, root_raw->chan_offset[i]); + offset += ipc_shm_encode_channel(root_raw, (struct ipc_shm_raw_channel *)(start + offset), num_buffers, buffer_size); + } + } + //TODO: pass maximum size and verify we didn't go through + return offset; +} diff --git a/Transceiver52M/device/ipc/shm.h b/Transceiver52M/device/ipc/shm.h new file mode 100644 index 0000000..8ee35d2 --- /dev/null +++ b/Transceiver52M/device/ipc/shm.h @@ -0,0 +1,207 @@ +/* +* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de> +* Author: Pau Espin Pedrol <pespin@sysmocom.de> +* +* SPDX-License-Identifier: AGPL-3.0+ +* +* This software is distributed under multiple licenses; see the COPYING file in +* the main directory for licensing information for this specific distribution. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +*/ + +#include <stdint.h> +#include <unistd.h> +#include <limits.h> + +/* RAW structures */ +struct ipc_shm_raw_smpl_buf { + uint32_t timestamp; + uint32_t data_len; /* In samples */ + uint16_t samples[0]; +}; + +struct ipc_shm_raw_stream { + uint32_t num_buffers; + uint32_t buffer_size; /* In samples */ + uint32_t read_next; + uint32_t write_next; + uint32_t buffer_offset[0]; + //struct ipc_shm_smpl_buf buffers[0]; +}; + +struct ipc_shm_raw_channel { + uint32_t dl_buf_offset; + uint32_t ul_buf_offset; +}; + +struct ipc_shm_raw_region { + uint32_t num_chans; + uint32_t chan_offset[0]; +}; + + +/* non-raw, Pointer converted structures */ +struct ipc_shm_stream { + uint32_t num_buffers; + uint32_t buffer_size; + struct ipc_shm_raw_stream *raw; + struct ipc_shm_raw_smpl_buf *buffers[0]; +}; + +struct ipc_shm_channel { + struct ipc_shm_stream *dl_stream; + struct ipc_shm_stream *ul_stream; +}; + +/* Pointer converted structures */ +struct ipc_shm_region { + uint32_t num_chans; + struct ipc_shm_channel *channels[0]; +}; + +unsigned int ipc_shm_encode_region(struct ipc_shm_raw_region *root_raw, uint32_t num_chans, uint32_t num_buffers, uint32_t buffer_size); +struct ipc_shm_region *ipc_shm_decode_region(void *tall_ctx, struct ipc_shm_raw_region *root_raw); +/****************************************/ +/* UNIX SOCKET API */ +/****************************************/ + +////////////////// +// Master socket +////////////////// + +#define IPC_SOCK_PATH "/tmp/ipc_sock" +#define IPC_SOCK_API_VERSION 1 + +/* msg_type */ +#define IPC_IF_MSG_GREETING_REQ 0x00 +#define IPC_IF_MSG_GREETING_CNF 0x01 +#define IPC_IF_MSG_INFO_REQ 0x02 +#define IPC_IF_MSG_INFO_CNF 0x03 +#define IPC_IF_MSG_OPEN_REQ 0x04 +#define IPC_IF_MSG_OPEN_CNF 0x05 + +#define MAX_NUM_CHANS 30 +#define RF_PATH_NAME_SIZE 25 +#define MAX_NUM_RF_PATHS 10 +#define SHM_NAME_MAX NAME_MAX /* 255 */ + +#define FEATURE_MASK_CLOCKREF_INTERNAL (0x1 << 0) +#define FEATURE_MASK_CLOCKREF_EXTERNAL (0x1 << 1) +struct ipc_sk_if_info_chan { + char tx_path[MAX_NUM_RF_PATHS][RF_PATH_NAME_SIZE]; + char rx_path[MAX_NUM_RF_PATHS][RF_PATH_NAME_SIZE]; +} __attribute__ ((packed)); + +struct ipc_sk_if_open_req_chan { + char tx_path[RF_PATH_NAME_SIZE]; + char rx_path[RF_PATH_NAME_SIZE]; +} __attribute__ ((packed)); + +struct ipc_sk_if_open_cnf_chan { + char chan_ipc_sk_path[108]; +} __attribute__ ((packed)); + +struct ipc_sk_if_greeting { + uint8_t req_version; +} __attribute__ ((packed)); + +struct ipc_sk_if_info_req { + uint8_t spare; +} __attribute__ ((packed)); + +struct ipc_sk_if_info_cnf { + uint32_t feature_mask; + double min_rx_gain; + double max_rx_gain; + double min_tx_gain; + double max_tx_gain; + uint32_t max_num_chans; + char dev_desc[200]; + struct ipc_sk_if_info_chan chan_info[0]; +} __attribute__ ((packed)); + +struct ipc_sk_if_open_req { + uint32_t num_chans; + uint32_t clockref; /* One of FEATUER_MASK_CLOCKREF_* */ + uint32_t rx_sps; + uint32_t tx_sps; + uint32_t bandwidth; + struct ipc_sk_if_open_req_chan chan_info[0]; +} __attribute__ ((packed)); + +struct ipc_sk_if_open_cnf { + uint8_t return_code; + char shm_name[SHM_NAME_MAX]; + struct ipc_sk_if_open_cnf_chan chan_info[0]; +} __attribute__ ((packed)); + +struct ipc_sk_if { + uint8_t msg_type; /* message type */ + uint8_t spare[2]; + + union { + struct ipc_sk_if_greeting greeting_req; + struct ipc_sk_if_greeting greeting_cnf; + struct ipc_sk_if_info_req info_req; + struct ipc_sk_if_info_cnf info_cnf; + struct ipc_sk_if_open_req open_req; + struct ipc_sk_if_open_cnf open_cnf; + } u; +} __attribute__ ((packed)); + + +////////////////// +// Channel socket +////////////////// +#define IPC_IF_MSG_START_REQ 0x00 +#define IPC_IF_MSG_START_CNF 0x01 +#define IPC_IF_MSG_STOP_REQ 0x02 +#define IPC_IF_MSG_STOP_CNF 0x03 +#define IPC_IF_MSG_SETGAIN_REQ 0x04 +#define IPC_IF_MSG_SETGAIN_CNF 0x05 +#define IPC_IF_MSG_SETFREQ_REQ 0x04 +#define IPC_IF_MSG_SETFREQ_CNF 0x05 + +struct ipc_sk_chan_if_op_void { +} __attribute__ ((packed)); + +struct ipc_sk_chan_if_op_rc { + uint8_t return_code; +} __attribute__ ((packed)); + +struct ipc_sk_chan_if_gain { + double gain; + uint8_t is_tx; +} __attribute__ ((packed)); + +struct ipc_sk_chan_if_freq_req { + double freq; + uint8_t is_tx; +} __attribute__ ((packed)); + +struct ipc_sk_chan_if_freq_cnf { + uint8_t return_code; +} __attribute__ ((packed)); + +struct ipc_sk_chan_if { + uint8_t msg_type; /* message type */ + uint8_t spare[2]; + + union { + struct ipc_sk_chan_if_op_void start_req; + struct ipc_sk_chan_if_op_rc start_cnf; + struct ipc_sk_chan_if_op_void stop_req; + struct ipc_sk_chan_if_op_rc stop_cnf; + struct ipc_sk_chan_if_gain set_gain_req; + struct ipc_sk_chan_if_gain set_gain_cnf; + struct ipc_sk_chan_if_freq_req set_freq_req; + struct ipc_sk_chan_if_freq_cnf set_freq_cnf; + } u; +} __attribute__ ((packed)); diff --git a/configure.ac b/configure.ac index b7b0d00..53e3e13 100644 --- a/configure.ac +++ b/configure.ac @@ -324,6 +324,7 @@ AC_CONFIG_FILES([\ Transceiver52M/device/uhd/Makefile \ Transceiver52M/device/usrp1/Makefile \ Transceiver52M/device/lms/Makefile \ + Transceiver52M/device/ipc/Makefile \ tests/Makefile \ tests/CommonLibs/Makefile \ tests/Transceiver52M/Makefile \ |