diff options
26 files changed, 2515 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..363f656f --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +*.o +*.a +Makefile.in +Makefile +.deps + +build-target +aclocal.m4 +autom4te.cache +config.log +config.status +config.guess +config.sub +configure +compile +depcomp +install-sh +missing +stamp-h1 +core +core.* + +# Backups, vi, merges +*~ +*.sw? +*.orig +*.sav + +# development environment +/.autotools +/.cproject +/.project +/.settings/ + diff --git a/src/Makefile b/src/Makefile index 00231a64..e83175c4 100644 --- a/src/Makefile +++ b/src/Makefile @@ -12,7 +12,8 @@ CROSS_TOOL_PREFIX=$(CROSS_HOST)- TOPDIR=$(shell pwd) all: libosmocore-target nofirmware firmware mtk-firmware -nofirmware: layer23 osmocon gsmmap + +nofirmware: layer23 osmocon gsmmap virtphy libosmocore-target: shared/libosmocore/build-target/src/.libs/libosmocore.a @@ -45,6 +46,15 @@ host/osmocon/Makefile: host/osmocon/configure host/osmocon/osmocon: host/osmocon/Makefile make -C host/osmocon +.PHONY: virtphy +virtphy: host/virt_phy/Makefile + cd host/virt_phy && make + +host/virt_phy/configure: host/virt_phy/configure.ac + cd host/virt_phy && autoreconf -i + +host/virt_phy/Makefile: host/virt_phy/configure + cd host/virt_phy && ./configure $(HOST_CONFARGS) .PHONY: gsmmap gsmmap: host/gsmmap/gsmmap @@ -86,6 +96,7 @@ clean: make -C host/layer23 $@ make -C host/osmocon $@ make -C host/gsmmap $@ + make -C host/virt_phy $@ make -C target/firmware $@ make -C target/firmware -f Makefile.mtk $@ @@ -94,5 +105,6 @@ distclean: make -C host/layer23 $@ make -C host/osmocon $@ make -C host/gsmmap $@ + make -C host/virt_phy $@ # 'firmware' also handles 'mtk-firmware' make -C target/firmware $@ diff --git a/src/host/virt_phy/.gitignore b/src/host/virt_phy/.gitignore new file mode 100644 index 00000000..1e19c7fb --- /dev/null +++ b/src/host/virt_phy/.gitignore @@ -0,0 +1,3 @@ +config.h +config.h.in +src/virtphy
\ No newline at end of file diff --git a/src/host/virt_phy/Makefile.am b/src/host/virt_phy/Makefile.am new file mode 100644 index 00000000..515d51b5 --- /dev/null +++ b/src/host/virt_phy/Makefile.am @@ -0,0 +1,2 @@ +SUBDIRS = src +dist_doc_DATA = README
\ No newline at end of file diff --git a/src/host/virt_phy/README b/src/host/virt_phy/README new file mode 100644 index 00000000..a7806647 --- /dev/null +++ b/src/host/virt_phy/README @@ -0,0 +1,2 @@ +This is the package for the Osmocom virtual physical layer. +A layer 1 implementation satisfying the L1CTL interface towards the l23 application (e.g. mobile).
\ No newline at end of file diff --git a/src/host/virt_phy/configure.ac b/src/host/virt_phy/configure.ac new file mode 100644 index 00000000..bcdbf91d --- /dev/null +++ b/src/host/virt_phy/configure.ac @@ -0,0 +1,27 @@ +dnl Process this file with autoconf to produce a configure script +AC_INIT([virtphy], 0.0.0) +AM_CONFIG_HEADER([config.h]) +AM_INIT_AUTOMAKE([foreign dist-bzip2]) + +dnl kernel style compile messages +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +dnl checks for programs +AC_PROG_MAKE_SET +AC_PROG_CC +AC_PROG_INSTALL + +dnl checks for libraries +PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore) +PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm) + +dnl checks for header files +AC_HEADER_STDC + +dnl Checks for typedefs, structures and compiler characteristics + +AC_CONFIG_FILES([ + Makefile + src/Makefile +]) +AC_OUTPUT diff --git a/src/host/virt_phy/example_configs/osmocom-bb-mobile.cfg b/src/host/virt_phy/example_configs/osmocom-bb-mobile.cfg new file mode 100644 index 00000000..382a09dc --- /dev/null +++ b/src/host/virt_phy/example_configs/osmocom-bb-mobile.cfg @@ -0,0 +1,67 @@ +! +! OsmocomBB (0.0.0) configuration saved from vty +!! +! +line vty + no login +! +gps device /dev/ttyACM0 +gps baudrate default +no gps enable +! +no hide-default +! +ms 1 + layer2-socket /tmp/osmocom_l2 + sap-socket /tmp/osmocom_sap + sim test + network-selection-mode auto + imei 123456789012345 0 + imei-fixed + no emergency-imsi + no sms-service-center + no call-waiting + no auto-answer + no force-rekey + no clip + no clir + tx-power auto + no simulated-delay + no stick + location-updating + neighbour-measurement + codec full-speed prefer + codec half-speed + no abbrev + support + sms + a5/1 + a5/2 + p-gsm + e-gsm + r-gsm + no gsm-850 + dcs + no pcs + class-900 4 + class-850 4 + class-dcs 1 + class-pcs 1 + channel-capability sdcch+tchf+tchh + full-speech-v1 + full-speech-v2 + half-speech-v1 + min-rxlev -106 + dsc-max 90 + no skip-max-per-band + exit + test-sim + imsi 901700000000403 + ki comp128 12 34 56 78 90 1b cd ef 12 34 56 78 90 ab cd ef + no barred-access + no rplmn + hplmn-search foreign-country + exit + no shutdown +exit +! diff --git a/src/host/virt_phy/include/layer1/mframe_sched.h b/src/host/virt_phy/include/layer1/mframe_sched.h new file mode 100644 index 00000000..ecdb1ec8 --- /dev/null +++ b/src/host/virt_phy/include/layer1/mframe_sched.h @@ -0,0 +1,68 @@ +#ifndef _L1_MFRAME_SCHED_H +#define _L1_MFRAME_SCHED_H + +#include <stdint.h> + +enum mframe_task { + MF_TASK_BCCH_NORM, + MF_TASK_BCCH_EXT, + MF_TASK_CCCH, + MF_TASK_CCCH_COMB, + + MF_TASK_SDCCH4_0, + MF_TASK_SDCCH4_1, + MF_TASK_SDCCH4_2, + MF_TASK_SDCCH4_3, + + MF_TASK_SDCCH8_0, + MF_TASK_SDCCH8_1, + MF_TASK_SDCCH8_2, + MF_TASK_SDCCH8_3, + MF_TASK_SDCCH8_4, + MF_TASK_SDCCH8_5, + MF_TASK_SDCCH8_6, + MF_TASK_SDCCH8_7, + + MF_TASK_TCH_F_EVEN, + MF_TASK_TCH_F_ODD, + MF_TASK_TCH_H_0, + MF_TASK_TCH_H_1, + + MF_TASK_NEIGH_PM51_C0T0, + MF_TASK_NEIGH_PM51, + MF_TASK_NEIGH_PM26E, + MF_TASK_NEIGH_PM26O, + + /* Test task: send Normal Burst in all timeslots */ + MF_TASK_UL_ALL_NB, +}; + +enum mf_sched_item_flag { + MF_F_SACCH = (1 << 0), +}; + +/* The scheduler itself */ +struct mframe_scheduler { + uint32_t tasks; + uint32_t tasks_tgt; + uint32_t safe_fn; +}; + +uint8_t mframe_task2chan_nr(enum mframe_task mft, uint8_t ts); + +/* Enable a specific task */ +void mframe_enable(enum mframe_task task_id); + +/* Disable a specific task */ +void mframe_disable(enum mframe_task task_id); + +/* Replace the current active set by the new one */ +void mframe_set(uint32_t tasks); + +/* Schedule mframe_sched_items according to current MF TASK list */ +void mframe_schedule(void); + +/* reset the scheduler, disabling all tasks */ +void mframe_reset(void); + +#endif /* _MFRAME_SCHED_H */ diff --git a/src/host/virt_phy/include/layer1/sync.h b/src/host/virt_phy/include/layer1/sync.h new file mode 100644 index 00000000..dae85a1a --- /dev/null +++ b/src/host/virt_phy/include/layer1/sync.h @@ -0,0 +1,204 @@ +#ifndef _L1_SYNC_H +#define _L1_SYNC_H + +#include <osmocom/core/linuxlist.h> +#include <osmocom/gsm/gsm_utils.h> +#include <layer1/tdma_sched.h> +#include <layer1/mframe_sched.h> +#include <l1ctl_proto.h> + +/* structure representing L1 sync information about a cell */ +struct l1_cell_info { + /* on which ARFCN (+band) is the cell? */ + uint16_t arfcn; + /* what's the BSIC of the cell (from SCH burst decoding) */ + uint8_t bsic; + /* Combined or non-combined CCCH */ + uint8_t ccch_mode; /* enum ccch_mode */ + /* whats the delta of the cells current GSM frame number + * compared to our current local frame number */ + int32_t fn_offset; + /* how much does the TPU need adjustment (delta) to synchronize + * with the cells burst */ + uint32_t time_alignment; + /* FIXME: should we also store the AFC value? */ +}; + +enum l1s_chan { + L1S_CHAN_MAIN, + L1S_CHAN_SACCH, + L1S_CHAN_TRAFFIC, + _NUM_L1S_CHAN +}; + +enum l1_compl { + L1_COMPL_FB, + L1_COMPL_RACH, + L1_COMPL_TX_NB, + L1_COMPL_TX_TCH, +}; + +typedef void l1_compl_cb(enum l1_compl c); + +#define L1S_NUM_COMPL 32 +#define L1S_NUM_NEIGH_CELL 6 + +struct l1s_h0 { + uint16_t arfcn; +}; + +struct l1s_h1 { + uint8_t hsn; + uint8_t maio; + uint8_t n; + uint16_t ma[64]; +}; + +struct l1s_state { + struct gsm_time current_time; /* current GSM time */ + struct gsm_time next_time; /* GSM time at next TMDMA irq */ + + /* the cell on which we are camping right now */ + struct l1_cell_info serving_cell; + + /* neighbor cell sync info */ + struct l1_cell_info neigh_cell[L1S_NUM_NEIGH_CELL]; + + /* TDMA scheduler */ + struct tdma_scheduler tdma_sched; + + /* Multiframe scheduler */ + struct mframe_scheduler mframe_sched; + + /* The current TPU offset register */ + uint32_t tpu_offset; + int32_t tpu_offset_correction; + + /* TX parameters */ + int8_t ta; + uint8_t tx_power; + + /* TCH */ + uint8_t tch_mode; + uint8_t tch_sync; + uint8_t audio_mode; + + /* Transmit queues of pending packets for main DCCH and ACCH */ + struct llist_head tx_queue[_NUM_L1S_CHAN]; + struct msgb *tx_meas; + + /* Which L1A completions are scheduled right now */ + uint32_t scheduled_compl; + /* callbacks for each of the completions */ + l1_compl_cb *completion[L1S_NUM_COMPL]; + + /* Structures below are for L1-task specific parameters, used + * to communicate between l1-sync and l1-async (l23_api) */ + struct { + uint8_t mode; /* FB_MODE 0/1 */ + } fb; + + struct { + /* power measurement l1 task */ + unsigned int mode; + union { + struct { + uint16_t arfcn_start; + uint16_t arfcn_next; + uint16_t arfcn_end; + } range; + }; + struct msgb *msg; + } pm; + + struct { + uint8_t ra; + } rach; + + struct { + enum { + GSM_DCHAN_NONE = 0, + GSM_DCHAN_SDCCH_4, + GSM_DCHAN_SDCCH_8, + GSM_DCHAN_TCH_H, + GSM_DCHAN_TCH_F, + GSM_DCHAN_UNKNOWN, + } type; + + uint8_t scn; + uint8_t tsc; + uint8_t tn; + uint8_t h; + + union { + struct l1s_h0 h0; + struct l1s_h1 h1; + }; + + uint8_t st_tsc; + uint8_t st_tn; + uint8_t st_h; + + union { + struct l1s_h0 st_h0; + struct l1s_h1 st_h1; + }; + } dedicated; + + /* neighbour cell power measurement process */ + struct { + uint8_t n, second; + uint8_t pos; + uint8_t running; + uint16_t band_arfcn[64]; + uint8_t tn[64]; + uint8_t level[64]; + } neigh_pm; +}; + +extern struct l1s_state l1s; + +struct l1s_meas_hdr { + uint16_t snr; /* signal/noise ratio */ + int16_t toa_qbit; /* time of arrival (qbits) */ + int16_t pm_dbm8; /* power level in dbm/8 */ + int16_t freq_err; /* Frequency error in Hz */ +}; + +int16_t l1s_snr_int(uint16_t snr); +uint16_t l1s_snr_fract(uint16_t snr); + +void l1s_dsp_abort(void); + +void l1s_tx_apc_helper(uint16_t arfcn); + +/* schedule a completion */ +void l1s_compl_sched(enum l1_compl c); + +void l1s_init(void); + +/* reset the layer1 as part of synchronizing to a new cell */ +void l1s_reset(void); + +/* init.c */ +void layer1_init(void); + +/* A debug macro to print every TDMA frame */ +#ifdef DEBUG_EVERY_TDMA +#define putchart(x) putchar(x) +#else +#define putchart(x) +#endif + +/* Convert an angle in fx1.15 notatinon into Hz */ +#define BITFREQ_DIV_2PI 43104 /* 270kHz / 2 * pi */ +#define BITFREQ_DIV_PI 86208 /* 270kHz / pi */ +#define ANG2FREQ_SCALING (2<<15) /* 2^15 scaling factor for fx1.15 */ +#define ANGLE_TO_FREQ(angle) ((int16_t)angle * BITFREQ_DIV_PI / ANG2FREQ_SCALING) + +void l1s_reset_hw(void); +void synchronize_tdma(struct l1_cell_info *cinfo); +void l1s_time_inc(struct gsm_time *time, uint32_t delta_fn); +void l1s_time_dump(const struct gsm_time *time); + +#endif /* _L1_SYNC_H */ diff --git a/src/host/virt_phy/include/layer1/tdma_sched.h b/src/host/virt_phy/include/layer1/tdma_sched.h new file mode 100644 index 00000000..f58d59bb --- /dev/null +++ b/src/host/virt_phy/include/layer1/tdma_sched.h @@ -0,0 +1,73 @@ +#ifndef _L1_TDMA_SCHED_H +#define _L1_TDMA_SCHED_H + +#include <stdint.h> + +/* TDMA scheduler */ + +/* The idea of this scheduler is that we have a circular buffer of buckets, + * where each bucket corresponds to one future TDMA frame [interrupt]. Each + * bucket contains of a list of callbacks which are executed when the bucket + * index reaches that particular bucket. */ + +#define TDMASCHED_NUM_FRAMES 25 +#define TDMASCHED_NUM_CB 8 + +#define TDMA_IFLG_TPU (1<<0) +#define TDMA_IFLG_DSP (1<<1) + +typedef int tdma_sched_cb(uint8_t p1, uint8_t p2, uint16_t p3); + +/* A single item in a TDMA scheduler bucket */ +struct tdma_sched_item { + tdma_sched_cb *cb; + uint8_t p1; + uint8_t p2; + uint16_t p3; + int16_t prio; + uint16_t flags; /* TDMA_IFLG_xxx */ +}; + +/* A bucket inside the TDMA scheduler */ +struct tdma_sched_bucket { + struct tdma_sched_item item[TDMASCHED_NUM_CB]; + uint8_t num_items; +}; + +/* The scheduler itself, consisting of buckets and a current index */ +struct tdma_scheduler { + struct tdma_sched_bucket bucket[TDMASCHED_NUM_FRAMES]; + uint8_t cur_bucket; +}; + +/* Schedule an item at 'frame_offset' TDMA frames in the future */ +int tdma_schedule(uint8_t frame_offset, tdma_sched_cb *cb, + uint8_t p1, uint8_t p2, uint16_t p3, int16_t prio); + +/* Schedule a set of items starting from 'frame_offset' TDMA frames in the future */ +int tdma_schedule_set(uint8_t frame_offset, const struct tdma_sched_item *item_set, uint16_t p3); + +/* Scan current frame scheduled items for flags */ +uint16_t tdma_sched_flag_scan(void); + +/* Execute pre-scheduled events for current frame */ +int tdma_sched_execute(void); + +/* Advance TDMA scheduler to the next bucket */ +void tdma_sched_advance(void); + +/* reset the scheduler; erase all scheduled items */ +void tdma_sched_reset(void); + +/* debug function: print number of entries of all TDMA buckets */ +void tdma_sched_dump(void); + + +extern int tdma_end_set(uint8_t p1, uint8_t p2, uint16_t p3); +#define SCHED_ITEM(x, p, y, z) { .cb = x, .p1 = y, .p2 = z, .prio = p, .flags = 0 } +#define SCHED_ITEM_DT(x, p, y, z) { .cb = x, .p1 = y, .p2 = z, .prio = p, \ + .flags = TDMA_IFLG_TPU | TDMA_IFLG_DSP } +#define SCHED_END_FRAME() { .cb = NULL, .p1 = 0, .p2 = 0 } +#define SCHED_END_SET() { .cb = &tdma_end_set, .p1 = 0, .p2 = 0 } + +#endif /* _L1_TDMA_SCHED_H */ diff --git a/src/host/virt_phy/src/Makefile.am b/src/host/virt_phy/src/Makefile.am new file mode 100644 index 00000000..a110435a --- /dev/null +++ b/src/host/virt_phy/src/Makefile.am @@ -0,0 +1,19 @@ +OSMOCOM_DIR = /home/basti/Osmocom +OSMO_BB_DIR = $(OSMOCOM_DIR)/osmocom-bb +OSMO_BTS_DIR = $(OSMOCOM_DIR)/osmo-bts +OPENBSC_DIR = $(OSMOCOM_DIR)/openbsc + +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OSMO_BB_DIR)/include -I$(OSMO_BTS_DIR)/include -I$(OPENBSC_DIR)/openbsc/include -I$(OSMO_BB_DIR)/src/host/layer23/include +# TODO: somehow this include path causes errors, thus the needed files are copied into $(top_srcdir)/include. Would be better to include directly from $(OSMO_BB_DIR)/src/target/firmware/include to avoid redundancy. +# -I$(OSMO_BB_DIR)/src/target/firmware/include + +CFLAGS = -g -O0 + +sbin_PROGRAMS = virtphy +virtphy_SOURCES = virtphy.c l1ctl_sock.c l1ctl_sap.c gsmtapl1_if.c logging.c virt_l1_model.c virtual_um.c osmo_mcast_sock.c +virtphy_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) + +# debug output +all: + $(info $$AM_CPPFLAGS is [${AM_CPPFLAGS}]) diff --git a/src/host/virt_phy/src/gsmtapl1_if.c b/src/host/virt_phy/src/gsmtapl1_if.c new file mode 100644 index 00000000..11214cf8 --- /dev/null +++ b/src/host/virt_phy/src/gsmtapl1_if.c @@ -0,0 +1,282 @@ +/* GSMTAP layer1 is transmits gsmtap messages over a virtual layer 1.*/ + +/* (C) 2016 Sebastian Stumpf + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 <osmocom/core/gsmtap.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/rsl.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/core/msgb.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <l1ctl_proto.h> + +#include "virtual_um.h" +#include "l1ctl_sock.h" +#include "virt_l1_model.h" +#include "l1ctl_sap.h" +#include "gsmtapl1_if.h" +#include "logging.h" + +static struct l1_model_ms *l1_model_ms = NULL; + +// for debugging +static const struct value_string gsmtap_channels [22] = { + { GSMTAP_CHANNEL_UNKNOWN, "UNKNOWN" }, + { GSMTAP_CHANNEL_BCCH, "BCCH" }, + { GSMTAP_CHANNEL_CCCH, "CCCH" }, + { GSMTAP_CHANNEL_RACH, "RACH" }, + { GSMTAP_CHANNEL_AGCH, "AGCH" }, + { GSMTAP_CHANNEL_PCH, "PCH" }, + { GSMTAP_CHANNEL_SDCCH, "SDCCH" }, + { GSMTAP_CHANNEL_SDCCH4, "SDCCH/4" }, + { GSMTAP_CHANNEL_SDCCH8, "SDCCH/8" }, + { GSMTAP_CHANNEL_TCH_F, "FACCH/F" }, + { GSMTAP_CHANNEL_TCH_H, "FACCH/H" }, + { GSMTAP_CHANNEL_PACCH, "PACCH" }, + { GSMTAP_CHANNEL_CBCH52, "CBCH" }, + { GSMTAP_CHANNEL_PDCH, "PDCH" }, + { GSMTAP_CHANNEL_PTCCH, "PTTCH" }, + { GSMTAP_CHANNEL_CBCH51, "CBCH" }, + { GSMTAP_CHANNEL_ACCH| + GSMTAP_CHANNEL_SDCCH, "LSACCH" }, + { GSMTAP_CHANNEL_ACCH| + GSMTAP_CHANNEL_SDCCH4, "SACCH/4" }, + { GSMTAP_CHANNEL_ACCH| + GSMTAP_CHANNEL_SDCCH8, "SACCH/8" }, + { GSMTAP_CHANNEL_ACCH| + GSMTAP_CHANNEL_TCH_F, "SACCH/F" }, + { GSMTAP_CHANNEL_ACCH| + GSMTAP_CHANNEL_TCH_H, "SACCH/H" }, + { 0, NULL }, +}; +// for debugging +static const struct value_string gsmtap_types [10] = { + { GSMTAP_TYPE_UM, "GSM Um (MS<->BTS)" }, + { GSMTAP_TYPE_ABIS, "GSM Abis (BTS<->BSC)" }, + { GSMTAP_TYPE_UM_BURST, "GSM Um burst (MS<->BTS)" }, + { GSMTAP_TYPE_SIM, "SIM" }, + { GSMTAP_TYPE_TETRA_I1, "TETRA V+D"}, + { GSMTAP_TYPE_WMX_BURST, "WiMAX burst" }, + { GSMTAP_TYPE_GMR1_UM, "GMR-1 air interfeace (MES-MS<->GTS)" }, + { GSMTAP_TYPE_UMTS_RLC_MAC, "UMTS RLC/MAC" }, + { GSMTAP_TYPE_UMTS_RRC, "UMTS RRC" }, + { 0, NULL }, +}; + +void gsmtapl1_init(struct l1_model_ms *model) +{ + l1_model_ms = model; +} + +/** + * Append a gsmtap header to msg and send it over the virt um. + */ +void gsmtapl1_tx_to_virt_um_inst(struct virt_um_inst *vui, struct msgb *msg) +{ + struct l1ctl_hdr *l1hdr = (struct l1ctl_hdr *)msg->l1h; + struct l1ctl_info_dl *l1dl = (struct l1ctl_info_dl *)msg->data; + uint8_t ss = 0; + uint8_t gsmtap_chan; + struct msgb *outmsg; + + switch (l1hdr->msg_type) { + case L1CTL_DATA_REQ: + // TODO: check what data request and set gsmtap_chan depending on that + gsmtap_chan = 0; + break; + } + outmsg = gsmtap_makemsg(l1dl->band_arfcn, l1dl->chan_nr, gsmtap_chan, + ss, l1dl->frame_nr, 0, 0, msgb_l2(msg), + msgb_l2len(msg)); + if (outmsg) { + struct gsmtap_hdr *gh = (struct gsmtap_hdr *)outmsg->l1h; + virt_um_write_msg(vui, outmsg); + DEBUGP(DVIRPHY, + "Sending gsmtap msg to virt um - (arfcn=%u, type=%u, subtype=%u, timeslot=%u, subslot=%u)\n", + gh->arfcn, gh->type, gh->sub_type, gh->timeslot, + gh->sub_slot); + } else { + LOGP(DVIRPHY, LOGL_ERROR, "Gsmtap msg could not be created!\n"); + } + + /* free message */ + msgb_free(msg); +} + +/** + * @see void gsmtapl1_tx_to_virt_um(struct virt_um_inst *vui, struct msgb *msg). + */ +void gsmtapl1_tx_to_virt_um(struct msgb *msg) +{ + gsmtapl1_tx_to_virt_um_inst(l1_model_ms->vui, msg); +} + +/* This is the header as it is used by gsmtap peer virtual layer 1. +struct gsmtap_hdr { + guint8 version; // version, set to 0x01 currently + guint8 hdr_len; // length in number of 32bit words + guint8 type; // see GSMTAP_TYPE_* + guint8 timeslot; // timeslot (0..7 on Um) + guint16 arfcn; // ARFCN (frequency) + gint8 signal_dbm; // signal level in dBm + gint8 snr_db; // signal/noise ratio in dB + guint32 frame_number; // GSM Frame Number (FN) + guint8 sub_type; // Type of burst/channel, see above + guint8 antenna_nr; // Antenna Number + guint8 sub_slot; // sub-slot within timeslot + guint8 res; // reserved for future use (RFU) +} + */ + +/** + * Receive a gsmtap message from the virt um. + */ +void gsmtapl1_rx_from_virt_um_inst_cb(struct virt_um_inst *vui, + struct msgb *msg) +{ + if (msg) { + struct gsmtap_hdr *gh; + struct l1ctl_info_dl *l1dl; + struct msgb *l1ctl_msg = NULL; + struct l1ctl_data_ind * l1di; + + msg->l1h = msgb_data(msg); + msg->l2h = msgb_pull(msg, sizeof(*gh)); + gh = msgb_l1(msg); + + DEBUGP(DVIRPHY, + "Receiving gsmtap msg from virt um - (arfcn=%u, framenumber=%u, type=%s, subtype=%s, timeslot=%u, subslot=%u)\n", + ntohs(gh->arfcn), ntohl(gh->frame_number), get_value_string(gsmtap_types, gh->type), get_value_string(gsmtap_channels, gh->sub_type), gh->timeslot, + gh->sub_slot); + + // compose the l1ctl message for layer 2 + switch (gh->sub_type) { + case GSMTAP_CHANNEL_RACH: + LOGP(DL1C, LOGL_NOTICE, + "Ignoring gsmtap msg from virt um - channel type is uplink only!\n"); + break; + case GSMTAP_CHANNEL_SDCCH: + case GSMTAP_CHANNEL_SDCCH4: + case GSMTAP_CHANNEL_SDCCH8: + l1ctl_msg = l1ctl_msgb_alloc(L1CTL_DATA_IND); + // TODO: implement channel handling + break; + case GSMTAP_CHANNEL_TCH_F: + l1ctl_msg = l1ctl_msgb_alloc(L1CTL_TRAFFIC_IND); + // TODO: implement channel handling + break; + case GSMTAP_CHANNEL_AGCH: + case GSMTAP_CHANNEL_PCH: + case GSMTAP_CHANNEL_BCCH: + l1ctl_msg = l1ctl_msgb_alloc(L1CTL_DATA_IND); + l1dl = (struct l1ctl_info_dl *) msgb_put(l1ctl_msg, sizeof(struct l1ctl_info_dl)); + l1di = (struct l1ctl_data_ind *) msgb_put(l1ctl_msg, sizeof(struct l1ctl_data_ind)); + + l1dl->band_arfcn = htons(ntohs(gh->arfcn)); + l1dl->link_id = gh->timeslot; + // see GSM 8.58 -> 9.3.1 for channel number encoding + l1dl->chan_nr = rsl_enc_chan_nr(chantype_gsmtap2rsl(gh->sub_type), gh->sub_slot, gh->timeslot); + l1dl->frame_nr = htonl(ntohl(gh->frame_number)); + l1dl->snr = gh->snr_db; + l1dl->rx_level = gh->signal_dbm; + l1dl->num_biterr = 0; + l1dl->fire_crc = 0; + + memcpy(l1di->data, msgb_data(msg), msgb_length(msg)); + + break; + case GSMTAP_CHANNEL_CCCH: + case GSMTAP_CHANNEL_TCH_H: + case GSMTAP_CHANNEL_PACCH: + case GSMTAP_CHANNEL_PDCH: + case GSMTAP_CHANNEL_PTCCH: + case GSMTAP_CHANNEL_CBCH51: + case GSMTAP_CHANNEL_CBCH52: + LOGP(DL1C, LOGL_NOTICE, + "Ignoring gsmtap msg from virt um - channel type not supported!\n"); + break; + default: + LOGP(DL1C, LOGL_NOTICE, + "Ignoring gsmtap msg from virt um - channel type unknown.\n"); + break; + } + + /* forward l1ctl message to l2 */ + if(l1ctl_msg) { + l1ctl_sap_tx_to_l23(l1ctl_msg); + } + + // handle memory deallocation + talloc_free(msg); + } +} + +/** + * @see void gsmtapl1_rx_from_virt_um_cb(struct virt_um_inst *vui, struct msgb msg). + */ +void gsmtapl1_rx_from_virt_um(struct msgb *msg) +{ + gsmtapl1_rx_from_virt_um_inst_cb(l1_model_ms->vui, msg); +} + +/*! \brief convert GSMTAP channel type to RSL channel number + * \param[in] rsl_chantype RSL channel type + * \param[in] link_id RSL link identifier + * \returns GSMTAP channel type + */ +uint8_t chantype_gsmtap2rsl(uint8_t gsmtap_chantype) +{ + // TODO: proper retval for unknown channel + uint8_t ret = 0; + + switch (gsmtap_chantype) { + case GSMTAP_CHANNEL_TCH_F: + ret = RSL_CHAN_Bm_ACCHs; + break; + case GSMTAP_CHANNEL_TCH_H: + ret = RSL_CHAN_Lm_ACCHs; + break; + case GSMTAP_CHANNEL_SDCCH4: + ret = RSL_CHAN_SDCCH4_ACCH; + break; + case GSMTAP_CHANNEL_SDCCH8: + ret = RSL_CHAN_SDCCH8_ACCH; + break; + case GSMTAP_CHANNEL_BCCH: + ret = RSL_CHAN_BCCH; + break; + case GSMTAP_CHANNEL_RACH: + ret = RSL_CHAN_RACH; + break; + case GSMTAP_CHANNEL_PCH: + case GSMTAP_CHANNEL_AGCH: + ret = RSL_CHAN_PCH_AGCH; + break; + } + + // TODO: check how to handle this... +// if (link_id & 0x40) +// ret |= GSMTAP_CHANNEL_ACCH; + + return ret; +} diff --git a/src/host/virt_phy/src/gsmtapl1_if.h b/src/host/virt_phy/src/gsmtapl1_if.h new file mode 100644 index 00000000..8c7491c0 --- /dev/null +++ b/src/host/virt_phy/src/gsmtapl1_if.h @@ -0,0 +1,18 @@ +#pragma once + +#include <osmocom/core/msgb.h> +#include <osmocom/core/gsmtap.h> + +#include "l1ctl_sock.h" +#include "virtual_um.h" +#include "virt_l1_model.h" + +void gsmtapl1_init(struct l1_model_ms *model); + +void gsmtapl1_rx_from_virt_um_inst_cb(struct virt_um_inst *vui, struct msgb *msg); +void gsmtapl1_rx_from_virt_um(struct msgb *msg); + +void gsmtapl1_tx_to_virt_um_inst(struct virt_um_inst *vui, struct msgb *msg); +void gsmtapl1_tx_to_virt_um(struct msgb *msg); + +uint8_t chantype_gsmtap2rsl(uint8_t gsmtap_chantype); diff --git a/src/host/virt_phy/src/l1ctl_sap.c b/src/host/virt_phy/src/l1ctl_sap.c new file mode 100644 index 00000000..46121ed1 --- /dev/null +++ b/src/host/virt_phy/src/l1ctl_sap.c @@ -0,0 +1,833 @@ +/* L1CTL SAP implementation. */ + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> +#include <stdio.h> +#include <l1ctl_proto.h> +#include <netinet/in.h> + +#include "virtual_um.h" +#include "l1ctl_sock.h" +#include "virt_l1_model.h" +#include "l1ctl_sap.h" +#include "logging.h" + +static struct l1_model_ms *l1_model_ms = NULL; + +/** + * @brief Init the SAP. + */ +void l1ctl_sap_init(struct l1_model_ms *model) +{ + l1_model_ms = model; +} + +/** + * @brief L1CTL handler called for received messages from L23. + * + * Enqueues the message into the rx queue. + */ +void l1ctl_sap_rx_from_l23_inst_cb(struct l1ctl_sock_inst *lsi, struct msgb *msg) +{ + if (msg) { + DEBUGP(DL1C, "Message incoming from layer 2: %s\n", + osmo_hexdump(msg->data, msg->len)); + l1ctl_sap_handler(msg); + } +} +/** + * @see l1ctl_sap_rx_from_l23_cb(struct l1ctl_sock_inst *lsi, struct msgb *msg). + */ +void l1ctl_sap_rx_from_l23(struct msgb *msg) +{ + l1ctl_sap_rx_from_l23_inst_cb(l1_model_ms->lsi, msg); +} + +/** + * @brief Send a l1ctl message to layer 23. + * + * This will forward the message as it is to the upper layer. + */ +void l1ctl_sap_tx_to_l23_inst(struct l1ctl_sock_inst *lsi, struct msgb *msg) +{ + uint16_t *len; + /* prepend 16bit length before sending */ + len = (uint16_t *) msgb_push(msg, sizeof(*len)); + *len = htons(msg->len - sizeof(*len)); + + if(l1ctl_sock_write_msg(lsi, msg) == -1 ) { + //DEBUGP(DL1C, "Error writing to layer2 socket"); + } +} + +/** + * @see void l1ctl_sap_tx_to_l23(struct l1ctl_sock_inst *lsi, struct msgb *msg). + */ +void l1ctl_sap_tx_to_l23(struct msgb *msg) +{ + l1ctl_sap_tx_to_l23_inst(l1_model_ms->lsi, msg); +} + +/** + * @brief Allocates a msgb with set l1ctl header and room for a l3 header. + * + * @param [in] msg_type L1CTL primitive message type set to l1ctl_hdr. + * @return the allocated message. + * + * The message looks as follows: + * # headers + * [l1ctl_hdr] : initialized. msgb->l1h points here + * [spare-bytes] : L3_MSG_HEAD bytes reserved for l3 header + * # data + * [spare-bytes] : L3_MSG_DATA bytes reserved for data. msgb->tail points here. msgb->data points here. + */ +struct msgb *l1ctl_msgb_alloc(uint8_t msg_type) +{ + struct msgb *msg; + struct l1ctl_hdr *l1h; + msg = msgb_alloc_headroom(L3_MSG_SIZE, L3_MSG_HEAD, "l1ctl"); + if (!msg) { + while (1) { + puts("OOPS. Out of buffers...\n"); + } + + return NULL; + } + l1h = (struct l1ctl_hdr *)msgb_put(msg, sizeof(*l1h)); + l1h->msg_type = msg_type; + l1h->flags = 0; + + msg->l1h = (uint8_t *)l1h; + + return msg; +} + +/** + * @brief Allocates a msgb with set l1ctl header and room for a l3 header and puts l1ctl_info_dl to the msgb data. + * + * @param [in] msg_type L1CTL primitive message type set to l1ctl_hdr. + * @param [in] fn framenumber put into l1ctl_info_dl. + * @param [in] snr time slot number put into l1ctl_info_dl. + * @param [in] arfcn arfcn put into l1ctl_info_dl. + * @return the allocated message. + * + * The message looks as follows: + * # headers + * [l1ctl_hdr] : initialized. msgb->l1h points here + * [spare-bytes] : L3_MSG_HEAD bytes reserved for l3 header + * # data + * [l1ctl_info_dl] : initialized with params. msgb->data points here. + * [spare-bytes] : L3_MSG_DATA bytes reserved for data. msgb->tail points here. + */ +struct msgb *l1ctl_create_l2_msg(int msg_type, uint32_t fn, uint16_t snr, + uint16_t arfcn) +{ + struct l1ctl_info_dl *dl; + struct msgb *msg = l1ctl_msgb_alloc(msg_type); + + dl = (struct l1ctl_info_dl *)msgb_put(msg, sizeof(*dl)); + dl->frame_nr = htonl(fn); + dl->snr = snr; + dl->band_arfcn = htons(arfcn); + + return msg; +} + +/** + * @brief General handler for incoming L1CTL messages from layer 2/3. + * + * This handler will dequeue the rx queue (if !empty) and call the specific routine for the dequeued l1ctl message. + * + */ +void l1ctl_sap_handler(struct msgb *msg) +{ +// struct msgb *msg; + struct l1ctl_hdr *l1h; + unsigned long flags; + + if (!msg) + return; + + l1h = (struct l1ctl_hdr *)msg->data; + + if (sizeof(*l1h) > msg->len) { + LOGP(DL1C, LOGL_NOTICE, "Short message. %u\n", msg->len); + goto exit_msgbfree; + } + + switch (l1h->msg_type) { + case L1CTL_FBSB_REQ: + l1ctl_rx_fbsb_req(msg); + break; + case L1CTL_DM_EST_REQ: + l1ctl_rx_dm_est_req(msg); + break; + case L1CTL_DM_REL_REQ: + l1ctl_rx_dm_rel_req(msg); + break; + case L1CTL_PARAM_REQ: + l1ctl_rx_param_req(msg); + break; + case L1CTL_DM_FREQ_REQ: + l1ctl_rx_dm_freq_req(msg); + break; + case L1CTL_CRYPTO_REQ: + l1ctl_rx_crypto_req(msg); + break; + case L1CTL_RACH_REQ: + l1ctl_rx_rach_req(msg); + break; + case L1CTL_DATA_REQ: + l1ctl_rx_data_req(msg); + /* we have to keep the msgb, not free it! */ + goto exit_nofree; + case L1CTL_PM_REQ: + l1ctl_rx_pm_req(msg); + break; + case L1CTL_RESET_REQ: + l1ctl_rx_reset_req(msg); + break; + case L1CTL_CCCH_MODE_REQ: + l1ctl_rx_ccch_mode_req(msg); + break; + case L1CTL_TCH_MODE_REQ: + l1ctl_rx_tch_mode_req(msg); + break; + case L1CTL_NEIGH_PM_REQ: + l1ctl_rx_neigh_pm_req(msg); + break; + case L1CTL_TRAFFIC_REQ: + l1ctl_rx_traffic_req(msg); + /* we have to keep the msgb, not free it! */ + goto exit_nofree; + case L1CTL_SIM_REQ: + l1ctl_rx_sim_req(msg); + break; + } + + exit_msgbfree: msgb_free(msg); + exit_nofree: return; +} + +/*************************************************************** + * L1CTL RX ROUTINES ******************************************* + ***************************************************************/ + +/** + * @brief Handler for received L1CTL_FBSB_REQ from L23. + * + * -- frequency burst synchronisation burst request -- + * + * @param [in] msg the received message. + * + * Transmit frequency control and synchronisation bursts on FCCH and SCH to calibrate transceiver and search for base stations. + * Sync to a given arfcn. + * + * Note: Not needed for virtual physical layer. + * TODO: Could be used to bind/connect to different virtual_bts sockets with a arfcn-socket mapping. + */ +void l1ctl_rx_fbsb_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data; + struct l1ctl_fbsb_req *sync_req = (struct l1ctl_fbsb_req *)l1h->data; + + DEBUGP(DL1C, + "Received and handled from l23 - L1CTL_FBSB_REQ (arfcn=%u, flags=0x%x)\n", + ntohs(sync_req->band_arfcn), sync_req->flags); + + l1ctl_tx_fbsb_conf(0, ntohs(sync_req->band_arfcn)); +} + +/** + * @brief Handler for received L1CTL_DM_EST_REQ from L23. + * + * -- dedicated mode established request -- + * + * @param [in] msg the received message. + * + * Handle state change from idle to dedicated mode. + * + * TODO: Implement this handler routine! + */ +void l1ctl_rx_dm_est_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data; + struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *)l1h->data; + struct l1ctl_dm_est_req *est_req = + (struct l1ctl_dm_est_req *)ul->payload; + + DEBUGP(DL1C, + "Received and handled from l23 - L1CTL_DM_EST_REQ (arfcn=%u, chan_nr=0x%02x, tsc=%u)\n", + ntohs(est_req->h0.band_arfcn), ul->chan_nr, + est_req->tsc); + +// /* disable neighbour cell measurement of C0 TS 0 */ +// mframe_disable(MF_TASK_NEIGH_PM51_C0T0); +// +// /* configure dedicated channel state */ +// l1s.dedicated.type = chan_nr2dchan_type(ul->chan_nr); +// l1s.dedicated.tsc = est_req->tsc; +// l1s.dedicated.tn = ul->chan_nr & 0x7; +// l1s.dedicated.h = est_req->h; +// +// if (est_req->h) { +// int i; +// l1s.dedicated.h1.hsn = est_req->h1.hsn; +// l1s.dedicated.h1.maio = est_req->h1.maio; +// l1s.dedicated.h1.n = est_req->h1.n; +// for (i=0; i<est_req->h1.n; i++) +// l1s.dedicated.h1.ma[i] = ntohs(est_req->h1.ma[i]); +// } else { +// l1s.dedicated.h0.arfcn = ntohs(est_req->h0.band_arfcn); +// } +// +// /* TCH config */ +// if (chan_nr_is_tch(ul->chan_nr)) { +// /* Mode */ +// l1a_tch_mode_set(est_req->tch_mode); +// l1a_audio_mode_set(est_req->audio_mode); +// +// /* Sync */ +// l1s.tch_sync = 1; /* can be set without locking */ +// +// /* Audio path */ +// audio_set_enabled(est_req->tch_mode, est_req->audio_mode); +// } +// +// /* figure out which MF tasks to enable */ +// l1a_mftask_set(chan_nr2mf_task_mask(ul->chan_nr, NEIGH_MODE_PM)); +} + +/** + * @brief Handler for received L1CTL_DM_FREQ_REQ from L23. + * + * -- dedicated mode frequency request -- + * + * @param [in] msg the received message. + * + * Handle frequency change in dedicated mode. E.g. used for frequency hopping. + * + * Note: Not needed for virtual physical layer. + */ +void l1ctl_rx_dm_freq_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data; + struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *)l1h->data; + struct l1ctl_dm_freq_req *freq_req = + (struct l1ctl_dm_freq_req *)ul->payload; + + DEBUGP(DL1C, + "Received and ignored from l23 - L1CTL_DM_FREQ_REQ (arfcn=%u, tsc=%u)\n", + ntohs(freq_req->h0.band_arfcn), freq_req->tsc); +} + +/** + * @brief Handler for received L1CTL_CRYPTO_REQ from L23. + * + * -- cryptographic request -- + * + * @param [in] msg the received message. + * + * Configure the key and algorithm used for cryptographic operations in the DSP (Digital Signal Processor). + * + * Note: in the virtual physical layer the cryptographic operations are not handled in the DSP. + * + * TODO: Implement cryptographic operations for virtual um! + * TODO: Implement this handler routine! + */ +void l1ctl_rx_crypto_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data; + struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *)l1h->data; + struct l1ctl_crypto_req *cr = (struct l1ctl_crypto_req *)ul->payload; + uint8_t key_len = msg->len - sizeof(*l1h) - sizeof(*ul) - sizeof(*cr); + + DEBUGP(DL1C, + "Received and handled from l23 - L1CTL_CRYPTO_REQ (algo=A5/%u, len=%u)\n", + cr->algo, key_len); + +// if (cr->algo && key_len != 8) { +// DEBUGP(DL1C, "L1CTL_CRYPTO_REQ -> Invalid key\n"); +// return; +// } +// +// dsp_load_ciph_param(cr->algo, cr->key); +} + +/** + * @brief Handler for received L1CTL_DM_REL_REQ from L23. + * + * -- dedicated mode release request -- + * + * @param [in] msg the received message. + * + * Handle state change from dedicated to idle mode. Flush message buffers of dedicated channel. + * + * TODO: Implement this handler routine! + */ +void l1ctl_rx_dm_rel_req(struct msgb *msg) +{ +// struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data; + + DEBUGP(DL1C, "Received and ignored from l23 - L1CTL_DM_REL_REQ\n"); +// l1a_mftask_set(0); +// l1s.dedicated.type = GSM_DCHAN_NONE; +// l1a_txq_msgb_flush(&l1s.tx_queue[L1S_CHAN_MAIN]); +// l1a_txq_msgb_flush(&l1s.tx_queue[L1S_CHAN_SACCH]); +// l1a_txq_msgb_flush(&l1s.tx_queue[L1S_CHAN_TRAFFIC]); +// l1a_meas_msgb_set(NULL); +// dsp_load_ciph_param(0, NULL); +// l1a_tch_mode_set(GSM48_CMODE_SIGN); +// audio_set_enabled(GSM48_CMODE_SIGN, 0); +// l1s.neigh_pm.n = 0; +} + +/** + * @brief Handler for received L1CTL_PARAM_REQ from L23. + * + * -- parameter request -- + * + * @param [in] msg the received message. + * + * Configure transceiver parameters timing advance value and sending power. + * + * Note: Not needed for virtual physical layer. + */ +void l1ctl_rx_param_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data; + struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *)l1h->data; + struct l1ctl_par_req *par_req = (struct l1ctl_par_req *)ul->payload; + + DEBUGP(DL1C, + "Received and ignored from l23 - L1CTL_PARAM_REQ (ta=%d, tx_power=%d)\n", + par_req->ta, par_req->tx_power); +} + +/** + * @brief Handler for received L1CTL_RACH_REQ from L23. + * + * -- random access channel request -- + * + * @param [in] msg the received message. + * + * Transmit RACH request on RACH. + * + * TODO: Implement this handler routine! + */ +void l1ctl_rx_rach_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data; + struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *)l1h->data; + struct l1ctl_rach_req *rach_req = (struct l1ctl_rach_req *)ul->payload; + + DEBUGP(DL1C, + "Received and handled from l23 - L1CTL_RACH_REQ (ra=0x%02x, offset=%d combined=%d)\n", + rach_req->ra, ntohs(rach_req->offset), + rach_req->combined); + +// l1a_rach_req(ntohs(rach_req->offset), rach_req->combined, +// rach_req->ra); +} + +/** + * @brief Handler for received L1CTL_DATA_REQ from L23. + * + * -- data request -- + * + * @param [in] msg the received message. + * + * Transmit message on a signalling channel. FACCH/SDCCH or SACCH depending on the headers set link id (TS 8.58 - 9.3.2). + * + * TODO: Implement this handler routine! + */ +void l1ctl_rx_data_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data; + struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *)l1h->data; + struct l1ctl_data_ind *data_ind = (struct l1ctl_data_ind *)ul->payload; + struct llist_head *tx_queue; + + DEBUGP(DL1C, + "Received and handled from l23 - L1CTL_DATA_REQ (link_id=0x%02x)\n", + ul->link_id); + +// msg->l3h = data_ind->data; +// if (ul->link_id & 0x40) { +// struct gsm48_hdr *gh = (struct gsm48_hdr *)(data_ind->data + 5); +// if (gh->proto_discr == GSM48_PDISC_RR +// && gh->msg_type == GSM48_MT_RR_MEAS_REP) { +// DEBUGP(DL1C, "updating measurement report\n"); +// l1a_meas_msgb_set(msg); +// return; +// } +// tx_queue = &l1s.tx_queue[L1S_CHAN_SACCH]; +// } else +// tx_queue = &l1s.tx_queue[L1S_CHAN_MAIN]; +// +// DEBUGP(DL1C, "ul=%p, ul->payload=%p, data_ind=%p, data_ind->data=%p l3h=%p\n", +// ul, ul->payload, data_ind, data_ind->data, msg->l3h); +// +// l1a_txq_msgb_enq(tx_queue, msg); +} + +/** + * @brief Handler for received L1CTL_PM_REQ from L23. + * + * -- power measurement request -- + * + * @param [in] msg the received message. + * + * Process power measurement for a given range of arfcns to calculate signal power and connection quality. + * + * Note: We do not need to calculate that for the virtual physical layer, but l23 apps can expect a response. So this response is mocked here. + */ +void l1ctl_rx_pm_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data; + struct l1ctl_pm_req *pm_req = (struct l1ctl_pm_req *)l1h->data; + struct msgb *resp_msg = l1ctl_msgb_alloc(L1CTL_PM_CONF); + uint16_t arfcn_next; + // convert to host order + pm_req->range.band_arfcn_from = ntohs(pm_req->range.band_arfcn_from); + pm_req->range.band_arfcn_to = ntohs(pm_req->range.band_arfcn_to); + + DEBUGP(DL1C, "Received from l23 - L1CTL_PM_REQ TYPE=%u, FROM=%d, TO=%d\n", + pm_req->type, pm_req->range.band_arfcn_from, pm_req->range.band_arfcn_to); + + for(arfcn_next = pm_req->range.band_arfcn_from; arfcn_next <= pm_req->range.band_arfcn_to; ++arfcn_next) { + struct l1ctl_pm_conf *pm_conf = (struct l1ctl_pm_conf *)msgb_put(resp_msg, sizeof(*pm_conf)); + pm_conf->band_arfcn = htons(arfcn_next); + // rxlev 63 is great, 0 is bad the two values are probably min and max + pm_conf->pm[0] = 63; + pm_conf->pm[1] = 63; + if(arfcn_next == pm_req->range.band_arfcn_to) { + struct l1ctl_hdr *resp_l1h = resp_msg->l1h; + resp_l1h->flags |= L1CTL_F_DONE; + } + // no more space in msgb, flush to l2 + if(msgb_tailroom(resp_msg) < sizeof(*pm_conf)) { + l1ctl_sap_tx_to_l23(resp_msg); + resp_msg = l1ctl_msgb_alloc(L1CTL_PM_CONF); + } + } + if(resp_msg) { + l1ctl_sap_tx_to_l23(resp_msg); + } +} + +/** + * @brief Handler for received L1CTL_RESET_REQ from L23. + * + * -- reset request -- + * + * @param [in] msg the received message. + * + * Reset layer 1 (state machine, scheduler, transceiver) depending on the reset type. + * + */ +void l1ctl_rx_reset_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data; + struct l1ctl_reset *reset_req = (struct l1ctl_reset *)l1h->data; + + switch (reset_req->type) { + case L1CTL_RES_T_FULL: + DEBUGP(DL1C, + "Received and handled from l23 - L1CTL_RESET_REQ (type=FULL)\n"); +// l1s_reset(); +// l1s_reset_hw(); +// audio_set_enabled(GSM48_CMODE_SIGN, 0); + l1ctl_tx_reset(L1CTL_RESET_CONF, reset_req->type); + break; + case L1CTL_RES_T_SCHED: + DEBUGP(DL1C, + "Received and handled from l23 - L1CTL_RESET_REQ (type=SCHED)\n"); +// sched_gsmtime_reset(); + l1ctl_tx_reset(L1CTL_RESET_CONF, reset_req->type); + break; + default: + LOGP(DL1C, LOGL_ERROR, + "Received and ignored from l23 - L1CTL_RESET_REQ (type=unknown)\n"); + break; + } +} + +/** + * @brief Handler for received L1CTL_CCCH_MODE_REQ from L23. + * + * -- common control channel mode request -- + * + * @param [in] msg the received message. + * + * Configure CCCH combined / non-combined mode. + * + * TODO: Implement this handler routine! + */ +void l1ctl_rx_ccch_mode_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data; + struct l1ctl_ccch_mode_req *ccch_mode_req = + (struct l1ctl_ccch_mode_req *)l1h->data; + uint8_t ccch_mode = ccch_mode_req->ccch_mode; + + DEBUGP(DL1C, "Received and handled from l23 - L1CTL_CCCH_MODE_REQ\n"); + + l1_model_ms->state->serving_cell.ccch_mode = ccch_mode; + + // check if more has to be done here + + l1ctl_tx_ccch_mode_conf(ccch_mode); + +// /* pre-set the CCCH mode */ +// l1s.serving_cell.ccch_mode = ccch_mode; +// +// /* Update task */ +// mframe_disable(MF_TASK_CCCH_COMB); +// mframe_disable(MF_TASK_CCCH); +// +// if (ccch_mode == CCCH_MODE_COMBINED) +// mframe_enable(MF_TASK_CCCH_COMB); +// else if (ccch_mode == CCCH_MODE_NON_COMBINED) +// mframe_enable(MF_TASK_CCCH); +// +// l1ctl_tx_ccch_mode_conf(ccch_mode); +} + +/** + * @brief Handler for received L1CTL_TCH_MODE_REQ from L23. + * + * -- traffic channel mode request -- + * + * @param [in] msg the received message. + * + * Configure TCH mode and audio mode. + * + * TODO: Implement this handler routine! + */ +void l1ctl_rx_tch_mode_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data; + struct l1ctl_tch_mode_req *tch_mode_req = + (struct l1ctl_tch_mode_req *)l1h->data; + uint8_t tch_mode = tch_mode_req->tch_mode; + uint8_t audio_mode = tch_mode_req->audio_mode; + + DEBUGP(DL1C, + "Received and handled from l23 - L1CTL_TCH_MODE_REQ (tch_mode=0x%02x audio_mode=0x%02x)\n", + tch_mode, audio_mode); +// tch_mode = l1a_tch_mode_set(tch_mode); +// audio_mode = l1a_audio_mode_set(audio_mode); +// +// audio_set_enabled(tch_mode, audio_mode); +// +// l1s.tch_sync = 1; /* Needed for audio to work */ +// +// l1ctl_tx_tch_mode_conf(tch_mode, audio_mode); +} + +/** + * @brief Handler for received L1CTL_NEIGH_PM_REQ from L23. + * + * -- neighbor power measurement request -- + * + * @param [in] msg the received message. + * + * Update the maintained list of neighbor cells used in neighbor cell power measurement. + * The neighbor cell description is one of the info messages sent by the BTS on BCCH. + * This method will also enable neighbor measurement in the multiframe scheduler. + * + * Note: Not needed for virtual physical layer. + */ +void l1ctl_rx_neigh_pm_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data; + struct l1ctl_neigh_pm_req *pm_req = + (struct l1ctl_neigh_pm_req *)l1h->data; + + DEBUGP(DL1C, + "Received and ignored from l23 - L1CTL_NEIGH_PM_REQ new list with %u entries\n", + pm_req->n); +} + +/** + * @brief Handler for received L1CTL_TRAFFIC_REQ from L23. + * + * -- traffic request -- + * + * @param [in] msg the received message. + * + * Enqueue the message (traffic frame) to the L1 state machine's transmit queue. + * Will drop the traffic frame at queue sizes >= 4. + * + * TODO: Implement this handler routine! + */ +void l1ctl_rx_traffic_req(struct msgb *msg) +{ + struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data; + struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *)l1h->data; + struct l1ctl_traffic_req *tr = (struct l1ctl_traffic_req *)ul->payload; + int num = 0; + + DEBUGP(DL1C, "Received and handled from l23 - L1CTL_TRAFFIC_REQ\n"); + +// msg->l2h = tr->data; + +// num = l1a_txq_msgb_count(&l1s.tx_queue[L1S_CHAN_TRAFFIC]); +// if (num >= 4) { +// DEBUGP(DL1C, "dropping traffic frame\n"); +// msgb_free(msg); +// return; +// } +// +// l1a_txq_msgb_enq(&l1s.tx_queue[L1S_CHAN_TRAFFIC], msg); +} + +/** + * @brief Handler for received L1CTL_SIM_REQ from L23. + * + * -- sim request -- + * + * @param [in] msg the received message. + * + * Forward and a sim request to the SIM APDU. + * + * Note: Not needed for virtual layer. Please configure layer23 application to use test-sim implementation. + * ms <x> + * -------- + * sim test + * test-sim + * imsi <xxxxxxxxxxxxxxx> + * ki comp128 <xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx> + * -------- + */ +void l1ctl_rx_sim_req(struct msgb *msg) +{ + uint16_t len = msg->len - sizeof(struct l1ctl_hdr); + uint8_t *data = msg->data + sizeof(struct l1ctl_hdr); + + DEBUGP(DL1C, + "Received and ignored from l23 - SIM Request length: %u, data: %s: ", + len, osmo_hexdump(data, sizeof(data))); + +} + +/*************************************************************** + * L1CTL TX ROUTINES ******************************************* + ***************************************************************/ + +/** + * @brief Transmit L1CTL_RESET_IND or L1CTL_RESET_CONF to layer 23. + * + * -- reset indication / confirm -- + * + * @param [in] msg_type L1CTL primitive message type. + * @param [in] reset_type reset type (full, boot or just scheduler reset). + */ +void l1ctl_tx_reset(uint8_t msg_type, uint8_t reset_type) +{ + struct msgb *msg = l1ctl_msgb_alloc(msg_type); + struct l1ctl_reset *reset_resp; + reset_resp = (struct l1ctl_reset *)msgb_put(msg, sizeof(*reset_resp)); + reset_resp->type = reset_type; + + DEBUGP(DL1C, "Sending to l23 - %s (reset_type: %u)\n", + getL1ctlPrimName(msg_type), reset_type); + l1ctl_sap_tx_to_l23(msg); +} + +/** + * @brief Transmit L1CTL msg of a given type to layer 23. + * + * @param [in] msg_type L1CTL primitive message type. + */ +void l1ctl_tx_msg(uint8_t msg_type) +{ + struct msgb *msg = l1ctl_msgb_alloc(msg_type); + DEBUGP(DL1C, "Sending to l23 - %s\n", getL1ctlPrimName(msg_type)); + l1ctl_sap_tx_to_l23(msg); +} + +/** + * @brief Transmit L1CTL_FBSB_CONF to l23. + * + * -- frequency burst synchronisation burst confirm -- + * + * @param [in] res 0 -> success, 255 -> error. + * @param [in] arfcn the arfcn we are synced to. + * + * No calculation needed for virtual pyh -> uses default values for a good link quality. + */ +void l1ctl_tx_fbsb_conf(uint8_t res, uint16_t arfcn) +{ + struct msgb *msg; + struct l1ctl_fbsb_conf *resp; + uint32_t fn = 0; // 0 should be okay here + uint16_t snr = 40; // signal noise ratio > 40db is best signal. + int16_t initial_freq_err = 0; // 0 means no error. + uint8_t bsic = 0; + + msg = l1ctl_create_l2_msg(L1CTL_FBSB_CONF, fn, + snr, + arfcn); + + resp = (struct l1ctl_fbsb_conf *) msgb_put(msg, sizeof(*resp)); + resp->initial_freq_err = htons(initial_freq_err); + resp->result = res; + resp->bsic = bsic; + + DEBUGP(DL1C, "Sending to l23 - %s (res: %u)\n", + getL1ctlPrimName(L1CTL_FBSB_CONF), res); + + l1ctl_sap_tx_to_l23(msg); +} + +/** + * @brief Transmit L1CTL_CCCH_MODE_CONF to layer 23. + * + * -- common control channel mode confirm -- + * + * @param [in] ccch_mode the new configured ccch mode. Combined or non-combined, see l1ctl_proto. + * + * Called by layer 1 to inform layer 2 that the ccch mode was successfully changed. + */ +void l1ctl_tx_ccch_mode_conf(uint8_t ccch_mode) +{ + struct msgb *msg = l1ctl_msgb_alloc(L1CTL_CCCH_MODE_CONF); + struct l1ctl_ccch_mode_conf *mode_conf; + mode_conf = (struct l1ctl_ccch_mode_conf *)msgb_put(msg, + sizeof(*mode_conf)); + mode_conf->ccch_mode = ccch_mode; + + DEBUGP(DL1C, "Sending to l23 - L1CTL_CCCH_MODE_CONF (mode: %u)\n", + ccch_mode); + l1ctl_sap_tx_to_l23(msg); +} + +/** + * @brief Transmit L1CTL_TCH_MODE_CONF to layer 23. + * + * -- traffic channel mode confirm -- + * + * @param [in] tch_mode the new configured traffic channel mode, see gsm48_chan_mode in gsm_04_08.h. + * @param [in] audio_mode the new configured audio mode(s), see l1ctl_tch_mode_req in l1ctl_proto.h. + * + * Called by layer 1 to inform layer 23 that the traffic channel mode was successfully changed. + */ +void l1ctl_tx_tch_mode_conf(uint8_t tch_mode, uint8_t audio_mode) +{ + struct msgb *msg = l1ctl_msgb_alloc(L1CTL_TCH_MODE_CONF); + struct l1ctl_tch_mode_conf *mode_conf; + mode_conf = (struct l1ctl_tch_mode_conf *)msgb_put(msg, + sizeof(*mode_conf)); + mode_conf->tch_mode = tch_mode; + mode_conf->audio_mode = audio_mode; + + DEBUGP(DL1C, + "Sending to l23 - L1CTL_TCH_MODE_CONF (tch_mode: %u, audio_mode: %u)\n", tch_mode, + audio_mode); + l1ctl_sap_tx_to_l23(msg); +} + + diff --git a/src/host/virt_phy/src/l1ctl_sap.h b/src/host/virt_phy/src/l1ctl_sap.h new file mode 100644 index 00000000..2d671287 --- /dev/null +++ b/src/host/virt_phy/src/l1ctl_sap.h @@ -0,0 +1,53 @@ +#pragma once + +#include <stdint.h> +#include <osmocom/core/msgb.h> +#include <l1ctl_proto.h> + +#include "l1ctl_sock.h" +#include "virtual_um.h" +#include "virt_l1_model.h" + +/* following sizes are used for message allocation */ +/* size of layer 3 header */ +#define L3_MSG_HEAD 4 +/* size of layer 3 payload */ +#define L3_MSG_DATA 200 +#define L3_MSG_SIZE (sizeof(struct l1ctl_hdr) + L3_MSG_HEAD + L3_MSG_DATA) + +void l1ctl_sap_init(struct l1_model_ms *model); +void l1ctl_sap_tx_to_l23_inst(struct l1ctl_sock_inst *lsi, struct msgb *msg); +void l1ctl_sap_tx_to_l23(struct msgb *msg); +void l1ctl_sap_rx_from_l23_inst_cb(struct l1ctl_sock_inst *lsi, struct msgb *msg); +void l1ctl_sap_rx_from_l23(struct msgb *msg); +void l1ctl_sap_handler(struct msgb *msg); + +/* utility methods */ +struct msgb *l1ctl_msgb_alloc(uint8_t msg_type); +struct msgb *l1ctl_create_l2_msg(int msg_type, uint32_t fn, uint16_t snr, + uint16_t arfcn); + +/* receive routines */ +void l1ctl_rx_fbsb_req(struct msgb *msg); +void l1ctl_rx_dm_est_req(struct msgb *msg); +void l1ctl_rx_dm_rel_req(struct msgb *msg); +void l1ctl_rx_param_req(struct msgb *msg); +void l1ctl_rx_dm_freq_req(struct msgb *msg); +void l1ctl_rx_crypto_req(struct msgb *msg); +void l1ctl_rx_rach_req(struct msgb *msg); +void l1ctl_rx_data_req(struct msgb *msg); +void l1ctl_rx_pm_req(struct msgb *msg); +void l1ctl_rx_reset_req(struct msgb *msg); +void l1ctl_rx_ccch_mode_req(struct msgb *msg); +void l1ctl_rx_tch_mode_req(struct msgb *msg); +void l1ctl_rx_neigh_pm_req(struct msgb *msg); +void l1ctl_rx_traffic_req(struct msgb *msg); +void l1ctl_rx_sim_req(struct msgb *msg); + +/* transmit routines */ +void l1ctl_tx_reset(uint8_t msg_type, uint8_t reset_type); +void l1ctl_tx_pm_conf(struct l1ctl_pm_req *pm_req); +void l1ctl_tx_fbsb_conf(uint8_t res, uint16_t arfcn); +void l1ctl_tx_ccch_mode_conf(uint8_t ccch_mode); +void l1ctl_tx_tch_mode_conf(uint8_t tch_mode, uint8_t audio_mode); +void l1ctl_tx_msg(uint8_t msg_type); diff --git a/src/host/virt_phy/src/l1ctl_sock.c b/src/host/virt_phy/src/l1ctl_sock.c new file mode 100644 index 00000000..e52b731a --- /dev/null +++ b/src/host/virt_phy/src/l1ctl_sock.c @@ -0,0 +1,197 @@ +/* Socket based Layer1 <-> Layer23 communication over L1CTL primitives. */ + +/* (C) 2016 Sebastian Stumpf + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdint.h> +#include <fcntl.h> +#include <errno.h> +#include <termios.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/select.h> +#include <osmocom/core/serial.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/timer.h> + +#include <arpa/inet.h> + +#include "l1ctl_sock.h" +#include "virtual_um.h" +#include "logging.h" + +#define L1CTL_SOCK_MSGB_SIZE 256 + +/** + * @brief L1CTL socket file descriptor callback function. + * + * @param ofd The osmocom file descriptor. + * @param what Indicates if the fd has a read, write or exception request. See select.h. + * + * Will be called by osmo_select_main() if data on fd is pending. + */ +static int l1ctl_sock_data_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct l1ctl_sock_inst *lsi = ofd->data; + int cnt = 0; + // Check if request is really read request + if (what & BSC_FD_READ) { + struct msgb *msg = msgb_alloc(L1CTL_SOCK_MSGB_SIZE, + "L1CTL sock rx"); + int rc; + uint16_t len; + + // read length of the message first and convert to host byte order + rc = read(ofd->fd, &len, sizeof(len)); + if (rc < sizeof(len)) { + goto ERR; + } + // convert to host byte order + len = ntohs(len); + if (len <= 0 || len > L1CTL_SOCK_MSGB_SIZE) { + goto ERR; + } + rc = read(ofd->fd, msgb_data(msg), len); + + if (rc == len) { + msgb_put(msg, rc); + msg->l1h = msgb_data(msg); + lsi->recv_cb(lsi, msg); + return 0; + } +ERR: + perror("Failed to receive msg from l2. Connection will be closed.\n"); + l1ctl_sock_disconnect(lsi); + } + return 0; + +} + +static int l1ctl_sock_accept_cb(struct osmo_fd *ofd, unsigned int what) +{ + + struct l1ctl_sock_inst *lsi = ofd->data; + struct sockaddr_un local_addr; + socklen_t addr_len = sizeof(struct sockaddr_in); + int fd; + + fd = accept(ofd->fd, (struct sockaddr *)&local_addr, &addr_len); + if (fd < 0) { + fprintf(stderr, "Failed to accept connection to l2.\n"); + return -1; + } + + lsi->connection.fd = fd; + lsi->connection.when = BSC_FD_READ; + lsi->connection.cb = l1ctl_sock_data_cb; + lsi->connection.data = lsi; + + if (osmo_fd_register(&lsi->connection) != 0) { + fprintf(stderr, "Failed to register the l2 connection fd.\n"); + return -1; + } + return 0; +} + +struct l1ctl_sock_inst *l1ctl_sock_init( + void *ctx, + void (*recv_cb)(struct l1ctl_sock_inst *lsi, struct msgb *msg), + char *path) +{ + struct l1ctl_sock_inst *lsi; + struct sockaddr_un local_addr; + int fd, rc; + + if (!path) + path = L1CTL_SOCK_PATH; + + if ((fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { + fprintf(stderr, "Failed to create Unix Domain Socket.\n"); + return NULL; + } + + local_addr.sun_family = AF_LOCAL; + strcpy(local_addr.sun_path, path); + unlink(local_addr.sun_path); + + if ((rc = bind(fd, (struct sockaddr *)&local_addr, sizeof(local_addr))) + != 0) { + fprintf(stderr, "Failed to bind the unix domain socket. '%s'\n", + local_addr.sun_path); + return NULL; + } + + if (listen(fd, 0) != 0) { + fprintf(stderr, "Failed to listen.\n"); + return NULL; + } + + lsi = talloc_zero(ctx, struct l1ctl_sock_inst); + lsi->priv = NULL; + lsi->recv_cb = recv_cb; + lsi->ofd.data = lsi; + lsi->ofd.fd = fd; + lsi->ofd.when = BSC_FD_READ; + lsi->ofd.cb = l1ctl_sock_accept_cb; + // no connection -> invalid filedescriptor and not 0 (==std_in) + lsi->connection.fd = -1; + + osmo_fd_register(&lsi->ofd); + + return lsi; +} + +void l1ctl_sock_destroy(struct l1ctl_sock_inst *lsi) +{ + struct osmo_fd *ofd = &lsi->ofd; + + osmo_fd_unregister(ofd); + close(ofd->fd); + ofd->fd = -1; + ofd->when = 0; + + talloc_free(lsi); +} + +void l1ctl_sock_disconnect(struct l1ctl_sock_inst *lsi) +{ + struct osmo_fd *ofd = &lsi->connection; + osmo_fd_unregister(ofd); + close(ofd->fd); + ofd->fd = -1; + ofd->when = 0; +} + +int l1ctl_sock_write_msg(struct l1ctl_sock_inst *lsi, struct msgb *msg) +{ + int rc; + rc = write(lsi->connection.fd, msgb_data(msg), msgb_length(msg)); + msgb_free(msg); + return rc; +} diff --git a/src/host/virt_phy/src/l1ctl_sock.h b/src/host/virt_phy/src/l1ctl_sock.h new file mode 100644 index 00000000..ef9799c2 --- /dev/null +++ b/src/host/virt_phy/src/l1ctl_sock.h @@ -0,0 +1,37 @@ +#pragma once + +#include <osmocom/core/msgb.h> +#include <osmocom/core/select.h> + +#define L1CTL_SOCK_PATH "/tmp/osmocom_l2" + +/* L1CTL socket instance contains socket data. */ +struct l1ctl_sock_inst { + void *priv; /* Will be appended after osmo-fd's data pointer. */ + struct osmo_fd connection; /* L1CTL connection to l2 app */ + struct osmo_fd ofd; /* Osmocom file descriptor to accept L1CTL connections. */ + void (*recv_cb)(struct l1ctl_sock_inst *vui, struct msgb *msg); /* Callback function called for incoming data from l2 app. */ +}; + +/** + * @brief Initialise the l1ctl socket for communication with l2 apps. + */ +struct l1ctl_sock_inst *l1ctl_sock_init( + void *ctx, + void (*recv_cb)(struct l1ctl_sock_inst *lsi, struct msgb *msg), + char *path); + +/** + * @brief Transmit message to l2. + */ +int l1ctl_sock_write_msg(struct l1ctl_sock_inst *lsi, struct msgb *msg); + +/** + * @brief Destroy instance. + */ +void l1ctl_sock_destroy(); + +/** + * @brief Disconnect current connection. + */ +void l1ctl_sock_disconnect(struct l1ctl_sock_inst *lsi); diff --git a/src/host/virt_phy/src/logging.c b/src/host/virt_phy/src/logging.c new file mode 100644 index 00000000..a017a521 --- /dev/null +++ b/src/host/virt_phy/src/logging.c @@ -0,0 +1,113 @@ +/* Logging/Debug support of the virtual physical layer */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + + +#include <osmocom/core/utils.h> +#include <osmocom/core/application.h> + +#include "logging.h" + +const char* l1ctlPrimNames[] = { + "_L1CTL_NONE", + "L1CTL_FBSB_REQ", + "L1CTL_FBSB_CONF", + "L1CTL_DATA_IND", + "L1CTL_RACH_REQ", + "L1CTL_DM_EST_REQ", + "L1CTL_DATA_REQ", + "L1CTL_RESET_IND", + "L1CTL_PM_REQ", + "L1CTL_PM_CONF", + "L1CTL_ECHO_REQ", + "L1CTL_ECHO_CONF", + "L1CTL_RACH_CONF", + "L1CTL_RESET_REQ", + "L1CTL_RESET_CONF", + "L1CTL_DATA_CONF", + "L1CTL_CCCH_MODE_REQ", + "L1CTL_CCCH_MODE_CONF", + "L1CTL_DM_REL_REQ", + "L1CTL_PARAM_REQ", + "L1CTL_DM_FREQ_REQ", + "L1CTL_CRYPTO_REQ", + "L1CTL_SIM_REQ", + "L1CTL_SIM_CONF", + "L1CTL_TCH_MODE_REQ", + "L1CTL_TCH_MODE_CONF", + "L1CTL_NEIGH_PM_REQ", + "L1CTL_NEIGH_PM_IND", + "L1CTL_TRAFFIC_REQ", + "L1CTL_TRAFFIC_CONF", + "L1CTL_TRAFFIC_IND" +}; + +static const struct log_info_cat default_categories[] = { + [DL1C] = { + .name = "DL1C", + .description = "Layer 1 Control", + .color = "\033[1;31m", + .enabled = 1, + .loglevel = LOGL_DEBUG, + }, + [DVIRPHY] = { + .name = "DVIRPHY", + .description = "Virtual Layer 1 Interface", + .color = "\033[1;31m", + .enabled = 1, + .loglevel = LOGL_DEBUG, + } +}; + +const struct log_info ms_log_info = { + .filter_fn = NULL, + .cat = default_categories, + .num_cat = ARRAY_SIZE(default_categories), +}; + +/** + * Initialize the logging system for the virtual physical layer. + */ +int ms_log_init(char *cat_mask) { + struct log_target *stderr_target; + + log_init(&ms_log_info, NULL); + stderr_target = log_target_create_stderr(); + if(!stderr) { + return -1; + } + log_add_target(stderr_target); + log_set_all_filter(stderr_target, 1); + //log_set_log_level(stderr_target, 1); + log_set_print_filename(stderr_target, 0); + log_set_use_color(stderr_target, 0); + log_set_print_timestamp(stderr_target, 1); + log_set_print_category(stderr_target, 1); + if(cat_mask) { + log_parse_category_mask(stderr_target, cat_mask); + } + return 0; +} + +char *getL1ctlPrimName(uint8_t type) +{ + return l1ctlPrimNames[type]; +} diff --git a/src/host/virt_phy/src/logging.h b/src/host/virt_phy/src/logging.h new file mode 100644 index 00000000..87f12f77 --- /dev/null +++ b/src/host/virt_phy/src/logging.h @@ -0,0 +1,11 @@ +#pragma once + +#include <osmocom/core/logging.h> + +#define DL1C 0 +#define DVIRPHY 1 + +extern const struct log_info ms_log_info; + +int ms_log_init(char *cat_mask); +char *getL1ctlPrimName(uint8_t type); diff --git a/src/host/virt_phy/src/osmo_mcast_sock.c b/src/host/virt_phy/src/osmo_mcast_sock.c new file mode 100644 index 00000000..c1777347 --- /dev/null +++ b/src/host/virt_phy/src/osmo_mcast_sock.c @@ -0,0 +1,192 @@ +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/select.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <talloc.h> +#include <unistd.h> + +#include "osmo_mcast_sock.h" + +struct mcast_server_sock *mcast_server_sock_setup(void *ctx, + char* tx_mcast_group, + int tx_mcast_port, + int loopback) +{ + struct mcast_server_sock *serv_sock = talloc_zero(ctx, + struct mcast_server_sock); + + serv_sock->osmo_fd = talloc_zero(ctx, struct osmo_fd); + serv_sock->sock_conf = talloc_zero(ctx, struct sockaddr_in); + + // setup mcast server socket + serv_sock->osmo_fd->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (serv_sock->osmo_fd->fd == -1) { + perror("Failed to create Multicast Server Socket"); + return NULL; + } + + serv_sock->sock_conf->sin_family = AF_INET; + serv_sock->sock_conf->sin_addr.s_addr = inet_addr(tx_mcast_group); + serv_sock->sock_conf->sin_port = htons(tx_mcast_port); + + // determines whether sent mcast packets should be looped back to the local sockets. + // loopback must be enabled if the mcast client is on the same machine + if (setsockopt(serv_sock->osmo_fd->fd, IPPROTO_IP, + IP_MULTICAST_LOOP, &loopback, sizeof(loopback)) < 0) { + perror("Failed to disable loopback.\n"); + return NULL; + } + + return serv_sock; +} + +struct mcast_client_sock *mcast_client_sock_setup( + void *ctx, char* mcast_group, int mcast_port, + int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what), + void *osmo_fd_data) +{ + struct mcast_client_sock *client_sock = talloc_zero(ctx, + struct mcast_client_sock); + struct sockaddr_in *rx_sock_conf = talloc_zero(NULL, + struct sockaddr_in); + int rc, reuseaddr = 1, loopback = 1; + + client_sock->osmo_fd = talloc_zero(ctx, struct osmo_fd); + client_sock->mcast_group = talloc_zero(ctx, struct ip_mreq); + + // Create mcast client socket + client_sock->osmo_fd->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (client_sock->osmo_fd->fd == -1) { + perror("Could not create mcast client socket"); + return NULL; + } + + // Enable SO_REUSEADDR to allow multiple instances of this application to receive copies of the multicast datagrams. + rc = setsockopt(client_sock->osmo_fd->fd, + SOL_SOCKET, + SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); + if (rc < 0) { + perror("Failed to configure REUSEADDR option"); + return NULL; + } + + // Bind to the proper port number with the IP address specified as INADDR_ANY. + rx_sock_conf->sin_family = AF_INET; + rx_sock_conf->sin_addr.s_addr = htonl(INADDR_ANY); + rx_sock_conf->sin_port = htons(mcast_port); + rc = bind(client_sock->osmo_fd->fd, (struct sockaddr *)rx_sock_conf, + sizeof(*rx_sock_conf)); + talloc_free(rx_sock_conf); + if (rc < 0) { + perror("Could not bind mcast client socket"); + return NULL; + } + + // Enable loopback of msgs to the host. + // Loopback must be enabled for the client, so multiple processes are able to recevie a mcast package. + rc = setsockopt(client_sock->osmo_fd->fd, + IPPROTO_IP, + IP_MULTICAST_LOOP, &loopback, sizeof(loopback)); + if (rc < 0) { + perror("Failed to enable IP_MULTICAST_LOOP"); + return NULL; + } + + // Configure and join the multicast group + client_sock->mcast_group->imr_multiaddr.s_addr = inet_addr(mcast_group); + client_sock->mcast_group->imr_interface.s_addr = htonl(INADDR_ANY); + rc = setsockopt(client_sock->osmo_fd->fd, + IPPROTO_IP, + IP_ADD_MEMBERSHIP, client_sock->mcast_group, + sizeof(*client_sock->mcast_group)); + if (rc < 0) { + perror("Failed to join to mcast goup"); + return NULL; + } + + // configure and register the osmocom filedescriptor + client_sock->osmo_fd->cb = fd_rx_cb; + client_sock->osmo_fd->when = BSC_FD_READ; + client_sock->osmo_fd->data = osmo_fd_data; + + osmo_fd_register(client_sock->osmo_fd); + + return client_sock; +} + +struct mcast_bidir_sock *mcast_bidir_sock_setup( + void *ctx, char* tx_mcast_group, int tx_mcast_port, + char* rx_mcast_group, int rx_mcast_port, int loopback, + int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what), + void *osmo_fd_data) +{ + struct mcast_bidir_sock *bidir_sock = talloc(ctx, + struct mcast_bidir_sock); + bidir_sock->rx_sock = mcast_client_sock_setup(ctx, rx_mcast_group, + rx_mcast_port, fd_rx_cb, osmo_fd_data); + bidir_sock->tx_sock = mcast_server_sock_setup(ctx, tx_mcast_group, + tx_mcast_port, loopback); + if (!bidir_sock->rx_sock || !bidir_sock->tx_sock) { + return NULL; + } + return bidir_sock; + +} + +int mcast_client_sock_rx(struct mcast_client_sock *client_sock, void* buf, + int buf_len) +{ + return recv(client_sock->osmo_fd->fd, buf, buf_len, 0); +} + +int mcast_server_sock_tx(struct mcast_server_sock *serv_sock, void* data, + int data_len) +{ + return sendto(serv_sock->osmo_fd->fd, data, data_len, 0, + (struct sockaddr *)serv_sock->sock_conf, + sizeof(*serv_sock->sock_conf)); +} + +int mcast_bidir_sock_tx(struct mcast_bidir_sock *bidir_sock, void* data, + int data_len) +{ + return mcast_server_sock_tx(bidir_sock->tx_sock, data, data_len); +} +int mcast_bidir_sock_rx(struct mcast_bidir_sock *bidir_sock, void* buf, + int buf_len) +{ + return mcast_client_sock_rx(bidir_sock->rx_sock, buf, buf_len); +} + +void mcast_client_sock_close(struct mcast_client_sock *client_sock) +{ + setsockopt(client_sock->osmo_fd->fd, + IPPROTO_IP, + IP_DROP_MEMBERSHIP, client_sock->mcast_group, + sizeof(*client_sock->mcast_group)); + osmo_fd_unregister(client_sock->osmo_fd); + client_sock->osmo_fd->fd = -1; + client_sock->osmo_fd->when = 0; + close(client_sock->osmo_fd->fd); + talloc_free(client_sock->mcast_group); + talloc_free(client_sock->osmo_fd); + talloc_free(client_sock); + +} +void mcast_server_sock_close(struct mcast_server_sock *serv_sock) +{ + close(serv_sock->osmo_fd->fd); + talloc_free(serv_sock->sock_conf); + talloc_free(serv_sock); +} + +void mcast_bidir_sock_close(struct mcast_bidir_sock *bidir_sock) +{ + mcast_client_sock_close(bidir_sock->rx_sock); + mcast_server_sock_close(bidir_sock->tx_sock); + talloc_free(bidir_sock); +} diff --git a/src/host/virt_phy/src/osmo_mcast_sock.h b/src/host/virt_phy/src/osmo_mcast_sock.h new file mode 100644 index 00000000..f318ffea --- /dev/null +++ b/src/host/virt_phy/src/osmo_mcast_sock.h @@ -0,0 +1,46 @@ +#pragma once + +#include <netinet/in.h> +#include <osmocom/core/select.h> + +struct mcast_server_sock { + struct osmo_fd *osmo_fd; + struct sockaddr_in *sock_conf; +}; + +struct mcast_client_sock { + struct osmo_fd *osmo_fd; + struct ip_mreq *mcast_group; +}; + +struct mcast_bidir_sock { + struct mcast_server_sock *tx_sock; + struct mcast_client_sock *rx_sock; +}; + +struct mcast_bidir_sock *mcast_bidir_sock_setup( + void *ctx, char* tx_mcast_group, int tx_mcast_port, + char* rx_mcast_group, int rx_mcast_port, int loopback, + int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what), + void *osmo_fd_data); + +struct mcast_server_sock *mcast_server_sock_setup(void *ctx, + char* tx_mcast_group, + int tx_mcast_port, + int loopback); +struct mcast_client_sock *mcast_client_sock_setup( + void *ctx, char* mcast_group, int mcast_port, + int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what), + void *osmo_fd_data); +int mcast_client_sock_rx(struct mcast_client_sock *client_sock, void* buf, + int buf_len); +int mcast_server_sock_tx(struct mcast_server_sock *serv_sock, void* data, + int data_len); +int mcast_bidir_sock_tx(struct mcast_bidir_sock *bidir_sock, void* data, + int data_len); +int mcast_bidir_sock_rx(struct mcast_bidir_sock *bidir_sock, void* buf, + int buf_len); +void mcast_client_sock_close(struct mcast_client_sock* client_sock); +void mcast_server_sock_close(struct mcast_server_sock* server_sock); +void mcast_bidir_sock_close(struct mcast_bidir_sock* bidir_sock); + diff --git a/src/host/virt_phy/src/virt_l1_model.c b/src/host/virt_phy/src/virt_l1_model.c new file mode 100644 index 00000000..f2e16905 --- /dev/null +++ b/src/host/virt_phy/src/virt_l1_model.c @@ -0,0 +1,16 @@ +#include "virt_l1_model.h" + +struct l1_model_ms* l1_model_ms_init(void *ctx) { + + struct l1_model_ms *model = talloc_zero(ctx, struct l1_model_ms); + model->state = talloc_zero(ctx, struct l1_state_ms); + + return model; +} + +void l1_model_ms_destroy(struct l1_model_ms *model) { + virt_um_destroy(model->vui); + l1ctl_sock_destroy(model->lsi); + talloc_free(model->state); + talloc_free(model); +} diff --git a/src/host/virt_phy/src/virt_l1_model.h b/src/host/virt_phy/src/virt_l1_model.h new file mode 100644 index 00000000..55a1e3ae --- /dev/null +++ b/src/host/virt_phy/src/virt_l1_model.h @@ -0,0 +1,31 @@ +#pragma once + +#include <layer1/sync.h> +#include "l1ctl_sock.h" +#include "virtual_um.h" + +struct l1_model_ms { + struct l1ctl_sock_inst *lsi; + struct virt_um_inst *vui; + struct l1_state_ms *state; +}; + +//TODO: must contain logical channel information (fram number, ciphering mode, ...) +struct l1_state_ms { + + /* the cell on which we are camping right now */ + struct l1_cell_info serving_cell; + + /* neighbor cell sync info */ + struct l1_cell_info neigh_cell[L1S_NUM_NEIGH_CELL]; + + /* TCH */ + uint8_t tch_mode; + uint8_t tch_sync; + uint8_t audio_mode; +}; + +struct l1_model_ms *l1_model_ms_init(void *ctx); + +void l1_model_ms_destroy(struct l1_model_ms *model); + diff --git a/src/host/virt_phy/src/virtphy.c b/src/host/virt_phy/src/virtphy.c new file mode 100644 index 00000000..94f6fa50 --- /dev/null +++ b/src/host/virt_phy/src/virtphy.c @@ -0,0 +1,50 @@ +/* osmocom includes */ + +#include "logging.h" +#include <osmocom/core/msgb.h> +#include <osmocom/core/select.h> +#include <osmo-bts/scheduler.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <virt_l1_model.h> + +#include "virtual_um.h" +#include "l1ctl_sock.h" +#include "virt_l1_model.h" +#include "gsmtapl1_if.h" +#include "l1ctl_sap.h" + +int main(void) +{ + + // init loginfo + static struct l1_model_ms *model; + ms_log_init("DL1C,1:DVIRPHY,1"); + //ms_log_init("DL1C,8:DVIRPHY,8"); + + LOGP(DVIRPHY, LOGL_INFO, "Virtual physical layer starting up...\n"); + + model = l1_model_ms_init(NULL); + + // TODO: make this configurable + model->vui = virt_um_init(NULL, DEFAULT_BTS_MCAST_GROUP, DEFAULT_BTS_MCAST_PORT, DEFAULT_MS_MCAST_GROUP, DEFAULT_MS_MCAST_PORT, gsmtapl1_rx_from_virt_um_inst_cb); + model->lsi = l1ctl_sock_init(NULL, l1ctl_sap_rx_from_l23_inst_cb, NULL); + + gsmtapl1_init(model); + l1ctl_sap_init(model); + + LOGP(DVIRPHY, LOGL_INFO, "Virtual physical layer ready...\n"); + + while (1) { + // handle osmocom fd READ events (l1ctl-unix-socket, virtual-um-mcast-socket) + osmo_select_main(0); + // handle outgoing l1ctl primitives to l2 + // TODO implement scheduler for uplink messages + } + + l1_model_ms_destroy(model); + + // not reached + return EXIT_FAILURE; +} diff --git a/src/host/virt_phy/src/virtual_um.c b/src/host/virt_phy/src/virtual_um.c new file mode 100644 index 00000000..2b155095 --- /dev/null +++ b/src/host/virt_phy/src/virtual_um.c @@ -0,0 +1,100 @@ +/* Routines for a Virtual Um interface over GSMTAP/UDP */ + +/* (C) 2015 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 <unistd.h> +#include <osmocom/core/select.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/gsmtap.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> + +#include "virtual_um.h" +#include "osmo_mcast_sock.h" + +/** + * Virtual UM interface file descriptor callback. + * Should be called by select.c when the fd is ready for reading. + */ +static int virt_um_fd_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct virt_um_inst *vui = ofd->data; + + // check if the read flag is set + if (what & BSC_FD_READ) { + // allocate message buffer of specified size + struct msgb *msg = msgb_alloc(VIRT_UM_MSGB_SIZE, + "Virtual UM Rx"); + int rc; + + // read message from fd in message buffer + rc = mcast_bidir_sock_rx(vui->mcast_sock, msgb_data(msg), msgb_tailroom(msg)); + // rc is number of bytes actually read + if (rc > 0) { + msgb_put(msg, rc); + // call the l1 callback function for a received msg + vui->recv_cb(vui, msg); + } else { + // TODO: this kind of error handling might be a bit harsh + vui->recv_cb(vui, NULL); + // Unregister fd from select loop + osmo_fd_unregister(ofd); + close(ofd->fd); + ofd->fd = -1; + ofd->when = 0; + } + } + + return 0; +} + +struct virt_um_inst *virt_um_init( + void *ctx, const char *tx_mcast_group, uint16_t tx_mcast_port, + const char *rx_mcast_group, uint16_t rx_mcast_port, void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg)) +{ + + struct virt_um_inst *vui = talloc_zero(ctx, struct virt_um_inst); + vui->mcast_sock = mcast_bidir_sock_setup(ctx, tx_mcast_group, tx_mcast_port, + rx_mcast_group, rx_mcast_port, 1, virt_um_fd_cb, vui); + vui->recv_cb = recv_cb; + + return vui; + +} + +void virt_um_destroy(struct virt_um_inst *vui) +{ + mcast_bidir_sock_close(vui->mcast_sock); + talloc_free(vui); +} + +/** + * Write msg to to multicast socket and free msg afterwards + */ +int virt_um_write_msg(struct virt_um_inst *vui, struct msgb *msg) +{ + int rc; + + rc = mcast_bidir_sock_tx(vui->mcast_sock, msgb_data(msg), msgb_length(msg)); + msgb_free(msg); + + return rc; +} diff --git a/src/host/virt_phy/src/virtual_um.h b/src/host/virt_phy/src/virtual_um.h new file mode 100644 index 00000000..eafb9940 --- /dev/null +++ b/src/host/virt_phy/src/virtual_um.h @@ -0,0 +1,24 @@ +#pragma once + +#include <osmocom/core/select.h> +#include <osmocom/core/msgb.h> +#include "osmo_mcast_sock.h" + +#define VIRT_UM_MSGB_SIZE 256 +#define DEFAULT_MS_MCAST_GROUP "224.0.0.1" +#define DEFAULT_MS_MCAST_PORT 6666 +#define DEFAULT_BTS_MCAST_GROUP "225.0.0.1" +#define DEFAULT_BTS_MCAST_PORT 6667 + +struct virt_um_inst { + void *priv; + struct mcast_bidir_sock *mcast_sock; + void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg); +}; + +struct virt_um_inst *virt_um_init(void *ctx, const char *tx_mcast_group, uint16_t tx_mcast_port, const char *rx_mcast_group, uint16_t rx_mcast_port, + void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg)); + +void virt_um_destroy(struct virt_um_inst *vui); + +int virt_um_write_msg(struct virt_um_inst *vui, struct msgb *msg); |