diff options
author | Harald Welte <laforge@gnumonks.org> | 2016-01-16 10:18:24 +0100 |
---|---|---|
committer | Harald Welte <laforge@gnumonks.org> | 2016-01-16 17:26:25 +0100 |
commit | 3e98f942e51ca5f39a09adcb3d0d68d93b1588b0 (patch) | |
tree | b4f67d703c69d72e4accbf86e6eebc1cfdded3d1 /src/osmo-bts-octphy | |
parent | 7bd2251dcb15552d424b53e0e0e85a4d8150868c (diff) |
OCTPHY: Implement command re-transmission after message loss
We re-use the 'wait_l1_conf' structure for implementing the
unacknowledge command window towards the PHY. This means that thre will
unconditionally be a 'wait_l1_conf' now, even for requests where the
caller didn't provide a call-back.
Diffstat (limited to 'src/osmo-bts-octphy')
-rw-r--r-- | src/osmo-bts-octphy/l1_if.c | 235 | ||||
-rw-r--r-- | src/osmo-bts-octphy/l1_if.h | 20 |
2 files changed, 216 insertions, 39 deletions
diff --git a/src/osmo-bts-octphy/l1_if.c b/src/osmo-bts-octphy/l1_if.c index e1f8af0d..2c029177 100644 --- a/src/osmo-bts-octphy/l1_if.c +++ b/src/osmo-bts-octphy/l1_if.c @@ -1,7 +1,7 @@ /* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */ /* Copyright (c) 2014 Octasic Inc. All rights reserved. - * Copyright (c) 2015 Harald Welte <laforge@gnumonks.org> + * Copyright (c) 2015-2016 Harald Welte <laforge@gnumonks.org> * * based on a copy of osmo-bts-sysmo/l1_if.c, which is * Copyright (C) 2011-2014 by Harald Welte <laforge@gnumonks.org> @@ -58,6 +58,13 @@ #define cPKTAPI_FIFO_ID_MSG 0xAAAA0001 +/* maximum window of unacknowledged commands */ +#define UNACK_CMD_WINDOW 8 +/* maximum number of re-transmissions of a command */ +#define MAX_RETRANS 3 +/* timeout until which we expect PHY to respond */ +#define CMD_TIMEOUT 5 + /* allocate a msgb for a Layer1 primitive */ struct msgb *l1p_msgb_alloc(void) { @@ -137,17 +144,28 @@ struct gsm_lchan *get_lchan_by_lchid(struct gsm_bts_trx *trx, /* TODO: Unify with sysmobts? */ struct wait_l1_conf { + /* list of wait_l1_conf in the phy handle */ struct llist_head list; + /* expiration timer */ struct osmo_timer_list timer; + /* primtivie / command ID */ uint32_t prim_id; + /* transaction ID */ uint32_t trans_id; + /* copy of the msgb containing the command */ + struct msgb *cmd_msg; + /* call-back to call on response */ l1if_compl_cb *cb; + /* data to hand to call-back on response */ void *cb_data; + /* number of re-transmissions so far */ + uint32_t num_retrans; }; static void release_wlc(struct wait_l1_conf *wlc) { osmo_timer_del(&wlc->timer); + msgb_free(wlc->cmd_msg); talloc_free(wlc); } @@ -155,11 +173,64 @@ static void l1if_req_timeout(void *data) { struct wait_l1_conf *wlc = data; + /* FIXME: Implement re-transmission of command on timer expiration */ + LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n", get_value_string(octphy_cid_vals, wlc->prim_id)); exit(23); } +/* FIXME: this should be in libosmocore */ +static struct llist_head *llist_first(struct llist_head *head) +{ + if (llist_empty(head)) + return NULL; + return head->next; +} + +static void check_refill_window(struct octphy_hdl *fl1h, struct wait_l1_conf *recent) +{ + struct wait_l1_conf *wlc; + int space = UNACK_CMD_WINDOW - fl1h->wlc_list_len; + int i; + + for (i = 0; i < space; i++) { + /* get head of queue */ + struct llist_head *first = llist_first(&fl1h->wlc_postponed); + struct msgb *msg; + if (!first) + break; + wlc = llist_entry(first, struct wait_l1_conf, list); + + /* remove from head of postponed queue */ + llist_del(&wlc->list); + fl1h->wlc_postponed_len--; + + /* add to window */ + llist_add_tail(&wlc->list, &fl1h->wlc_list); + fl1h->wlc_list_len++; + + if (wlc != recent) { + LOGP(DL1C, LOGL_INFO, "Txing formerly postponed " + "command %s (trans_id=%u)\n", + get_value_string(octphy_cid_vals, wlc->prim_id), + wlc->trans_id); + } + msg = msgb_copy(wlc->cmd_msg, "Tx from wlc_postponed"); + /* queue for execution and response handling */ + if (osmo_wqueue_enqueue(&fl1h->phy_wq, msg) != 0) { + LOGP(DL1C, LOGL_ERROR, "Tx Write queue full. dropping msg\n"); + llist_del(&wlc->list); + msgb_free(msg); + exit(24); + } + /* schedule a timer for CMD_TIMEOUT seconds. If PHY fails to + * respond, we terminate */ + osmo_timer_schedule(&wlc->timer, CMD_TIMEOUT, 0); + + } +} + /* send a request(command) to L1, scheduling a call-back to be executed * on receiving the response*/ int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg, @@ -173,9 +244,9 @@ int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg, uint32_t type_r_cmdid = ntohl(msg_hdr->ul_Type_R_CmdId); uint32_t cmd_id = (type_r_cmdid >> cOCTVC1_MSG_ID_BIT_OFFSET) & cOCTVC1_MSG_ID_BIT_MASK; - LOGP(DL1C, LOGL_DEBUG, "l1if_req_compl(msg_len=%u, cmd_id=%s) %s\n", + LOGP(DL1C, LOGL_DEBUG, "l1if_req_compl(msg_len=%u, cmd_id=%s, trans_id=%u)\n", msgb_length(msg), get_value_string(octphy_cid_vals, cmd_id), - osmo_hexdump(msg->data, msgb_length(msg))); + ntohl(msg_hdr->ulTransactionId)); /* push the two common headers in front */ octvocnet_push_ctl_hdr(msg, cOCTVC1_FIFO_ID_MGW_CONTROL, @@ -183,26 +254,28 @@ int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg, octpkt_push_common_hdr(msg, cOCTVOCNET_PKT_FORMAT_CTRL, 0, cOCTPKT_HDR_CONTROL_PROTOCOL_TYPE_ENUM_OCTVOCNET); - if (cb) { - wlc = talloc_zero(fl1h, struct wait_l1_conf); - wlc->cb = cb; - wlc->cb_data = data; - wlc->prim_id = cmd_id; - wlc->trans_id = ntohl(msg_hdr->ulTransactionId); - - /* schedule a timer for 10 seconds. If PHY fails to - * respond, we terminate */ - wlc->timer.data = wlc; - wlc->timer.cb = l1if_req_timeout; - osmo_timer_schedule(&wlc->timer, 10, 0); - - llist_add_tail(&wlc->list, &fl1h->wlc_list); - } - - /* queue for execution and response handling */ - if (osmo_wqueue_enqueue(&fl1h->phy_wq, msg) != 0) { - LOGP(DL1C, LOGL_ERROR, "Tx Write queue full. dropping msg\n"); - msgb_free(msg); + wlc = talloc_zero(fl1h, struct wait_l1_conf); + wlc->cmd_msg = msg; + wlc->cb = cb; + wlc->cb_data = data; + wlc->prim_id = cmd_id; + wlc->trans_id = ntohl(msg_hdr->ulTransactionId); + wlc->timer.data = wlc; + wlc->timer.cb = l1if_req_timeout; + + /* unconditionally add t to the tail of postponed commands */ + llist_add_tail(&wlc->list, &fl1h->wlc_postponed); + fl1h->wlc_postponed_len++; + + /* check if the unacknowledged window has some space to transmit */ + check_refill_window(fl1h, wlc); + + /* if any messages are in the queue, it must be at least 'our' message, + * as we always enqueue from the tail */ + if (fl1h->wlc_postponed_len) { + fl1h->stats.wlc_postponed++; + LOGP(DL1C, LOGL_INFO, "Postponed command %s (trans_id=%u)\n", + get_value_string(octphy_cid_vals, cmd_id), wlc->trans_id); } return 0; @@ -622,6 +695,7 @@ int bts_model_init(struct gsm_bts *bts) return -ENOMEM; INIT_LLIST_HEAD(&fl1h->wlc_list); + INIT_LLIST_HEAD(&fl1h->wlc_postponed); fl1h->priv = bts->c0; bts->c0->role_bts.l1h = fl1h; /* FIXME: what is the nominal transmit power of the PHY/board? */ @@ -981,20 +1055,73 @@ static int rx_gsm_trx_time_ind(struct msgb *msg) return handle_mph_time_ind(fl1h, tind->TrxId.byTrxId, tind->ulFrameNumber); } +/* mark this message as RETRANSMIT of a previous msg */ +static void msg_set_retrans_flag(struct msgb *msg) +{ + tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; + uint32_t type_r_cmdid = ntohl(mh->ul_Type_R_CmdId); + type_r_cmdid |= cOCTVC1_MSG_RETRANSMIT_FLAG; + mh->ul_Type_R_CmdId = htonl(type_r_cmdid); +} + +/* re-transmit all commands in the window that have a transaction ID lower than + * trans_id */ +static int retransmit_wlc_upto(struct octphy_hdl *fl1h, uint32_t trans_id) +{ + struct wait_l1_conf *wlc; + int count = 0; + + LOGP(DL1C, LOGL_INFO, "Retransmitting up to trans_id=%u\n", trans_id); + + /* trans_id represents the trans_id of the just-received response, we + * therefore need to re-send any commands with a lower trans_id */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + if (wlc->trans_id <= trans_id) { + struct msgb *msg; + if (wlc->num_retrans >= MAX_RETRANS) { + LOGP(DL1C, LOGL_ERROR, "Command %s: maximum " + "number of retransmissions reached\n", + get_value_string(octphy_cid_vals, + wlc->prim_id)); + exit(24); + } + wlc->num_retrans++; + msg = msgb_copy(wlc->cmd_msg, "PHY CMD Retrans"); + msg_set_retrans_flag(msg); + osmo_wqueue_enqueue(&fl1h->phy_wq, msg); + osmo_timer_schedule(&wlc->timer, CMD_TIMEOUT, 0); + count++; + LOGP(DL1C, LOGL_INFO, "Re-transmitting %s " + "(trans_id=%u, attempt %u)\n", + get_value_string(octphy_cid_vals, wlc->prim_id), + wlc->trans_id, wlc->num_retrans); + } + } + + return count; +} + /* Receive a response (to a prior command) from the PHY */ static int rx_octvc1_resp(struct msgb *msg, uint32_t msg_id, uint32_t trans_id) { tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; + struct llist_head *first; uint32_t return_code = ntohl(mh->ulReturnCode); struct octphy_hdl *fl1h = msg->dst; - struct wait_l1_conf *wlc; + struct wait_l1_conf *wlc = NULL; int rc; - /* check if anyone has registered a call-back for the given - * command_id and transaction, and call them back */ - llist_for_each_entry(wlc, &fl1h->wlc_list, list) { - if (wlc->prim_id == msg_id && wlc->trans_id == trans_id) { + LOGP(DL1C, LOGL_DEBUG, "rx_octvc1_resp(msg_id=%s, trans_id=%u)\n", + get_value_string(octphy_cid_vals, msg_id), trans_id); + + /* check if the response is for the oldest (first) entry in wlc_list */ + first = llist_first(&fl1h->wlc_list); + if (first) { + wlc = llist_entry(first, struct wait_l1_conf, list); + if (wlc->trans_id == trans_id) { + /* process the received response */ llist_del(&wlc->list); + fl1h->wlc_list_len--; if (wlc->cb) { /* call-back function must take msgb * ownership. */ @@ -1004,19 +1131,41 @@ static int rx_octvc1_resp(struct msgb *msg, uint32_t msg_id, uint32_t trans_id) msgb_free(msg); } release_wlc(wlc); + /* check if there are postponed wlcs and re-fill the window */ + check_refill_window(fl1h, NULL); return rc; } } - /* ignore unhandled responses that went ok. The caller might just not - * be interested in them (as we are in case of DATA.req and - * EMPTY-FRAME.req */ + LOGP(DL1C, LOGL_NOTICE, "Sequence error: Rx response (cmd=%s, trans_id=%u) " + "for cmd != oldest entry in window (trans_id=%u)!!\n", + get_value_string(octphy_cid_vals, msg_id), trans_id, + wlc ? wlc->trans_id : 0); + + /* check if the response is for any of the other entries in wlc_list */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + if (wlc->prim_id == msg_id && wlc->trans_id == trans_id) { + /* it is assumed that all of the previous response + * message(s) have been lost, and we need to + * re-transmit older messages from the window */ + rc = retransmit_wlc_upto(fl1h, trans_id); + fl1h->stats.retrans_cmds_trans_id += rc; + /* do not process the received response, we rather wait + * for the in-order retransmissions to arrive */ + msgb_free(msg); + return 0; + } + } + + /* ignore unhandled responses that went ok, but let the user know about + * failing ones. */ if (return_code != cOCTVC1_RC_OK) { - LOGP(DL1C, LOGL_NOTICE, "Rx Unexpected response %s (trans_id=0x%x)\n", + LOGP(DL1C, LOGL_NOTICE, "Rx Unexpected response %s (trans_id=%u)\n", get_value_string(octphy_cid_vals, msg_id), trans_id); } msgb_free(msg); return 0; + } static int rx_gsm_clockmgr_status_ind(struct msgb *msg) @@ -1164,17 +1313,25 @@ static int rx_octvc1_event_msg(struct msgb *msg) /* Receive a supervisory message from the PHY */ static int rx_octvc1_supv(struct msgb *msg, uint32_t msg_id, uint32_t trans_id) { + struct octphy_hdl *fl1h = msg->dst; tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; - uint32_t return_code = ntohl(mh->ulReturnCode); tOCTVC1_CTRL_MSG_MODULE_REJECT_SPV *rej; + uint32_t return_code = ntohl(mh->ulReturnCode); + uint32_t rejected_msg_id; + int rc; switch (msg_id) { case cOCTVC1_CTRL_MSG_MODULE_REJECT_SID: rej = (tOCTVC1_CTRL_MSG_MODULE_REJECT_SPV *) mh; mOCTVC1_CTRL_MSG_MODULE_REJECT_SPV_SWAP(rej); - LOGP(DL1C, LOGL_NOTICE, "Rx REJECT_SID (ExpectedTID=0x%08x, " - "RejectedCmdID=0x%08x)\n", rej->ulExpectedTransactionId, - rej->ulRejectedCmdId); + rejected_msg_id = (rej->ulRejectedCmdId >> cOCTVC1_MSG_ID_BIT_OFFSET) & + cOCTVC1_MSG_ID_BIT_MASK; + LOGP(DL1C, LOGL_NOTICE, "Rx REJECT_SID (TID=%u, " + "ExpectedTID=0x%08x, RejectedCmdID=%s)\n", + trans_id, rej->ulExpectedTransactionId, + get_value_string(octphy_cid_vals, rejected_msg_id)); + rc = retransmit_wlc_upto(fl1h, trans_id); + fl1h->stats.retrans_cmds_supv += rc; break; default: LOGP(DL1C, LOGL_NOTICE, "Rx unhandled supervisory msg_id " @@ -1289,9 +1446,9 @@ static int rx_octphy_msg(struct msgb *msg) & cOCTVOCNET_PKT_LENGTH_MASK; if (len > msgb_length(msg)) { - LOGP(DL1C, LOGL_ERROR, "Received length (%u) > length " - "as per packt header (%u)\n", msgb_length(msg), - len); + LOGP(DL1C, LOGL_ERROR, "Received length (%u) < length " + "as per packet header (%u): %s\n", msgb_length(msg), + len, osmo_hexdump(msgb_data(msg), msgb_length(msg))); msgb_free(msg); return -1; } diff --git a/src/osmo-bts-octphy/l1_if.h b/src/osmo-bts-octphy/l1_if.h index ce956807..be2338b3 100644 --- a/src/osmo-bts-octphy/l1_if.h +++ b/src/osmo-bts-octphy/l1_if.h @@ -38,7 +38,27 @@ struct octphy_hdl { uint32_t tx_atten_db; } config; + /* This is a list of outstanding commands sent to the PHY, for which we + * currently still wait for a response. Represented by 'struct + * wait_l1_conf' in l1_if.c - Octasic calls this the 'Unacknowledged + * Command Window' */ struct llist_head wlc_list; + int wlc_list_len; + struct { + /* messages retransmitted due to discontinuity of transaction + * ID in responses from PHY */ + uint32_t retrans_cmds_trans_id; + /* messages retransmitted due to supervisory messages by PHY */ + uint32_t retrans_cmds_supv; + /* number of commands/wlcs that we ever had to postpone */ + uint32_t wlc_postponed; + } stats; + + /* This is a list of wait_la_conf that OsmoBTS wanted to transmit to + * the PHY, but which couldn't yet been sent as the unacknowledged + * command window was full. */ + struct llist_head wlc_postponed; + int wlc_postponed_len; /* private pointer, points back to TRX */ void *priv; |