From 3e023128dc23d842ceab838ed63f9864a72a4b17 Mon Sep 17 00:00:00 2001 From: Vadim Yanitskiy Date: Mon, 18 Jul 2022 05:03:45 +0700 Subject: [PATCH] trxcon: rework trxcon_fsm, move into a separate file The original trxcon_fsm I wrote back in 2017 [1] was more like a boolean flag, as there were only two states: IDLE and MANAGED. Not surprising, given that until recently handling of multiple L1CTL connections was not supported. Now that we have this implemented, lifetime of a trxcon_fsm instance is limited by lifetime of a L1CTL connection, what renders the FSM useless. This change removes the old 'boolean' trxcon_fsm and introduces the new one, which will allows us to abstract the L1CTL interface from the TRXC/TRXD interfaces, as well as the scheduler. The new FSM will also simplify development of the RLC/MAC layer for GPRS. Change-Id: Ifaf63ead9dd180181358e771367b2a686ba159ca Related: [1] I7ee6fc891abe5f775f5b7ebbf093181a97950dea Related: OS#5599 --- .../include/osmocom/bb/l1sched/l1sched.h | 2 + .../trxcon/include/osmocom/bb/trxcon/l1ctl.h | 4 +- .../trxcon/include/osmocom/bb/trxcon/trxcon.h | 128 ++++- src/host/trxcon/src/Makefile.am | 1 + src/host/trxcon/src/l1ctl.c | 458 ++++++----------- src/host/trxcon/src/trx_if.c | 20 +- src/host/trxcon/src/trxcon.c | 104 +--- src/host/trxcon/src/trxcon_fsm.c | 481 ++++++++++++++++++ 8 files changed, 787 insertions(+), 411 deletions(-) create mode 100644 src/host/trxcon/src/trxcon_fsm.c diff --git a/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h b/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h index 2c48eae41..3f7dae0ce 100644 --- a/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h +++ b/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h @@ -4,6 +4,8 @@ #include #include +#include + #include #include #include diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/l1ctl.h b/src/host/trxcon/include/osmocom/bb/trxcon/l1ctl.h index e15d5a20d..9518bb978 100644 --- a/src/host/trxcon/include/osmocom/bb/trxcon/l1ctl.h +++ b/src/host/trxcon/include/osmocom/bb/trxcon/l1ctl.h @@ -9,8 +9,8 @@ /* Event handlers */ int l1ctl_rx_cb(struct l1ctl_client *l1c, struct msgb *msg); -int l1ctl_tx_fbsb_conf(struct l1ctl_client *l1c, uint8_t result, - const struct l1ctl_info_dl *dl_info, uint8_t bsic); +int l1ctl_tx_fbsb_conf(struct l1ctl_client *l1c, uint16_t band_arfcn, uint8_t bsic); +int l1ctl_tx_fbsb_fail(struct l1ctl_client *l1c, uint16_t band_arfcn); int l1ctl_tx_ccch_mode_conf(struct l1ctl_client *l1c, uint8_t mode); int l1ctl_tx_pm_conf(struct l1ctl_client *l1c, uint16_t band_arfcn, int dbm, int last); diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h b/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h index da47f8b39..b982193b7 100644 --- a/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h +++ b/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h @@ -4,19 +4,127 @@ struct l1sched_state; struct trx_instance; struct l1ctl_client; +extern struct osmo_fsm trxcon_fsm_def; + enum trxcon_fsm_states { - TRXCON_STATE_IDLE = 0, - TRXCON_STATE_MANAGED, + TRXCON_ST_RESET, + TRXCON_ST_FULL_POWER_SCAN, + TRXCON_ST_FBSB_SEARCH, + TRXCON_ST_BCCH_CCCH, + TRXCON_ST_DEDICATED, }; enum trxcon_fsm_events { - /* L1CTL specific events */ - L1CTL_EVENT_CONNECT, - L1CTL_EVENT_DISCONNECT, + TRXCON_EV_L1IF_FAILURE, + TRXCON_EV_L2IF_FAILURE, + TRXCON_EV_RESET_FULL_REQ, + TRXCON_EV_RESET_SCHED_REQ, + TRXCON_EV_FULL_POWER_SCAN_REQ, + TRXCON_EV_FULL_POWER_SCAN_RES, + TRXCON_EV_FBSB_SEARCH_REQ, + TRXCON_EV_FBSB_SEARCH_RES, + TRXCON_EV_SET_CCCH_MODE_REQ, + TRXCON_EV_SET_TCH_MODE_REQ, + TRXCON_EV_SET_CONFIG_REQ, + TRXCON_EV_TX_ACCESS_BURST_REQ, + TRXCON_EV_DEDICATED_ESTABLISH_REQ, + TRXCON_EV_DEDICATED_RELEASE_REQ, + TRXCON_EV_TX_TRAFFIC_REQ, + TRXCON_EV_RX_TRAFFIC_IND, + TRXCON_EV_TX_DATA_REQ, + TRXCON_EV_RX_DATA_IND, + TRXCON_EV_CRYPTO_REQ, +}; - /* TRX specific events */ - TRX_EVENT_RSP_ERROR, - TRX_EVENT_OFFLINE, +/* param of TRXCON_EV_FULL_POWER_SCAN_REQ */ +struct trxcon_param_full_power_scan_req { + uint16_t band_arfcn_start; + uint16_t band_arfcn_stop; +}; + +/* param of TRXCON_EV_FULL_POWER_SCAN_RES */ +struct trxcon_param_full_power_scan_res { + bool last_result; + uint16_t band_arfcn; + int dbm; +}; + +/* param of TRXCON_EV_FBSB_SEARCH_REQ */ +struct trxcon_param_fbsb_search_req { + uint16_t band_arfcn; + uint16_t timeout_ms; + uint8_t pchan_config; +}; + +/* param of TRXCON_EV_SET_{CCCH,TCH}_MODE_REQ */ +struct trxcon_param_set_ccch_tch_mode_req { + uint8_t mode; + bool applied; +}; + +/* param of TRXCON_EV_SET_CONFIG_REQ */ +struct trxcon_param_set_config_req { + uint8_t timing_advance; + uint8_t tx_power; +}; + +/* param of TRXCON_EV_TX_{TRAFFIC,DATA}_REQ */ +struct trxcon_param_tx_traffic_data_req { + uint8_t chan_nr; + uint8_t link_id; + size_t data_len; + const uint8_t *data; +}; + +/* param of TRXCON_EV_RX_{TRAFFIC,DATA}_IND */ +struct trxcon_param_rx_traffic_data_ind { + uint8_t chan_nr; + uint8_t link_id; + uint32_t frame_nr; + int16_t toa256; + int8_t rssi; + int n_errors; + int n_bits_total; + size_t data_len; + const uint8_t *data; +}; + +/* param of TRXCON_EV_TX_ACCESS_BURST_REQ */ +struct trxcon_param_tx_access_burst_req { + uint8_t chan_nr; + uint8_t link_id; + uint8_t offset; + uint8_t synch_seq; + uint16_t ra; + bool is_11bit; +}; + +/* param of TRXCON_EV_DEDICATED_ESTABLISH_REQ */ +struct trxcon_param_dedicated_establish_req { + uint8_t chan_nr; + uint8_t tch_mode; + uint8_t tsc; + + bool hopping; + union { + struct { /* hopping=false */ + uint16_t band_arfcn; + } h0; + struct { /* hopping=true */ + uint8_t hsn; + uint8_t maio; + uint8_t n; + uint16_t ma[64]; + } h1; + }; +}; + +/* param of TRXCON_EV_CRYPTO_REQ */ +struct trxcon_param_crypto_req { + uint8_t chan_nr; + uint8_t a5_algo; /* 0 is A5/0 */ + uint8_t key_len; + const uint8_t *key; }; struct trxcon_inst { @@ -31,10 +139,6 @@ struct trxcon_inst { /* L1/L2 interfaces */ struct trx_instance *trx; struct l1ctl_client *l1c; - - /* TODO: implement this as an FSM state with timeout */ - struct osmo_timer_list fbsb_timer; - bool fbsb_conf_sent; }; struct trxcon_inst *trxcon_inst_alloc(void *ctx, unsigned int id); diff --git a/src/host/trxcon/src/Makefile.am b/src/host/trxcon/src/Makefile.am index cf1357fd8..3af004d61 100644 --- a/src/host/trxcon/src/Makefile.am +++ b/src/host/trxcon/src/Makefile.am @@ -42,6 +42,7 @@ trxcon_SOURCES = \ l1ctl.c \ trx_if.c \ logging.c \ + trxcon_fsm.c \ trxcon.c \ $(NULL) diff --git a/src/host/trxcon/src/l1ctl.c b/src/host/trxcon/src/l1ctl.c index 5e40af58d..96dde7305 100644 --- a/src/host/trxcon/src/l1ctl.c +++ b/src/host/trxcon/src/l1ctl.c @@ -30,18 +30,18 @@ #include +#include #include #include #include + +#include #include #include #include #include #include - -#include -#include #include static const char *arfcn2band_name(uint16_t arfcn) @@ -166,33 +166,52 @@ static struct l1ctl_fbsb_conf *fbsb_conf_make(struct msgb *msg, uint8_t result, return conf; } -int l1ctl_tx_fbsb_conf(struct l1ctl_client *l1c, uint8_t result, - const struct l1ctl_info_dl *dl_info, uint8_t bsic) +int l1ctl_tx_fbsb_fail(struct l1ctl_client *l1c, uint16_t band_arfcn) { struct trxcon_inst *trxcon = l1c->priv; - struct l1ctl_fbsb_conf *conf; + struct l1ctl_info_dl *dl; struct msgb *msg; msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF); if (msg == NULL) return -ENOMEM; - put_dl_info_hdr(msg, dl_info); + dl = put_dl_info_hdr(msg, NULL); - conf = fbsb_conf_make(msg, result, bsic); + /* Fill in current ARFCN */ + dl->band_arfcn = htons(band_arfcn); + + fbsb_conf_make(msg, 255, 0); + + LOGPFSMSL(trxcon->fi, DL1C, LOGL_DEBUG, "Send FBSB Conf (timeout)\n"); + + return l1ctl_client_send(l1c, msg); +} + +int l1ctl_tx_fbsb_conf(struct l1ctl_client *l1c, uint16_t band_arfcn, uint8_t bsic) +{ + struct trxcon_inst *trxcon = l1c->priv; + struct l1ctl_fbsb_conf *conf; + struct l1ctl_info_dl *dl; + struct msgb *msg; + + msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF); + if (msg == NULL) + return -ENOMEM; + + dl = put_dl_info_hdr(msg, NULL); + + /* Fill in current ARFCN */ + dl->band_arfcn = htons(band_arfcn); + + conf = fbsb_conf_make(msg, 0, bsic); /* FIXME: set proper value */ conf->initial_freq_err = 0; - /* Ask SCH handler not to send L1CTL_FBSB_CONF anymore */ - trxcon->fbsb_conf_sent = true; - - /* Abort FBSB expire timer */ - osmo_timer_del(&trxcon->fbsb_timer); - LOGPFSMSL(trxcon->fi, DL1C, LOGL_DEBUG, "Send FBSB Conf (result=%u, bsic=%u)\n", - result, bsic); + conf->result, conf->bsic); return l1ctl_client_send(l1c, msg); } @@ -299,44 +318,10 @@ static enum gsm_phys_chan_config l1ctl_ccch_mode2pchan_config(enum ccch_mode mod } } -/* FBSB expire timer */ -static void fbsb_timer_cb(void *data) -{ - struct l1ctl_client *l1c = (struct l1ctl_client *) data; - struct trxcon_inst *trxcon = l1c->priv; - struct l1ctl_info_dl *dl; - struct msgb *msg; - - msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF); - if (msg == NULL) - return; - - LOGPFSMSL(trxcon->fi, DL1C, LOGL_NOTICE, - "FBSB timer fired for ARFCN %u\n", - trxcon->trx->band_arfcn & ~ARFCN_FLAG_MASK); - - dl = put_dl_info_hdr(msg, NULL); - - /* Fill in current ARFCN */ - dl->band_arfcn = htons(trxcon->trx->band_arfcn); - - fbsb_conf_make(msg, 255, 0); - - /* Ask SCH handler not to send L1CTL_FBSB_CONF anymore */ - trxcon->fbsb_conf_sent = true; - - LOGPFSMSL(trxcon->fi, DL1C, LOGL_DEBUG, "Send FBSB Conf (timeout)\n"); - - l1ctl_client_send(l1c, msg); -} - static int l1ctl_rx_fbsb_req(struct l1ctl_client *l1c, struct msgb *msg) { struct trxcon_inst *trxcon = l1c->priv; - enum gsm_phys_chan_config ch_config; struct l1ctl_fbsb_req *fbsb; - uint16_t band_arfcn; - uint16_t timeout; int rc = 0; fbsb = (struct l1ctl_fbsb_req *) msg->l1h; @@ -348,44 +333,19 @@ static int l1ctl_rx_fbsb_req(struct l1ctl_client *l1c, struct msgb *msg) goto exit; } - ch_config = l1ctl_ccch_mode2pchan_config(fbsb->ccch_mode); - band_arfcn = ntohs(fbsb->band_arfcn); - timeout = ntohs(fbsb->timeout); + struct trxcon_param_fbsb_search_req req = { + .pchan_config = l1ctl_ccch_mode2pchan_config(fbsb->ccch_mode), + .timeout_ms = ntohs(fbsb->timeout) * GSM_TDMA_FN_DURATION_uS / 1000, + .band_arfcn = ntohs(fbsb->band_arfcn), + }; - LOGPFSMSL(trxcon->fi, DL1C, LOGL_NOTICE, "Received FBSB request (%s %d)\n", - arfcn2band_name(band_arfcn), band_arfcn & ~ARFCN_FLAG_MASK); + LOGPFSMSL(trxcon->fi, DL1C, LOGL_NOTICE, + "Received FBSB request (%s %d, timeout %u ms)\n", + arfcn2band_name(req.band_arfcn), + req.band_arfcn & ~ARFCN_FLAG_MASK, + req.timeout_ms); - /* Reset scheduler and clock counter */ - l1sched_reset(trxcon->sched, true); - - /* Configure a single timeslot */ - l1sched_configure_ts(trxcon->sched, 0, ch_config); - - /* Ask SCH handler to send L1CTL_FBSB_CONF */ - trxcon->fbsb_conf_sent = false; - - /* Only if current ARFCN differs */ - if (trxcon->trx->band_arfcn != band_arfcn) { - /* Update current ARFCN */ - trxcon->trx->band_arfcn = band_arfcn; - - /* Tune transceiver to required ARFCN */ - trx_if_cmd_rxtune(trxcon->trx, band_arfcn); - trx_if_cmd_txtune(trxcon->trx, band_arfcn); - } - - /* Transceiver might have been powered on before, e.g. - * in case of sending L1CTL_FBSB_REQ due to signal loss. */ - if (!trxcon->trx->powered_up) - trx_if_cmd_poweron(trxcon->trx); - - /* Start FBSB expire timer */ - trxcon->fbsb_timer.data = l1c; - trxcon->fbsb_timer.cb = fbsb_timer_cb; - LOGPFSMSL(trxcon->fi, DL1C, LOGL_INFO, "Starting FBSB timer %u ms\n", - timeout * GSM_TDMA_FN_DURATION_uS / 1000); - osmo_timer_schedule(&trxcon->fbsb_timer, 0, - timeout * GSM_TDMA_FN_DURATION_uS); + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_FBSB_SEARCH_REQ, &req); exit: msgb_free(msg); @@ -394,7 +354,6 @@ exit: static int l1ctl_rx_pm_req(struct l1ctl_client *l1c, struct msgb *msg) { - uint16_t band_arfcn_start, band_arfcn_stop; struct trxcon_inst *trxcon = l1c->priv; struct l1ctl_pm_req *pmr; int rc = 0; @@ -408,17 +367,18 @@ static int l1ctl_rx_pm_req(struct l1ctl_client *l1c, struct msgb *msg) goto exit; } - band_arfcn_start = ntohs(pmr->range.band_arfcn_from); - band_arfcn_stop = ntohs(pmr->range.band_arfcn_to); + struct trxcon_param_full_power_scan_req req = { + .band_arfcn_start = ntohs(pmr->range.band_arfcn_from), + .band_arfcn_stop = ntohs(pmr->range.band_arfcn_to), + }; LOGPFSMSL(trxcon->fi, DL1C, LOGL_NOTICE, "Received power measurement request (%s: %d -> %d)\n", - arfcn2band_name(band_arfcn_start), - band_arfcn_start & ~ARFCN_FLAG_MASK, - band_arfcn_stop & ~ARFCN_FLAG_MASK); + arfcn2band_name(req.band_arfcn_start), + req.band_arfcn_start & ~ARFCN_FLAG_MASK, + req.band_arfcn_stop & ~ARFCN_FLAG_MASK); - /* Send measurement request to transceiver */ - rc = trx_if_cmd_measure(trxcon->trx, band_arfcn_start, band_arfcn_stop); + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_FULL_POWER_SCAN_REQ, &req); exit: msgb_free(msg); @@ -445,13 +405,10 @@ static int l1ctl_rx_reset_req(struct l1ctl_client *l1c, struct msgb *msg) switch (res->type) { case L1CTL_RES_T_FULL: - /* TODO: implement trx_if_reset() */ - trx_if_cmd_poweroff(trxcon->trx); - trx_if_cmd_echo(trxcon->trx); - - /* Fall through */ + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_RESET_FULL_REQ, NULL); + break; case L1CTL_RES_T_SCHED: - l1sched_reset(trxcon->sched, true); + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_RESET_SCHED_REQ, NULL); break; default: LOGPFSMSL(trxcon->fi, DL1C, LOGL_ERROR, @@ -486,13 +443,11 @@ static int l1ctl_rx_echo_req(struct l1ctl_client *l1c, struct msgb *msg) static int l1ctl_rx_ccch_mode_req(struct l1ctl_client *l1c, struct msgb *msg) { struct trxcon_inst *trxcon = l1c->priv; - enum gsm_phys_chan_config ch_config; - struct l1ctl_ccch_mode_req *req; - struct l1sched_ts *ts; - int rc = 0; + struct l1ctl_ccch_mode_req *mode_req; + int rc; - req = (struct l1ctl_ccch_mode_req *) msg->l1h; - if (msgb_l1len(msg) < sizeof(*req)) { + mode_req = (struct l1ctl_ccch_mode_req *)msg->l1h; + if (msgb_l1len(msg) < sizeof(*mode_req)) { LOGPFSMSL(trxcon->fi, DL1C, LOGL_ERROR, "MSG too short Reset Req: %u\n", msgb_l1len(msg)); @@ -501,26 +456,16 @@ static int l1ctl_rx_ccch_mode_req(struct l1ctl_client *l1c, struct msgb *msg) } LOGPFSMSL(trxcon->fi, DL1C, LOGL_NOTICE, "Received CCCH mode request (%u)\n", - req->ccch_mode); /* TODO: add value-string for ccch_mode */ + mode_req->ccch_mode); /* TODO: add value-string for ccch_mode */ - /* Make sure that TS0 is allocated and configured */ - ts = trxcon->sched->ts[0]; - if (ts == NULL || ts->mf_layout == NULL) { - LOGPFSMSL(trxcon->fi, DL1C, LOGL_ERROR, "TS0 is not configured"); - rc = -EINVAL; - goto exit; - } + struct trxcon_param_set_ccch_tch_mode_req req = { + /* Choose corresponding channel combination */ + .mode = l1ctl_ccch_mode2pchan_config(mode_req->ccch_mode), + }; - /* Choose corresponding channel combination */ - ch_config = l1ctl_ccch_mode2pchan_config(req->ccch_mode); - - /* Do nothing if the current mode matches required */ - if (ts->mf_layout->chan_config != ch_config) - rc = l1sched_configure_ts(trxcon->sched, 0, ch_config); - - /* Confirm reconfiguration */ - if (!rc) - rc = l1ctl_tx_ccch_mode_conf(l1c, req->ccch_mode); + rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_SET_CCCH_MODE_REQ, &req); + if (rc == 0 && req.applied) + l1ctl_tx_ccch_mode_conf(l1c, mode_req->ccch_mode); exit: msgb_free(msg); @@ -529,40 +474,38 @@ exit: static int l1ctl_rx_rach_req(struct l1ctl_client *l1c, struct msgb *msg, bool ext) { + struct trxcon_param_tx_access_burst_req req; struct trxcon_inst *trxcon = l1c->priv; struct l1ctl_info_ul *ul; - struct l1sched_ts_prim *prim; - struct l1sched_ts_prim_rach rach; - enum l1sched_ts_prim_type prim_type; - int rc; ul = (struct l1ctl_info_ul *) msg->l1h; /* Is it extended (11-bit) RACH or not? */ if (ext) { - const struct l1ctl_ext_rach_req *req = (void *)ul->payload; + const struct l1ctl_ext_rach_req *rr = (void *)ul->payload; - rach = (struct l1sched_ts_prim_rach) { - .ra = ntohs(req->ra11), - .synch_seq = req->synch_seq, - .offset = ntohs(req->offset), + req = (struct trxcon_param_tx_access_burst_req) { + .offset = ntohs(rr->offset), + .synch_seq = rr->synch_seq, + .ra = ntohs(rr->ra11), + .is_11bit = true, }; LOGPFSMSL(trxcon->fi, DL1C, LOGL_NOTICE, "Received extended (11-bit) RACH request " "(offset=%u, synch_seq=%u, ra11=0x%02hx)\n", - rach.offset, rach.synch_seq, rach.ra); + req.offset, req.synch_seq, req.ra); } else { - const struct l1ctl_rach_req *req = (void *)ul->payload; + const struct l1ctl_rach_req *rr = (void *)ul->payload; - rach = (struct l1sched_ts_prim_rach) { - .ra = req->ra, - .offset = ntohs(req->offset), + req = (struct trxcon_param_tx_access_burst_req) { + .offset = ntohs(rr->offset), + .ra = rr->ra, }; LOGPFSMSL(trxcon->fi, DL1C, LOGL_NOTICE, "Received regular (8-bit) RACH request " - "(offset=%u, ra=0x%02x)\n", rach.offset, rach.ra); + "(offset=%u, ra=0x%02x)\n", req.offset, req.ra); } /* The controlling L1CTL side always does include the UL info header, @@ -570,55 +513,38 @@ static int l1ctl_rx_rach_req(struct l1ctl_client *l1c, struct msgb *msg, bool ex if (ul->chan_nr == 0x00) { LOGPFSMSL(trxcon->fi, DL1C, LOGL_NOTICE, "The UL info header is empty, assuming RACH is on TS0\n"); - ul->chan_nr = RSL_CHAN_RACH; + req.chan_nr = RSL_CHAN_RACH; + req.link_id = 0x00; + } else { + req.chan_nr = ul->chan_nr; + req.link_id = ul->link_id; } - /** - * Push this primitive to the transmit queue. - * Indicated timeslot needs to be configured. - */ - prim_type = ext ? L1SCHED_PRIM_RACH11 : L1SCHED_PRIM_RACH8; - prim = l1sched_prim_push(trxcon->sched, prim_type, ul->chan_nr, ul->link_id, - (const uint8_t *)&rach, sizeof(rach)); - if (prim == NULL) - rc = -ENOMEM; + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_TX_ACCESS_BURST_REQ, &req); msgb_free(msg); - return rc; + return 0; } -static int l1ctl_proc_est_req_h0(struct trxcon_inst *trxcon, struct l1ctl_h0 *h) +static int l1ctl_proc_est_req_h0(struct trxcon_inst *trxcon, + struct trxcon_param_dedicated_establish_req *req, + const struct l1ctl_h0 *h) { - struct trx_instance *trx = trxcon->trx; - uint16_t band_arfcn; - int rc = 0; - - band_arfcn = ntohs(h->band_arfcn); + req->h0.band_arfcn = ntohs(h->band_arfcn); LOGPFSMSL(trxcon->fi, DL1C, LOGL_NOTICE, - "L1CTL_DM_EST_REQ indicates a single ARFCN=%u\n", - band_arfcn & ~ARFCN_FLAG_MASK); - - /* Do we need to retune? */ - if (trx->band_arfcn == band_arfcn) - return 0; - - /* Tune transceiver to required ARFCN */ - rc |= trx_if_cmd_rxtune(trx, band_arfcn); - rc |= trx_if_cmd_txtune(trx, band_arfcn); - if (rc) - return rc; - - /* Update current ARFCN */ - trx->band_arfcn = band_arfcn; + "L1CTL_DM_EST_REQ indicates single ARFCN %s %u\n", + arfcn2band_name(req->h0.band_arfcn), + req->h0.band_arfcn & ~ARFCN_FLAG_MASK); return 0; } -static int l1ctl_proc_est_req_h1(struct trxcon_inst *trxcon, struct l1ctl_h1 *h) +static int l1ctl_proc_est_req_h1(struct trxcon_inst *trxcon, + struct trxcon_param_dedicated_establish_req *req, + const struct l1ctl_h1 *h) { - uint16_t ma[64]; - int i, rc; + unsigned int i; LOGPFSMSL(trxcon->fi, DL1C, LOGL_NOTICE, "L1CTL_DM_EST_REQ indicates a Frequency " @@ -630,7 +556,7 @@ static int l1ctl_proc_est_req_h1(struct trxcon_inst *trxcon, struct l1ctl_h1 *h) LOGPFSMSL(trxcon->fi, DL1C, LOGL_ERROR, "No channels in mobile allocation?!?\n"); return -EINVAL; - } else if (h->n > ARRAY_SIZE(ma)) { + } else if (h->n > ARRAY_SIZE(h->ma)) { LOGPFSMSL(trxcon->fi, DL1C, LOGL_ERROR, "More than 64 channels in mobile allocation?!?\n"); return -EINVAL; @@ -638,75 +564,45 @@ static int l1ctl_proc_est_req_h1(struct trxcon_inst *trxcon, struct l1ctl_h1 *h) /* Convert from network to host byte order */ for (i = 0; i < h->n; i++) - ma[i] = ntohs(h->ma[i]); + req->h1.ma[i] = ntohs(h->ma[i]); + req->h1.n = h->n; + req->h1.hsn = h->hsn; + req->h1.maio = h->maio; - /* Forward hopping parameters to TRX */ - rc = trx_if_cmd_setfh(trxcon->trx, h->hsn, h->maio, ma, h->n); - if (rc) - return rc; - - /** - * TODO: update the state of trx_instance somehow - * in order to indicate that it is in hopping mode... - */ return 0; } static int l1ctl_rx_dm_est_req(struct l1ctl_client *l1c, struct msgb *msg) { struct trxcon_inst *trxcon = l1c->priv; - enum gsm_phys_chan_config config; struct l1ctl_dm_est_req *est_req; struct l1ctl_info_ul *ul; - struct l1sched_ts *ts; - uint8_t chan_nr, tn; int rc; ul = (struct l1ctl_info_ul *) msg->l1h; est_req = (struct l1ctl_dm_est_req *) ul->payload; - chan_nr = ul->chan_nr; - tn = chan_nr & 0x07; + struct trxcon_param_dedicated_establish_req req = { + .chan_nr = ul->chan_nr, + .tch_mode = est_req->tch_mode, + .tsc = est_req->tsc, + .hopping = est_req->h, + }; LOGPFSMSL(trxcon->fi, DL1C, LOGL_NOTICE, "Received L1CTL_DM_EST_REQ " "(tn=%u, chan_nr=0x%02x, tsc=%u, tch_mode=0x%02x)\n", - tn, chan_nr, est_req->tsc, est_req->tch_mode); - - /* Determine channel config */ - config = l1sched_chan_nr2pchan_config(chan_nr); - if (config == GSM_PCHAN_NONE) { - LOGPFSMSL(trxcon->fi, DL1C, LOGL_ERROR, "Couldn't determine channel config\n"); - rc = -EINVAL; - goto exit; - } + req.chan_nr & 0x07, req.chan_nr, req.tsc, req.tch_mode); /* Frequency hopping? */ if (est_req->h) - rc = l1ctl_proc_est_req_h1(trxcon, &est_req->h1); + rc = l1ctl_proc_est_req_h1(trxcon, &req, &est_req->h1); else /* Single ARFCN */ - rc = l1ctl_proc_est_req_h0(trxcon, &est_req->h0); + rc = l1ctl_proc_est_req_h0(trxcon, &req, &est_req->h0); if (rc) goto exit; - /* Configure requested TS */ - rc = l1sched_configure_ts(trxcon->sched, tn, config); - ts = trxcon->sched->ts[tn]; - if (rc) { - rc = -EINVAL; - goto exit; - } - - /* Deactivate all lchans */ - l1sched_deactivate_all_lchans(ts); - - /* Activate only requested lchans */ - rc = l1sched_set_lchans(ts, chan_nr, 1, est_req->tch_mode, est_req->tsc); - if (rc) { - LOGPFSMSL(trxcon->fi, DL1C, LOGL_ERROR, "Couldn't activate requested lchans\n"); - rc = -EINVAL; - goto exit; - } + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_DEDICATED_ESTABLISH_REQ, &req); exit: msgb_free(msg); @@ -717,11 +613,9 @@ static int l1ctl_rx_dm_rel_req(struct l1ctl_client *l1c, struct msgb *msg) { struct trxcon_inst *trxcon = l1c->priv; - LOGPFSMSL(trxcon->fi, DL1C, LOGL_NOTICE, - "Received L1CTL_DM_REL_REQ, resetting scheduler\n"); + LOGPFSMSL(trxcon->fi, DL1C, LOGL_NOTICE, "Received L1CTL_DM_REL_REQ\n"); - /* Reset scheduler */ - l1sched_reset(trxcon->sched, false); + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_DEDICATED_RELEASE_REQ, NULL); msgb_free(msg); return 0; @@ -735,34 +629,29 @@ static int l1ctl_rx_dt_req(struct l1ctl_client *l1c, { struct trxcon_inst *trxcon = l1c->priv; struct l1ctl_info_ul *ul; - struct l1sched_ts_prim *prim; - uint8_t chan_nr, link_id; - size_t payload_len; - int rc; /* Extract UL frame header */ ul = (struct l1ctl_info_ul *) msg->l1h; - - /* Calculate the payload len */ msg->l2h = ul->payload; - payload_len = msgb_l2len(msg); - /* Obtain channel description */ - chan_nr = ul->chan_nr; - link_id = ul->link_id & 0x40; + struct trxcon_param_tx_traffic_data_req req = { + .chan_nr = ul->chan_nr, + .link_id = ul->link_id & 0x40, + .data_len = msgb_l2len(msg), + .data = ul->payload, + }; LOGPFSMSL(trxcon->fi, DL1D, LOGL_DEBUG, "Recv %s Req (chan_nr=0x%02x, link_id=0x%02x, len=%zu)\n", - traffic ? "TRAFFIC" : "DATA", chan_nr, link_id, payload_len); + traffic ? "TRAFFIC" : "DATA", req.chan_nr, req.link_id, req.data_len); - /* Push this primitive to transmit queue */ - prim = l1sched_prim_push(trxcon->sched, L1SCHED_PRIM_DATA, - chan_nr, link_id, ul->payload, payload_len); - if (prim == NULL) - rc = -ENOMEM; + if (traffic) + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_TX_TRAFFIC_REQ, &req); + else + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_TX_DATA_REQ, &req); msgb_free(msg); - return rc; + return 0; } static int l1ctl_rx_param_req(struct l1ctl_client *l1c, struct msgb *msg) @@ -778,13 +667,12 @@ static int l1ctl_rx_param_req(struct l1ctl_client *l1c, struct msgb *msg) "Received L1CTL_PARAM_REQ (ta=%d, tx_power=%u)\n", par_req->ta, par_req->tx_power); - /* Instruct TRX to use new TA value */ - if (trxcon->trx->ta != par_req->ta) { - trx_if_cmd_setta(trxcon->trx, par_req->ta); - trxcon->trx->ta = par_req->ta; - } + struct trxcon_param_set_config_req req = { + .timing_advance = par_req->ta, + .tx_power = par_req->tx_power, + }; - trxcon->trx->tx_power = par_req->tx_power; + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_SET_CONFIG_REQ, &req); msgb_free(msg); return 0; @@ -793,41 +681,27 @@ static int l1ctl_rx_param_req(struct l1ctl_client *l1c, struct msgb *msg) static int l1ctl_rx_tch_mode_req(struct l1ctl_client *l1c, struct msgb *msg) { struct trxcon_inst *trxcon = l1c->priv; - struct l1ctl_tch_mode_req *req; - struct l1sched_lchan_state *lchan; - struct l1sched_ts *ts; - unsigned int tn; + struct l1ctl_tch_mode_req *mode_req; + int rc; - req = (struct l1ctl_tch_mode_req *) msg->l1h; + mode_req = (struct l1ctl_tch_mode_req *)msg->l1h; LOGPFSMSL(trxcon->fi, DL1C, LOGL_NOTICE, "Received L1CTL_TCH_MODE_REQ (tch_mode=%u, audio_mode=%u)\n", - req->tch_mode, req->audio_mode); - - /* Iterate over timeslot list */ - for (tn = 0; tn < ARRAY_SIZE(trxcon->sched->ts); tn++) { - /* Timeslot is not allocated */ - ts = trxcon->sched->ts[tn]; - if (ts == NULL) - continue; - - /* Timeslot is not configured */ - if (ts->mf_layout == NULL) - continue; - - /* Iterate over all allocated lchans */ - llist_for_each_entry(lchan, &ts->lchans, list) { - /* Omit inactive channels */ - if (!lchan->active) - continue; - - /* Set TCH mode */ - lchan->tch_mode = req->tch_mode; - } - } + mode_req->tch_mode, mode_req->audio_mode); /* TODO: do we need to care about audio_mode? */ + struct trxcon_param_set_ccch_tch_mode_req req = { + .mode = mode_req->tch_mode, + }; + + rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_SET_TCH_MODE_REQ, &req); + if (rc != 0 || !req.applied) { + talloc_free(msg); + return rc; + } + /* Re-use the original message as confirmation */ struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; l1h->msg_type = L1CTL_TCH_MODE_CONF; @@ -838,41 +712,27 @@ static int l1ctl_rx_tch_mode_req(struct l1ctl_client *l1c, struct msgb *msg) static int l1ctl_rx_crypto_req(struct l1ctl_client *l1c, struct msgb *msg) { struct trxcon_inst *trxcon = l1c->priv; - struct l1ctl_crypto_req *req; + struct l1ctl_crypto_req *cr; struct l1ctl_info_ul *ul; - struct l1sched_ts *ts; - uint8_t tn; - int rc = 0; ul = (struct l1ctl_info_ul *) msg->l1h; - req = (struct l1ctl_crypto_req *) ul->payload; + cr = (struct l1ctl_crypto_req *) ul->payload; + + struct trxcon_param_crypto_req req = { + .chan_nr = ul->chan_nr, + .a5_algo = cr->algo, + .key_len = cr->key_len, + .key = cr->key, + }; LOGPFSMSL(trxcon->fi, DL1C, LOGL_NOTICE, "L1CTL_CRYPTO_REQ (algo=A5/%u, key_len=%u)\n", - req->algo, req->key_len); + req.a5_algo, req.key_len); - /* Determine TS index */ - tn = ul->chan_nr & 0x7; + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_CRYPTO_REQ, &req); - /* Make sure that required TS is allocated and configured */ - ts = trxcon->sched->ts[tn]; - if (ts == NULL || ts->mf_layout == NULL) { - LOGPFSMSL(trxcon->fi, DL1C, LOGL_ERROR, "TS %u is not configured\n", tn); - rc = -EINVAL; - goto exit; - } - - /* Poke scheduler */ - rc = l1sched_start_ciphering(ts, req->algo, req->key, req->key_len); - if (rc) { - LOGPFSMSL(trxcon->fi, DL1C, LOGL_ERROR, "Couldn't configure ciphering\n"); - rc = -EINVAL; - goto exit; - } - -exit: msgb_free(msg); - return rc; + return 0; } int l1ctl_rx_cb(struct l1ctl_client *l1c, struct msgb *msg) diff --git a/src/host/trxcon/src/trx_if.c b/src/host/trxcon/src/trx_if.c index 92b9d12c6..d51c9d1a9 100644 --- a/src/host/trxcon/src/trx_if.c +++ b/src/host/trxcon/src/trx_if.c @@ -40,7 +40,6 @@ #include #include -#include #include #include #include @@ -163,7 +162,6 @@ static void trx_ctrl_send(struct trx_instance *trx) static void trx_ctrl_timer_cb(void *data) { struct trx_instance *trx = (struct trx_instance *) data; - struct trxcon_inst *trxcon = trx->trxcon; struct trx_ctrl_msg *tcm; /* Queue may be cleaned at this moment */ @@ -176,7 +174,7 @@ static void trx_ctrl_timer_cb(void *data) if (++tcm->retry_cnt > 3) { LOGPFSML(trx->fi, LOGL_NOTICE, "Transceiver offline\n"); osmo_fsm_inst_state_chg(trx->fi, TRX_STATE_OFFLINE, 0, 0); - osmo_fsm_inst_dispatch(trxcon->fi, TRX_EVENT_OFFLINE, trx); + osmo_fsm_inst_term(trx->fi, OSMO_FSM_TERM_TIMEOUT, NULL); return; } @@ -388,9 +386,13 @@ static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp) return; } - /* Send L1CTL_PM_CONF */ - l1ctl_tx_pm_conf(trxcon->l1c, band_arfcn, dbm, - band_arfcn == trx->pm_band_arfcn_stop); + struct trxcon_param_full_power_scan_res res = { + .last_result = band_arfcn == trx->pm_band_arfcn_stop, + .band_arfcn = band_arfcn, + .dbm = dbm, + }; + + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_FULL_POWER_SCAN_RES, &res); /* Schedule a next measurement */ if (band_arfcn != trx->pm_band_arfcn_stop) @@ -479,7 +481,6 @@ int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn, uint8_t maio, static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what) { struct trx_instance *trx = ofd->data; - struct trxcon_inst *trxcon = trx->trxcon; struct trx_ctrl_msg *tcm; int resp, rsp_len; char buf[TRXC_BUF_SIZE], *p; @@ -560,8 +561,7 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what) return 0; rsp_error: - /* Notify higher layers about the problem */ - osmo_fsm_inst_dispatch(trxcon->fi, TRX_EVENT_RSP_ERROR, trx); + osmo_fsm_inst_term(trx->fi, OSMO_FSM_TERM_ERROR, NULL); return -EIO; } @@ -712,7 +712,7 @@ struct trx_instance *trx_if_open(struct trxcon_inst *trxcon, } /* Allocate a new dedicated state machine */ - trx->fi = osmo_fsm_inst_alloc_child(&trx_fsm, trxcon->fi, TRX_EVENT_OFFLINE); + trx->fi = osmo_fsm_inst_alloc_child(&trx_fsm, trxcon->fi, TRXCON_EV_L1IF_FAILURE); if (trx->fi == NULL) { LOGPFSML(trxcon->fi, LOGL_ERROR, "Failed to allocate an instance " "of FSM '%s'\n", trx_fsm.name); diff --git a/src/host/trxcon/src/trxcon.c b/src/host/trxcon/src/trxcon.c index 14354d1d9..d4f4214fe 100644 --- a/src/host/trxcon/src/trxcon.c +++ b/src/host/trxcon/src/trxcon.c @@ -49,8 +49,6 @@ #include #include -#define S(x) (1 << (x)) - #define COPYRIGHT \ "Copyright (C) 2016-2022 by Vadim Yanitskiy \n" \ "Contributions by sysmocom - s.f.m.c. GmbH \n" \ @@ -142,35 +140,35 @@ int l1sched_handle_data_ind(struct l1sched_lchan_state *lchan, const struct l1sched_lchan_desc *lchan_desc; struct l1sched_state *sched = lchan->ts->sched; struct trxcon_inst *trxcon = sched->priv; - struct l1ctl_info_dl dl_hdr; int rc; lchan_desc = &l1sched_lchan_desc[lchan->type]; - dl_hdr = (struct l1ctl_info_dl) { + struct trxcon_param_rx_traffic_data_ind ind = { .chan_nr = lchan_desc->chan_nr | lchan->ts->index, .link_id = lchan_desc->link_id, - .frame_nr = htonl(meas->fn), - .band_arfcn = htons(trxcon->trx->band_arfcn), - .fire_crc = data_len > 0 ? 0 : 2, - .rx_level = dbm2rxlev(meas->rssi), - .num_biterr = n_errors, - /* TODO: set proper .snr */ + .frame_nr = meas->fn, + .toa256 = meas->toa256, + .rssi = meas->rssi, + .n_errors = n_errors, + .n_bits_total = n_bits_total, + .data_len = data_len, + .data = data, }; switch (dt) { case L1SCHED_DT_TRAFFIC: case L1SCHED_DT_PACKET_DATA: - rc = l1ctl_tx_dt_ind(trxcon->l1c, &dl_hdr, data, data_len, true); + rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_RX_TRAFFIC_IND, &ind); break; case L1SCHED_DT_SIGNALING: - rc = l1ctl_tx_dt_ind(trxcon->l1c, &dl_hdr, data, data_len, false); + rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_RX_DATA_IND, &ind); break; case L1SCHED_DT_OTHER: if (lchan->type == L1SCHED_SCH) { - if (trxcon->fbsb_conf_sent) + if (trxcon->fi->state != TRXCON_ST_FBSB_SEARCH) return 0; - rc = l1ctl_tx_fbsb_conf(trxcon->l1c, 0, &dl_hdr, sched->bsic); + rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_FBSB_SEARCH_RES, NULL); break; } /* fall through */ @@ -255,75 +253,6 @@ int l1sched_handle_data_cnf(struct l1sched_lchan_state *lchan, return rc; } -/* The trxcon state machine */ -static void trxcon_fsm_idle_action(struct osmo_fsm_inst *fi, - uint32_t event, void *data) -{ - if (event == L1CTL_EVENT_CONNECT) - osmo_fsm_inst_state_chg(fi, TRXCON_STATE_MANAGED, 0, 0); -} - -static void trxcon_fsm_managed_action(struct osmo_fsm_inst *fi, - uint32_t event, void *data) -{ - struct trxcon_inst *trxcon = fi->priv; - - switch (event) { - case L1CTL_EVENT_DISCONNECT: - osmo_fsm_inst_state_chg(fi, TRXCON_STATE_IDLE, 0, 0); - - if (trxcon->trx->fi->state != TRX_STATE_OFFLINE) { - /* Reset scheduler and clock counter */ - l1sched_reset(trxcon->sched, true); - - /* TODO: implement trx_if_reset() */ - trx_if_cmd_poweroff(trxcon->trx); - trx_if_cmd_echo(trxcon->trx); - } - break; - case TRX_EVENT_RSP_ERROR: - case TRX_EVENT_OFFLINE: - /* TODO: notify L2 & L3 about that */ - break; - default: - LOGPFSML(fi, LOGL_ERROR, "Unhandled event %u\n", event); - } -} - -static struct osmo_fsm_state trxcon_fsm_states[] = { - [TRXCON_STATE_IDLE] = { - .in_event_mask = S(L1CTL_EVENT_CONNECT), - .out_state_mask = S(TRXCON_STATE_MANAGED), - .name = "IDLE", - .action = trxcon_fsm_idle_action, - }, - [TRXCON_STATE_MANAGED] = { - .in_event_mask = ( - S(L1CTL_EVENT_DISCONNECT) | - S(TRX_EVENT_RSP_ERROR) | - S(TRX_EVENT_OFFLINE)), - .out_state_mask = S(TRXCON_STATE_IDLE), - .name = "MANAGED", - .action = trxcon_fsm_managed_action, - }, -}; - -static const struct value_string trxcon_fsm_event_names[] = { - OSMO_VALUE_STRING(L1CTL_EVENT_CONNECT), - OSMO_VALUE_STRING(L1CTL_EVENT_DISCONNECT), - OSMO_VALUE_STRING(TRX_EVENT_OFFLINE), - OSMO_VALUE_STRING(TRX_EVENT_RSP_ERROR), - { 0, NULL } -}; - -static struct osmo_fsm trxcon_fsm_def = { - .name = "trxcon", - .states = trxcon_fsm_states, - .num_states = ARRAY_SIZE(trxcon_fsm_states), - .log_subsys = DAPP, - .event_names = trxcon_fsm_event_names, -}; - struct trxcon_inst *trxcon_inst_alloc(void *ctx, unsigned int id) { struct trxcon_inst *trxcon; @@ -377,8 +306,6 @@ void trxcon_inst_free(struct trxcon_inst *trxcon) if (trxcon->trx != NULL) trx_if_close(trxcon->trx); - osmo_timer_del(&trxcon->fbsb_timer); - if (trxcon->fi != NULL) osmo_fsm_inst_free(trxcon->fi); talloc_free(trxcon); @@ -406,6 +333,8 @@ static void l1ctl_conn_close_cb(struct l1ctl_client *l1c) if (trxcon == NULL) return; + osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_L2IF_FAILURE, NULL); + /* l1c is free()ed by the caller */ trxcon->l1c = NULL; trxcon_inst_free(trxcon); @@ -560,6 +489,8 @@ int main(int argc, char **argv) log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME); log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END); + osmo_fsm_log_timeouts(true); + /* Optional GSMTAP */ if (app_data.gsmtap_ip != NULL) { app_data.gsmtap = gsmtap_source_init(app_data.gsmtap_ip, GSMTAP_UDP_PORT, 1); @@ -571,9 +502,6 @@ int main(int argc, char **argv) gsmtap_source_add_sink(app_data.gsmtap); } - /* Register the trxcon state machine */ - OSMO_ASSERT(osmo_fsm_register(&trxcon_fsm_def) == 0); - /* Start the L1CTL server */ server_cfg = (struct l1ctl_server_cfg) { .sock_path = app_data.bind_socket, diff --git a/src/host/trxcon/src/trxcon_fsm.c b/src/host/trxcon/src/trxcon_fsm.c new file mode 100644 index 000000000..1d3b515fe --- /dev/null +++ b/src/host/trxcon/src/trxcon_fsm.c @@ -0,0 +1,481 @@ +/* + * OsmocomBB <-> SDR connection bridge + * + * (C) 2022 by sysmocom - s.f.m.c. GmbH + * Author: Vadim Yanitskiy + * + * 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. + * + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define S(x) (1 << (x)) + +static void trxcon_allstate_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct trxcon_inst *trxcon = fi->priv; + + switch (event) { + case TRXCON_EV_L1IF_FAILURE: + case TRXCON_EV_L2IF_FAILURE: + LOGPFSML(fi, LOGL_NOTICE, "Event %s is not handled\n", + osmo_fsm_event_name(&trxcon_fsm_def, event)); + /* TODO: osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); */ + break; + case TRXCON_EV_RESET_FULL_REQ: + if (fi->state != TRXCON_ST_RESET) + osmo_fsm_inst_state_chg(fi, TRXCON_ST_RESET, 0, 0); + l1sched_reset(trxcon->sched, true); + trx_if_cmd_poweroff(trxcon->trx); + trx_if_cmd_echo(trxcon->trx); + break; + case TRXCON_EV_RESET_SCHED_REQ: + l1sched_reset(trxcon->sched, false); + break; + case TRXCON_EV_SET_CONFIG_REQ: + { + const struct trxcon_param_set_config_req *req = data; + + if (trxcon->trx->ta != req->timing_advance) + trx_if_cmd_setta(trxcon->trx, req->timing_advance); + trxcon->trx->tx_power = req->tx_power; + trxcon->trx->ta = req->timing_advance; + break; + } + default: + OSMO_ASSERT(0); + } +} + +static int trxcon_timer_cb(struct osmo_fsm_inst *fi) +{ + struct trxcon_inst *trxcon = fi->priv; + + switch (fi->state) { + case TRXCON_ST_FBSB_SEARCH: + l1ctl_tx_fbsb_fail(trxcon->l1c, trxcon->trx->band_arfcn); + osmo_fsm_inst_state_chg(fi, TRXCON_ST_RESET, 0, 0); + return 0; + default: + OSMO_ASSERT(0); + } +} + +static void trxcon_st_reset_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct trxcon_inst *trxcon = fi->priv; + + switch (event) { + case TRXCON_EV_FBSB_SEARCH_REQ: + { + const struct trxcon_param_fbsb_search_req *req = data; + + osmo_fsm_inst_state_chg_ms(fi, TRXCON_ST_FBSB_SEARCH, req->timeout_ms, 0); + + l1sched_configure_ts(trxcon->sched, 0, req->pchan_config); + + /* Only if current ARFCN differs */ + if (trxcon->trx->band_arfcn != req->band_arfcn) { + /* Update current ARFCN */ + trxcon->trx->band_arfcn = req->band_arfcn; + + /* Tune transceiver to required ARFCN */ + trx_if_cmd_rxtune(trxcon->trx, req->band_arfcn); + trx_if_cmd_txtune(trxcon->trx, req->band_arfcn); + } + + /* Transceiver might have been powered on before, e.g. + * in case of sending L1CTL_FBSB_REQ due to signal loss. */ + if (!trxcon->trx->powered_up) + trx_if_cmd_poweron(trxcon->trx); + break; + } + case TRXCON_EV_FULL_POWER_SCAN_REQ: + { + const struct trxcon_param_full_power_scan_req *req = data; + + osmo_fsm_inst_state_chg(fi, TRXCON_ST_FULL_POWER_SCAN, 0, 0); /* TODO: timeout */ + trx_if_cmd_measure(trxcon->trx, req->band_arfcn_start, req->band_arfcn_stop); + break; + } + default: + OSMO_ASSERT(0); + } +} + +static void trxcon_st_full_power_scan_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct trxcon_inst *trxcon = fi->priv; + + switch (event) { + case TRXCON_EV_FULL_POWER_SCAN_RES: + { + const struct trxcon_param_full_power_scan_res *res = data; + + l1ctl_tx_pm_conf(trxcon->l1c, res->band_arfcn, res->dbm, res->last_result); + break; + } + default: + OSMO_ASSERT(0); + } +} + +static void trxcon_st_fbsb_search_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct trxcon_inst *trxcon = fi->priv; + + switch (event) { + case TRXCON_EV_FBSB_SEARCH_RES: + osmo_fsm_inst_state_chg(fi, TRXCON_ST_BCCH_CCCH, 0, 0); + l1ctl_tx_fbsb_conf(trxcon->l1c, + trxcon->trx->band_arfcn, + trxcon->sched->bsic); + break; + default: + OSMO_ASSERT(0); + } +} + +static void handle_tx_access_burst_req(struct osmo_fsm_inst *fi, + const struct trxcon_param_tx_access_burst_req *req) +{ + struct trxcon_inst *trxcon = fi->priv; + enum l1sched_ts_prim_type prim_type; + const struct l1sched_ts_prim *prim; + + const struct l1sched_ts_prim_rach rach = { + .synch_seq = req->synch_seq, + .offset = req->offset, + .ra = req->ra, + }; + + prim_type = req->is_11bit ? L1SCHED_PRIM_RACH11 : L1SCHED_PRIM_RACH8; + prim = l1sched_prim_push(trxcon->sched, prim_type, + req->chan_nr, req->link_id, + (const uint8_t *)&rach, sizeof(rach)); + if (prim == NULL) + LOGPFSML(fi, LOGL_ERROR, "Failed to enqueue a prim\n"); +} + +static void trxcon_st_bcch_ccch_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct trxcon_inst *trxcon = fi->priv; + struct l1sched_ts *ts; + int rc; + + switch (event) { + case TRXCON_EV_TX_ACCESS_BURST_REQ: + handle_tx_access_burst_req(fi, data); + break; + case TRXCON_EV_SET_CCCH_MODE_REQ: + { + struct trxcon_param_set_ccch_tch_mode_req *req = data; + enum gsm_phys_chan_config chan_config = req->mode; + + /* Make sure that TS0 is allocated and configured */ + ts = trxcon->sched->ts[0]; + if (ts == NULL || ts->mf_layout == NULL) { + LOGPFSML(fi, LOGL_ERROR, "TS0 is not configured\n"); + return; + } + + /* Do nothing if the current mode matches required */ + if (ts->mf_layout->chan_config != chan_config) + l1sched_configure_ts(trxcon->sched, 0, chan_config); + req->applied = true; + break; + } + case TRXCON_EV_DEDICATED_ESTABLISH_REQ: + { + const struct trxcon_param_dedicated_establish_req *req = data; + enum gsm_phys_chan_config config; + + config = l1sched_chan_nr2pchan_config(req->chan_nr); + if (config == GSM_PCHAN_NONE) { + LOGPFSML(fi, LOGL_ERROR, "Failed to determine channel config\n"); + return; + } + + if (req->hopping) { + /* Apply the freq. hopping parameters */ + rc = trx_if_cmd_setfh(trxcon->trx, + req->h1.hsn, req->h1.maio, + &req->h1.ma[0], req->h1.n); + if (rc) + return; + + /* Set current ARFCN to an invalid value */ + trxcon->trx->band_arfcn = 0xffff; + } else { + /* Tune transceiver to required ARFCN */ + if (trx_if_cmd_rxtune(trxcon->trx, req->h0.band_arfcn)) + return; + if (trx_if_cmd_txtune(trxcon->trx, req->h0.band_arfcn)) + return; + + /* Update current ARFCN */ + trxcon->trx->band_arfcn = req->h0.band_arfcn; + } + + rc = l1sched_configure_ts(trxcon->sched, req->chan_nr & 0x07, config); + if (rc) + return; + ts = trxcon->sched->ts[req->chan_nr & 0x07]; + OSMO_ASSERT(ts != NULL); + + l1sched_deactivate_all_lchans(ts); + + /* Activate only requested lchans */ + rc = l1sched_set_lchans(ts, req->chan_nr, 1, req->tch_mode, req->tsc); + if (rc) { + LOGPFSML(fi, LOGL_ERROR, "Failed to activate requested lchans\n"); + return; + } + + osmo_fsm_inst_state_chg(fi, TRXCON_ST_DEDICATED, 0, 0); + break; + } + case TRXCON_EV_RX_DATA_IND: + { + const struct trxcon_param_rx_traffic_data_ind *ind = data; + const struct l1ctl_info_dl dl_hdr = { + .chan_nr = ind->chan_nr, + .link_id = ind->link_id, + .frame_nr = htonl(ind->frame_nr), + .band_arfcn = htons(trxcon->trx->band_arfcn), + .fire_crc = ind->data_len > 0 ? 0 : 2, + .rx_level = dbm2rxlev(ind->rssi), + .num_biterr = ind->n_errors, + /* TODO: set proper .snr */ + }; + + l1ctl_tx_dt_ind(trxcon->l1c, &dl_hdr, ind->data, ind->data_len, false); + break; + } + default: + OSMO_ASSERT(0); + } +} + +static void trxcon_st_dedicated_action(struct osmo_fsm_inst *fi, + uint32_t event, void *data) +{ + struct trxcon_inst *trxcon = fi->priv; + + switch (event) { + case TRXCON_EV_TX_ACCESS_BURST_REQ: + handle_tx_access_burst_req(fi, data); + break; + case TRXCON_EV_DEDICATED_RELEASE_REQ: + l1sched_reset(trxcon->sched, false); + osmo_fsm_inst_state_chg(fi, TRXCON_ST_RESET, 0, 0); + break; + case TRXCON_EV_SET_TCH_MODE_REQ: + { + struct trxcon_param_set_ccch_tch_mode_req *req = data; + unsigned int tn; + + /* Iterate over timeslot list */ + for (tn = 0; tn < ARRAY_SIZE(trxcon->sched->ts); tn++) { + struct l1sched_ts *ts = trxcon->sched->ts[tn]; + struct l1sched_lchan_state *lchan; + + /* Timeslot is not allocated */ + if (ts == NULL || ts->mf_layout == NULL) + continue; + + /* Iterate over all allocated lchans */ + llist_for_each_entry(lchan, &ts->lchans, list) { + /* Omit inactive channels */ + if (!lchan->active) + continue; + lchan->tch_mode = req->mode; + req->applied = true; + } + } + break; + } + case TRXCON_EV_CRYPTO_REQ: + { + const struct trxcon_param_crypto_req *req = data; + unsigned int tn = req->chan_nr & 0x07; + struct l1sched_ts *ts; + + /* Make sure that required TS is allocated and configured */ + ts = trxcon->sched->ts[tn]; + if (ts == NULL || ts->mf_layout == NULL) { + LOGPFSML(fi, LOGL_ERROR, "TS%u is not configured\n", tn); + return; + } + + if (l1sched_start_ciphering(ts, req->a5_algo, req->key, req->key_len) != 0) { + LOGPFSML(fi, LOGL_ERROR, "Failed to configure ciphering\n"); + return; + } + break; + } + case TRXCON_EV_TX_TRAFFIC_REQ: + case TRXCON_EV_TX_DATA_REQ: + { + const struct trxcon_param_tx_traffic_data_req *req = data; + struct l1sched_ts_prim *prim; + + prim = l1sched_prim_push(trxcon->sched, L1SCHED_PRIM_DATA, + req->chan_nr, req->link_id, + req->data, req->data_len); + if (prim == NULL) { + LOGPFSML(fi, LOGL_ERROR, "Failed to enqueue a prim\n"); + return; + } + break; + } + case TRXCON_EV_RX_TRAFFIC_IND: + case TRXCON_EV_RX_DATA_IND: + { + const struct trxcon_param_rx_traffic_data_ind *ind = data; + const struct l1ctl_info_dl dl_hdr = { + .chan_nr = ind->chan_nr, + .link_id = ind->link_id, + .frame_nr = htonl(ind->frame_nr), + .band_arfcn = htons(trxcon->trx->band_arfcn), + .fire_crc = ind->data_len > 0 ? 0 : 2, + .rx_level = dbm2rxlev(ind->rssi), + .num_biterr = ind->n_errors, + /* TODO: set proper .snr */ + }; + + l1ctl_tx_dt_ind(trxcon->l1c, &dl_hdr, + ind->data, ind->data_len, + event == TRXCON_EV_RX_TRAFFIC_IND); + break; + } + default: + OSMO_ASSERT(0); + } +} + +static const struct osmo_fsm_state trxcon_fsm_states[] = { + [TRXCON_ST_RESET] = { + .name = "RESET", + .out_state_mask = S(TRXCON_ST_FBSB_SEARCH) + | S(TRXCON_ST_FULL_POWER_SCAN), + .in_event_mask = S(TRXCON_EV_FBSB_SEARCH_REQ) + | S(TRXCON_EV_FULL_POWER_SCAN_REQ), + .action = &trxcon_st_reset_action, + }, + [TRXCON_ST_FULL_POWER_SCAN] = { + .name = "FULL_POWER_SCAN", + .out_state_mask = S(TRXCON_ST_RESET), + .in_event_mask = S(TRXCON_EV_FULL_POWER_SCAN_RES), + .action = &trxcon_st_full_power_scan_action, + }, + [TRXCON_ST_FBSB_SEARCH] = { + .name = "FBSB_SEARCH", + .out_state_mask = S(TRXCON_ST_RESET) + | S(TRXCON_ST_BCCH_CCCH), + .in_event_mask = S(TRXCON_EV_FBSB_SEARCH_RES), + .action = &trxcon_st_fbsb_search_action, + }, + [TRXCON_ST_BCCH_CCCH] = { + .name = "BCCH_CCCH", + .out_state_mask = S(TRXCON_ST_RESET) + | S(TRXCON_ST_FBSB_SEARCH) + | S(TRXCON_ST_DEDICATED), + .in_event_mask = S(TRXCON_EV_RX_DATA_IND) + | S(TRXCON_EV_SET_CCCH_MODE_REQ) + | S(TRXCON_EV_TX_ACCESS_BURST_REQ) + | S(TRXCON_EV_DEDICATED_ESTABLISH_REQ), + .action = &trxcon_st_bcch_ccch_action, + }, + [TRXCON_ST_DEDICATED] = { + .name = "DEDICATED", + .out_state_mask = S(TRXCON_ST_RESET) + | S(TRXCON_ST_FBSB_SEARCH) + | S(TRXCON_ST_BCCH_CCCH), + .in_event_mask = S(TRXCON_EV_DEDICATED_RELEASE_REQ) + | S(TRXCON_EV_TX_ACCESS_BURST_REQ) + | S(TRXCON_EV_SET_TCH_MODE_REQ) + | S(TRXCON_EV_TX_TRAFFIC_REQ) + | S(TRXCON_EV_RX_TRAFFIC_IND) + | S(TRXCON_EV_TX_DATA_REQ) + | S(TRXCON_EV_RX_DATA_IND) + | S(TRXCON_EV_CRYPTO_REQ), + .action = &trxcon_st_dedicated_action, + }, +}; + +static const struct value_string trxcon_fsm_event_names[] = { + OSMO_VALUE_STRING(TRXCON_EV_L1IF_FAILURE), + OSMO_VALUE_STRING(TRXCON_EV_L2IF_FAILURE), + OSMO_VALUE_STRING(TRXCON_EV_RESET_FULL_REQ), + OSMO_VALUE_STRING(TRXCON_EV_RESET_SCHED_REQ), + OSMO_VALUE_STRING(TRXCON_EV_FULL_POWER_SCAN_REQ), + OSMO_VALUE_STRING(TRXCON_EV_FULL_POWER_SCAN_RES), + OSMO_VALUE_STRING(TRXCON_EV_FBSB_SEARCH_REQ), + OSMO_VALUE_STRING(TRXCON_EV_FBSB_SEARCH_RES), + OSMO_VALUE_STRING(TRXCON_EV_SET_CCCH_MODE_REQ), + OSMO_VALUE_STRING(TRXCON_EV_SET_TCH_MODE_REQ), + OSMO_VALUE_STRING(TRXCON_EV_SET_CONFIG_REQ), + OSMO_VALUE_STRING(TRXCON_EV_TX_ACCESS_BURST_REQ), + OSMO_VALUE_STRING(TRXCON_EV_DEDICATED_ESTABLISH_REQ), + OSMO_VALUE_STRING(TRXCON_EV_DEDICATED_RELEASE_REQ), + OSMO_VALUE_STRING(TRXCON_EV_TX_TRAFFIC_REQ), + OSMO_VALUE_STRING(TRXCON_EV_RX_TRAFFIC_IND), + OSMO_VALUE_STRING(TRXCON_EV_TX_DATA_REQ), + OSMO_VALUE_STRING(TRXCON_EV_RX_DATA_IND), + OSMO_VALUE_STRING(TRXCON_EV_CRYPTO_REQ), + { 0, NULL } +}; + +struct osmo_fsm trxcon_fsm_def = { + .name = "trxcon", + .states = trxcon_fsm_states, + .num_states = ARRAY_SIZE(trxcon_fsm_states), + .log_subsys = DAPP, + .event_names = trxcon_fsm_event_names, + .allstate_event_mask = S(TRXCON_EV_L1IF_FAILURE) + | S(TRXCON_EV_L2IF_FAILURE) + | S(TRXCON_EV_RESET_FULL_REQ) + | S(TRXCON_EV_RESET_SCHED_REQ) + | S(TRXCON_EV_SET_CONFIG_REQ), + .allstate_action = &trxcon_allstate_action, + .timer_cb = &trxcon_timer_cb, +}; + +static __attribute__((constructor)) void on_dso_load(void) +{ + OSMO_ASSERT(osmo_fsm_register(&trxcon_fsm_def) == 0); +}