diff options
-rw-r--r-- | doc/manuals/chapters/anr.adoc | 159 | ||||
-rw-r--r-- | doc/manuals/osmobsc-usermanual.adoc | 2 | ||||
-rw-r--r-- | include/osmocom/bsc/abis_osmo.h | 2 | ||||
-rw-r--r-- | include/osmocom/bsc/bts.h | 5 | ||||
-rw-r--r-- | include/osmocom/bsc/gsm_04_08_rr.h | 3 | ||||
-rw-r--r-- | include/osmocom/bsc/gsm_data.h | 12 | ||||
-rw-r--r-- | include/osmocom/bsc/neighbor_ident.h | 3 | ||||
-rw-r--r-- | include/osmocom/bsc/pcuif_proto.h | 17 | ||||
-rw-r--r-- | include/osmocom/bsc/vty.h | 1 | ||||
-rw-r--r-- | src/ipaccess/ipaccess-config.c | 2 | ||||
-rw-r--r-- | src/ipaccess/ipaccess-proxy.c | 2 | ||||
-rw-r--r-- | src/osmo-bsc/abis_osmo.c | 104 | ||||
-rw-r--r-- | src/osmo-bsc/bsc_init.c | 49 | ||||
-rw-r--r-- | src/osmo-bsc/bsc_vty.c | 111 | ||||
-rw-r--r-- | src/osmo-bsc/bts.c | 39 | ||||
-rw-r--r-- | src/osmo-bsc/gsm_04_08_rr.c | 2 | ||||
-rw-r--r-- | src/osmo-bsc/neighbor_ident_vty.c | 8 | ||||
-rw-r--r-- | src/osmo-bsc/osmo_bsc_main.c | 3 | ||||
-rw-r--r-- | src/utils/bs11_config.c | 2 | ||||
-rw-r--r-- | src/utils/meas_json.c | 2 | ||||
-rw-r--r-- | tests/abis/abis_test.c | 2 | ||||
-rw-r--r-- | tests/acc/acc_test.c | 2 | ||||
-rw-r--r-- | tests/nanobts_omlattr/nanobts_omlattr_test.c | 2 |
23 files changed, 529 insertions, 5 deletions
diff --git a/doc/manuals/chapters/anr.adoc b/doc/manuals/chapters/anr.adoc new file mode 100644 index 000000000..ec86b95e8 --- /dev/null +++ b/doc/manuals/chapters/anr.adoc @@ -0,0 +1,159 @@ +[[anr]] +== Automatic Neighbor Registration (ANR) + +Automatic Neighbor Registration (ANR from now on) is the process by which the +BSC is able to dynamically resolve the topology of the network, that is, cells neighboring +one another, by means of requesting and processing measurement reports from MS +camping on the network. + +This feature is not described in any 3GPP TS specification, but it can be +implemented by using standard specification features. The communication between +BSC and PCU, though, is not standardized, and hence Osmocom specific protocol is +used. + +ANR can be implemented using either (or both) CS and PS domains. In CS, MS are +requested to provide Measurement Reports through SACCH, while in PS MS are +requested to provide Packet measurement Reports through PDCH. + +=== ANR on CS domain (BTS) + +OsmOBSC doesn't implement this method, since the former (CS) relies on +SACCH which is slow (throughput constrainer) and usually already constringed +with usual usage, so using it for ANR would probably affect the operation of the +network. + +=== ANR on PS domain (PCU) + +Under the PS domain, the BSC provides a list of target desired frequencies to +the PCU and requests it to orchestrate the reporting of measures from MS on +those frequencies. The provided list of desired target frequencies is basically +the entire list of BCCH frequencies (TRX0) of all BTS under the management of +the BSC. + +In order to communicate with OsmoPCU, OsmoBSC uses the PCUIF protocol. Since in +the Osmocom architecture usually the PCU is directly attached to the BTS and hence +there's no direct connection between the BSC and the PCU, a new transport link +needs to be added to allow them to communicate. The implemented solution so far +is to reuse the underlying IPA multiplex of the IPA/OML link between OsmoBSC and +OsmoBTS to support forwarding PCUIF messages between the BSC and the PCU. + +OsmoBTS supporting the above mentioned protocol extension, must signal so during +BTS Get Attr during OML link set up (see feature `BTS_FEAT_ABIS_OSMO_PCU`). + +When OsmoBSC wants to start the ANR procedure, and hence ask the PCU to provide +measurement reports for the target BCCH frequencies, it does so by submitting a +`PCU_IF_MSG_ANR_REQ` message on the above described protocol. + +Upon receiving that messages (forwarded from the BTS), the PCU is then +responsible for the following tasks: + +Splitting the entire BCCH frequency list into smaller chunks:: +This is done for several reasons. Mainly, because an MS can only measure and +hence provide measurement reports for up to 32 frequencies at a time. +Furthermore, splitting the list into several MS is more fair since load is +spread among several subscribers. Finally, spreading the load also means quicker +results, with less noticeable operational affectation on the MS registered with +the cell. + +Selecting candidate MS to issue and provide the measurement reports for a chunk of frequencies:: +The process is different whether the MS is in Idle mode or in Active +(MAC-shared) mode. So far OsmoPCU only supports selecting and managing this +procedure on MS in Active mode (holding an active TBF). The exact procedure is +described bee, see <<anr_neigh_list>>. + +Report the measurements for each chunk of frequencies back to the BSC:: +This is done by means of sending back the recorded measurement reports obtained +from MS in a message of type `PCU_IF_MSG_ANR_CNF`, which will then be forwarded +by the BTS to the BSC. + +[[anr_neigh_list]] +==== Gathering Measurements (TBF active mode) + +The purpose of this section is to describe the techinque used by the PCU to be +able to retrieve measurements from an MS on the desired chunk of frequency list. + +In general, the MS can be instructed to send Packet Measurement Reports to the +network when in PDCH by means of one of these: + +* Broadcasting Network Control Order NC1 or NC2 in CCCH System Information. +* Setting Network Control Order to NC1 or NC2 when sending a _Packet Measurement + Order_ message to it. + +In general, the MS does the measurements on up to 32 BCCH frequencies from a +list called the _Neighbour List_, which is built up locally based on the +frequencies received in System Information 2, also called _BA(GPRS)_ or +_BA(list)_ (they both are the same since no PBCCH is used). + +However, when in ANR, we want to instruct each MS to issue measurements on a +different list of desired target frequencies, which is different from that of +the currently announced neighbors in the cell (SI2, see above). +The MS can be instructed to build up a different local _Neighbour List_ by using +the `NC_FREQUENCY_LIST` bits present in _Packet Measurement Order_ sent to it. + +The `NC_FREQUENCY_LIST` contains an array of frequencies to remove, and an array +of frequencies to add to the local _Neighbour List_ of the MS. Hence OsmoPCU +builds up the `NC_FREQUENCY_LIST` with additions or removals taking into +consideration the `BA(GPRS)` from SI2 and the target desired chunk of +frequencies, so that when applying the bits, the MS ends up having a local +_Neighbour List_ matching that of the chunk of frequencies we want to measure. + +This `NC_FREQUENCY_LIST` is sent to the MS using one or more instances of +_Packet Measurement Order_ messages (increasing `PMO_IND`), and also setting +`NETWORK_CONTROL_ORDER` to `NC1` in order to ask that MS to start sending +_Packet Measurement Reports_. + +Once the desired _Packet Measurement Reports_ are received, OsmoPCU then sends a +new _Packet Measurement Order_ message with an empty `NC_FREQUENCY_LIST` (which +resets the local _Neighbour List_ to `BA(GPRS)` according to 3GPP TS 44.040 sec +5.6.3.2), and sets `NETWORK_CONTROL_ORDER` to `RESET`. + + +=== Configuring ANR + +ANR is mostly configured globally for the entire BSC, and it is configured to be disabled by default: + +.Example: Default ANR configuration +---- +network + anr + scan-frequency 0 <1> + expiration-frequency 0 <2> + expiration-time 0 + rxlev-threshold 0 +---- +<1> Disable (value 0) sending of ANR Requests towards PCU of all cells +<2> Disable (value 0) expiration of dynamically found neighbors. + +Hence, one can see than using `scan frequency 0` alone effectively disables ANR +for all BTS, and in that case expiration related values doesn't matter because +no dynamic neighor is ever created. One can also quickly (1 minute delay) all +dynamic neighbors by diabling ANR (`scan-frequency 0`), then setting +`expiration-frequency 1` and `expiration-time 0`. + +On the other hand, a more typical configuration with ANR enabled could be: + +.Example: Default ANR configuration +---- +network + anr + scan-frequency 30 <1> + expiration-frequency 15 <2> + expiration-time 600 <3> + rxlev-threshold 35 <4> +---- +<1> An ANR request is scheduled for each cell every 30 minutes +<2> Run the expiration timer to drop dynamic neighbors every 10 minutes +<3> When the expiration timer runs, drop dynamic neighbors for which we didn't receive any measurement over last 10 hours (600 min). +<4> Don't register, as neighbors, cells with Measurement Reports containing RXLEV lesser than 35 (-85dBm). + +Finally, ANR can also be enabled or disabled specifically for each BTS. The +default configuration is to have it enabled by default, since anyway the default +global config doesn't activate ANR. Hence, once the user enables ANR globally, +the default is to enable it for all BTS under the BSC. + +.Example: Forbid use of ANR in a specific BTS +---- +network + bts 0 + anr disable +---- diff --git a/doc/manuals/osmobsc-usermanual.adoc b/doc/manuals/osmobsc-usermanual.adoc index c7589cc35..70113b7e6 100644 --- a/doc/manuals/osmobsc-usermanual.adoc +++ b/doc/manuals/osmobsc-usermanual.adoc @@ -34,6 +34,8 @@ include::{srcdir}/chapters/mscpool.adoc[] include::{srcdir}/chapters/smlc.adoc[] +include::{srcdir}/chapters/anr.adoc[] + include::./common/chapters/qos-dscp-pcp.adoc[] include::./common/chapters/counters-overview.adoc[] diff --git a/include/osmocom/bsc/abis_osmo.h b/include/osmocom/bsc/abis_osmo.h index 97871ace0..96800859d 100644 --- a/include/osmocom/bsc/abis_osmo.h +++ b/include/osmocom/bsc/abis_osmo.h @@ -31,3 +31,5 @@ struct gsm_bts; int abis_osmo_rcvmsg(struct msgb *msg); int abis_osmo_sendmsg(struct gsm_bts *bts, struct msgb *msg); + +int abis_osmo_pcu_tx_anr_req(struct gsm_bts *bts, const struct gsm48_cell_desc *cell_desc_li, unsigned int num_cells); diff --git a/include/osmocom/bsc/bts.h b/include/osmocom/bsc/bts.h index 2e88129dd..314719797 100644 --- a/include/osmocom/bsc/bts.h +++ b/include/osmocom/bsc/bts.h @@ -556,6 +556,8 @@ struct gsm_bts { /* Is Fast return to LTE allowed during Chan Release in this BTS? */ bool srvcc_fast_return_allowed; + + bool anr_enabled; /* Is ANR enabled for this BTS (VTY)? */ }; #define GSM_BTS_SI2Q(bts, i) (struct gsm48_system_information_type_2quater *)((bts)->si_buf[SYSINFO_TYPE_2quater][i]) @@ -670,6 +672,9 @@ int bts_gprs_mode_is_compat(struct gsm_bts *bts, enum bts_gprs_mode mode); #define BTS_STORE_UPTIME_INTERVAL 10 /* in seconds */ void bts_store_uptime(struct gsm_bts *bts); +int bts_anr_request(struct gsm_bts *bts, const struct gsm48_cell_desc *cell_desc_li, unsigned int num_cells); +void bts_anr_expiration(struct gsm_bts *bts, const struct timespec *expire_ts); + unsigned long long bts_uptime(const struct gsm_bts *bts); char *get_model_oml_status(const struct gsm_bts *bts); diff --git a/include/osmocom/bsc/gsm_04_08_rr.h b/include/osmocom/bsc/gsm_04_08_rr.h index 5ddee7fc3..ebba0f862 100644 --- a/include/osmocom/bsc/gsm_04_08_rr.h +++ b/include/osmocom/bsc/gsm_04_08_rr.h @@ -12,6 +12,7 @@ struct gsm_lchan; struct gsm_meas_rep; struct gsm_network; struct gsm_subscriber_connection; +struct gsm_bts; void gsm_net_update_ctype(struct gsm_network *network); enum gsm_chan_t get_ctype_by_chreq(struct gsm_network *network, uint8_t ra); @@ -43,6 +44,8 @@ struct msgb *gsm48_create_loc_upd_rej(uint8_t cause); struct msgb *gsm48_create_rr_status(uint8_t cause); int gsm48_tx_rr_status(struct gsm_subscriber_connection *conn, uint8_t cause); +void gsm48_cell_desc(struct gsm48_cell_desc *cd, const struct gsm_bts *bts); + #define GSM48_ALLOC_SIZE 2048 #define GSM48_ALLOC_HEADROOM 256 diff --git a/include/osmocom/bsc/gsm_data.h b/include/osmocom/bsc/gsm_data.h index b07345de0..fd9196770 100644 --- a/include/osmocom/bsc/gsm_data.h +++ b/include/osmocom/bsc/gsm_data.h @@ -1349,6 +1349,18 @@ struct gsm_network { struct osmo_nri_ranges *null_nri_ranges; struct smlc_config *smlc; + + struct { + unsigned int scan_frequency; /* frequency of request_timer, in seconds. 0 = disabled */ + unsigned int expiration_frequency; /* frequency of expiration_timer, in seconds. 0 = disabled */ + unsigned int expiration_time; /* maximum allowed time for a dynamic neigh without detection, in seconds. 0 = disabled */ + unsigned int rxlev_threshold; /* Minimum RXLEV an MS must provide for a neighboru to be considered valid */ + /* Timer to submit Measure Neighbours Request from time to time. */ + struct osmo_timer_list request_timer; + /* Timer to delete dynamic neighbours generated from ANR which are not recently detected. */ + struct osmo_timer_list expiration_timer; + + } anr; }; struct gsm_audio_support { diff --git a/include/osmocom/bsc/neighbor_ident.h b/include/osmocom/bsc/neighbor_ident.h index 0565d528c..6f097c305 100644 --- a/include/osmocom/bsc/neighbor_ident.h +++ b/include/osmocom/bsc/neighbor_ident.h @@ -42,7 +42,6 @@ enum neighbor_type { */ struct neighbor { struct llist_head entry; - enum neighbor_type type; union { uint8_t bts_nr; @@ -52,6 +51,8 @@ struct neighbor { struct cell_ab ab; } cell_id; }; + bool dynamic; /* Generated dynamically by means like ANR, not from config */ + struct timespec last_meas_detected; /* Last time it was detected by (pkt) meas report, triggered by ANR */ }; int resolve_local_neighbor(struct gsm_bts **local_neighbor_p, const struct gsm_bts *from_bts, diff --git a/include/osmocom/bsc/pcuif_proto.h b/include/osmocom/bsc/pcuif_proto.h index 3e6f6510e..2b62deebc 100644 --- a/include/osmocom/bsc/pcuif_proto.h +++ b/include/osmocom/bsc/pcuif_proto.h @@ -26,6 +26,10 @@ #define PCU_IF_MSG_TXT_IND 0x70 /* Text indication for BTS */ #define PCU_IF_MSG_CONTAINER 0x80 /* Transparent container message */ +/* msg_type coming from BSC (inside PCU_IF_MSG_CONTAINER) */ +#define PCU_IF_MSG_ANR_REQ 0x81 /* Automatic Neighbor Registration Request */ +#define PCU_IF_MSG_ANR_CNF 0x82 /* Automatic Neighbor Registration Confirmation (meas results) */ + /* sapi */ #define PCU_IF_SAPI_RACH 0x01 /* channel request on CCCH */ #define PCU_IF_SAPI_AGCH 0x02 /* assignment on AGCH */ @@ -226,6 +230,19 @@ struct gsm_pcu_if_container { uint8_t data[0]; } __attribute__ ((packed)); +/* Used inside container: */ +struct gsm_pcu_if_anr_req { + uint8_t num_cells; + uint16_t cell_list[96]; /* struct gsm48_cell_desc */ +} __attribute__ ((packed)); + +/* PCU confirms back with measurements of target cells */ +struct gsm_pcu_if_anr_cnf { + uint8_t num_cells; + uint16_t cell_list[32]; /* struct gsm48_cell_desc */ + uint8_t rxlev_list[32]; /* value 0xff: invalid */ +} __attribute__ ((packed)); + struct gsm_pcu_if { /* context based information */ uint8_t msg_type; /* message type */ diff --git a/include/osmocom/bsc/vty.h b/include/osmocom/bsc/vty.h index 30094fa35..249b79b40 100644 --- a/include/osmocom/bsc/vty.h +++ b/include/osmocom/bsc/vty.h @@ -14,6 +14,7 @@ struct buffer *vty_argv_to_buffer(int argc, const char *argv[], int base); enum bsc_vty_node { GSMNET_NODE = _LAST_OSMOVTY_NODE + 1, + ANR_NODE, BTS_NODE, TRX_NODE, TS_NODE, diff --git a/src/ipaccess/ipaccess-config.c b/src/ipaccess/ipaccess-config.c index 02501bdb5..5ffafa96d 100644 --- a/src/ipaccess/ipaccess-config.c +++ b/src/ipaccess/ipaccess-config.c @@ -1143,3 +1143,5 @@ int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type, const uint8_t *data int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type, const uint8_t *data, int len) { return 0; } int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type) { return 0; } +struct neighbor; +char *neighbor_to_str_c(void *ctx, const struct neighbor *n) { return NULL; } diff --git a/src/ipaccess/ipaccess-proxy.c b/src/ipaccess/ipaccess-proxy.c index d67603833..903a94ffb 100644 --- a/src/ipaccess/ipaccess-proxy.c +++ b/src/ipaccess/ipaccess-proxy.c @@ -1261,3 +1261,5 @@ int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type, const uint8_t *data int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type, const uint8_t *data, int len) { return 0; } int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type) { return 0; } +struct neighbor; +char *neighbor_to_str_c(void *ctx, const struct neighbor *n) { return NULL; } diff --git a/src/osmo-bsc/abis_osmo.c b/src/osmo-bsc/abis_osmo.c index 39caac6df..cb79c15a2 100644 --- a/src/osmo-bsc/abis_osmo.c +++ b/src/osmo-bsc/abis_osmo.c @@ -32,6 +32,7 @@ #include <osmocom/bsc/debug.h> #include <osmocom/bsc/bts.h> #include <osmocom/bsc/pcuif_proto.h> +#include <osmocom/bsc/neighbor_ident.h> #define OM_HEADROOM_SIZE 128 @@ -40,7 +41,6 @@ /////////////////////////////////////// #define PCUIF_HDR_SIZE ( sizeof(struct gsm_pcu_if) - sizeof(((struct gsm_pcu_if *)0)->u) ) -#if 0 static struct msgb *abis_osmo_pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr, size_t extra_size) { struct msgb *msg; @@ -62,7 +62,100 @@ static int abis_osmo_pcu_sendmsg(struct gsm_bts *bts, struct msgb *msg) ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_PCU); return abis_osmo_sendmsg(bts, msg); } -#endif + +int abis_osmo_pcu_tx_anr_req(struct gsm_bts *bts, const struct gsm48_cell_desc *cell_desc_li, unsigned int num_cells) +{ + struct msgb *msg = abis_osmo_pcu_msgb_alloc(PCU_IF_MSG_CONTAINER, bts->bts_nr, sizeof(struct gsm_pcu_if_anr_req)); + struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msgb_data(msg); + struct gsm_pcu_if_anr_req *anr_req = (struct gsm_pcu_if_anr_req *)&pcu_prim->u.container.data[0]; + + msgb_put(msg, sizeof(pcu_prim->u.container) + sizeof(struct gsm_pcu_if_anr_req)); + pcu_prim->u.container.msg_type = PCU_IF_MSG_ANR_REQ; + osmo_store16be(sizeof(struct gsm_pcu_if_anr_req), &pcu_prim->u.container.length); + + anr_req->num_cells = num_cells; + OSMO_ASSERT(num_cells <= ARRAY_SIZE(anr_req->cell_list)); + if (num_cells) + memcpy(anr_req->cell_list, cell_desc_li, sizeof(*cell_desc_li) * num_cells); + + return abis_osmo_pcu_sendmsg(bts, msg); +} + +#define ANR_NEIGH_RXLEV_INVALID 0xff +static int rcvmsg_pcu_anr_cnf(struct gsm_bts *bts, const struct gsm_pcu_if_anr_cnf* anr_cnf) +{ + unsigned int i; + struct timespec now; + struct gsm_bts *neigh_bts; + bool neigh_bts_found; + struct neighbor *n; + + osmo_clock_gettime(CLOCK_MONOTONIC, &now); + LOGP(DNM, LOGL_INFO, "(bts=%d) Rx ANR Confirmation (%u cells)\n", + bts->nr, anr_cnf->num_cells); + + for (i = 0; i < anr_cnf->num_cells; i++) { + const struct gsm48_cell_desc *cell_desc = (const struct gsm48_cell_desc *)&anr_cnf->cell_list[i]; + uint16_t arfcn = (cell_desc->arfcn_hi << 8) | cell_desc->arfcn_lo; + uint8_t bsic = (cell_desc->ncc << 3) | cell_desc->bcc; + + if (anr_cnf->rxlev_list[i] == ANR_NEIGH_RXLEV_INVALID) { + LOGP(DNM, LOGL_INFO, "(bts=%d) ANR: ARFCN=%u BSIC=%u is NOT a neighbor (not found)\n", + bts->nr, arfcn, bsic); + continue; + } + if (anr_cnf->rxlev_list[i] < bts->network->anr.rxlev_threshold) { + LOGP(DNM, LOGL_INFO, + "(bts=%d) ANR: ARFCN=%u BSIC=%u RXLEV=%u (%d dBm) is NOT a neighbor (< rxlev %u)\n", + bts->nr, arfcn, bsic, anr_cnf->rxlev_list[i], anr_cnf->rxlev_list[i] - 110, + bts->network->anr.rxlev_threshold); + continue; + } + LOGP(DNM, LOGL_INFO, "(bts=%d) ANR: ARFCN=%u BSIC=%u RXLEV=%u (%d dBm) is a neighbor\n", + bts->nr, arfcn, bsic, anr_cnf->rxlev_list[i], anr_cnf->rxlev_list[i] - 110); + + /* Find BTS owning ARFCN+BSIC: */ + neigh_bts_found = false; + llist_for_each_entry(neigh_bts, &bts->network->bts_list, list) { + if (neigh_bts->c0->arfcn != arfcn || neigh_bts->bsic != bsic) + continue; + neigh_bts_found = true; + break; + } + if (!neigh_bts_found) { + LOGP(DNM, LOGL_NOTICE, "(bts=%d) ANR: ARFCN=%u BSIC=%u RXLEV=%u (%d dBm) matches no BTS configured in BSC!\n", + bts->nr, arfcn, bsic, anr_cnf->rxlev_list[i], anr_cnf->rxlev_list[i] - 110); + continue; + } + + /* Try to find existing neighbour and update it. */ + neigh_bts_found = false; + llist_for_each_entry(n, &bts->neighbors, entry) { + if (n->type != NEIGHBOR_TYPE_BTS_NR) + continue; + if (n->bts_nr != neigh_bts->nr) + continue; + neigh_bts_found = true; + n->last_meas_detected = now; + break; + } + /* If the neighbour didn't exist yet, create it and add it to the list */ + if (!neigh_bts_found) { + LOGP(DNM, LOGL_NOTICE, "(bts=%d) ANR: Added new dynamic neighbor BTS%d\n", + bts->nr, neigh_bts->nr); + n = talloc(bts, struct neighbor); + *n = (struct neighbor){ + .type = NEIGHBOR_TYPE_BTS_NR, + .bts_nr = neigh_bts->nr, + .dynamic = true, + .last_meas_detected = now, + }; + llist_add_tail(&n->entry, &bts->neighbors); + /* TODO: force re-creation of SI? */ + } + } + return 0; +} static int rcvmsg_pcu_container(struct gsm_bts *bts, struct gsm_pcu_if_container *container, size_t container_len) { @@ -79,6 +172,13 @@ static int rcvmsg_pcu_container(struct gsm_bts *bts, struct gsm_pcu_if_container bts->nr, container->msg_type); switch (container->msg_type) { + case PCU_IF_MSG_ANR_CNF: + if (data_length < sizeof(struct gsm_pcu_if_anr_cnf)) { + LOGP(DNM, LOGL_ERROR, "ABIS_OSMO_PCU CONTAINER ANR_CNF message too short\n"); + return -EINVAL; + } + rc = rcvmsg_pcu_anr_cnf(bts, (struct gsm_pcu_if_anr_cnf*)&container->data); + break; default: LOGP(DNM, LOGL_NOTICE, "(bts=%d) Rx ABIS_OSMO_PCU unexpected msg type (%u) inside container!\n", bts->nr, container->msg_type); diff --git a/src/osmo-bsc/bsc_init.c b/src/osmo-bsc/bsc_init.c index 24596f4eb..e4bfe0639 100644 --- a/src/osmo-bsc/bsc_init.c +++ b/src/osmo-bsc/bsc_init.c @@ -97,6 +97,51 @@ static void bsc_store_bts_uptime(void *data) osmo_timer_schedule(&net->bts_store_uptime_timer, BTS_STORE_UPTIME_INTERVAL, 0); } +static void bsc_anr_request(void *data) +{ + struct gsm_network *net = data; + struct gsm_bts *bts; + struct gsm48_cell_desc cell_desc[256]; + unsigned int num_cells = 0; + + /* Build list of ARFCN+BSIC: */ + llist_for_each_entry(bts, &net->bts_list, list) { + if (num_cells == ARRAY_SIZE(cell_desc)) { + LOGP(DNM, LOGL_ERROR, + "ANR Req: Unable to build list larger than %zu elements", + ARRAY_SIZE(cell_desc)); + break; + } + gsm48_cell_desc(&cell_desc[num_cells], bts); + num_cells++; + } + + llist_for_each_entry(bts, &net->bts_list, list) + bts_anr_request(bts, cell_desc, num_cells); + + /* Keep this timer ticking. */ + osmo_timer_schedule(&net->anr.request_timer, net->anr.scan_frequency, 0); +} + +static void bsc_anr_expiration(void *data) +{ + struct gsm_network *net = data; + struct gsm_bts *bts; + struct timespec now, res; + struct timespec max_age = { + .tv_sec = net->anr.expiration_time, + .tv_nsec = 0, + }; + osmo_clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&now, &max_age, &res); + + llist_for_each_entry(bts, &net->bts_list, list) + bts_anr_expiration(bts, &res); + + /* Keep this timer ticking. */ + osmo_timer_schedule(&net->anr.expiration_timer, net->anr.expiration_frequency, 0); +} + static struct gsm_network *bsc_network_init(void *ctx) { struct gsm_network *net = gsm_network_init(ctx); @@ -144,6 +189,10 @@ static struct gsm_network *bsc_network_init(void *ctx) osmo_timer_setup(&net->bts_store_uptime_timer, bsc_store_bts_uptime, net); osmo_timer_schedule(&net->bts_store_uptime_timer, BTS_STORE_UPTIME_INTERVAL, 0); + /* Init ANR tracking timer. */ + osmo_timer_setup(&net->anr.request_timer, bsc_anr_request, net); + osmo_timer_setup(&net->anr.expiration_timer, bsc_anr_expiration, net); + net->cbc->net = net; net->cbc->mode = BSC_CBC_LINK_MODE_DISABLED; net->cbc->server.local_addr = bsc_cbc_default_server_local_addr; diff --git a/src/osmo-bsc/bsc_vty.c b/src/osmo-bsc/bsc_vty.c index 070b66081..6ed31872a 100644 --- a/src/osmo-bsc/bsc_vty.c +++ b/src/osmo-bsc/bsc_vty.c @@ -179,6 +179,12 @@ static struct cmd_node ts_node = { 1, }; +static struct cmd_node anr_node = { + ANR_NODE, + "%s(config-net-anr)# ", + 1, +}; + static struct gsm_network *vty_global_gsm_network = NULL; struct gsm_network *gsmnet_from_vty(struct vty *v) @@ -1294,6 +1300,9 @@ static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts) if (!bts->srvcc_fast_return_allowed) vty_out(vty, " srvcc fast-return forbid%s", VTY_NEWLINE); + vty_out(vty, " anr %s%s", + bts->anr_enabled ? "enable" : "disable", VTY_NEWLINE); + /* BS/MS Power Control parameters */ config_write_power_ctrl(vty, 2, &bts->bs_power_ctrl); config_write_power_ctrl(vty, 2, &bts->ms_power_ctrl); @@ -1312,6 +1321,18 @@ static int config_write_bts(struct vty *v) return CMD_SUCCESS; } +static int config_write_anr(struct vty *vty) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + vty_out(vty, " anr%s", VTY_NEWLINE); + vty_out(vty, " scan-frequency %u%s", gsmnet->anr.scan_frequency / 60, VTY_NEWLINE); + vty_out(vty, " expiration-frequency %u%s", gsmnet->anr.scan_frequency / 60, VTY_NEWLINE); + vty_out(vty, " expiration-time %u%s", gsmnet->anr.expiration_time / 60, VTY_NEWLINE); + vty_out(vty, " rxlev-threshold %u%s", gsmnet->anr.rxlev_threshold, VTY_NEWLINE); + + return CMD_SUCCESS; +} + static int config_write_net(struct vty *vty) { struct gsm_network *gsmnet = gsmnet_from_vty(vty); @@ -2374,6 +2395,73 @@ DEFUN_ATTR(cfg_net_nri_null_del, return CMD_SUCCESS; } +/* ANR configuration */ +DEFUN_ATTR(cfg_anr, + cfg_anr_cmd, + "anr", + "Configure ANR (Automatic Neighbor Registration) parameters\n", + CMD_ATTR_IMMEDIATE) +{ + vty->node = ANR_NODE; + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_anr_scan_frequency, + cfg_anr_scan_frequency_cmd, + "scan-frequency <0-100000>", + "Initiate ANR scan at given frequency\n" + "Time in minutes (0 = disabled, default)", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->anr.scan_frequency = atoi(argv[0]) * 60; + if (gsmnet->anr.scan_frequency) + osmo_timer_schedule(&gsmnet->anr.request_timer, gsmnet->anr.scan_frequency, 0); + else + osmo_timer_del(&gsmnet->anr.request_timer); + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_anr_expiration_frequency, + cfg_anr_expiration_frequency_cmd, + "expiration-frequency <0-100000>", + "Frequency at which to trigger expiration lookup timer\n" + "Frequency in minutes (0 = default)", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->anr.expiration_frequency = atoi(argv[0]) * 60; + if (gsmnet->anr.expiration_frequency) + osmo_timer_schedule(&gsmnet->anr.expiration_timer, gsmnet->anr.expiration_frequency, 0); + else + osmo_timer_del(&gsmnet->anr.expiration_timer); + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_anr_expiration_time, + cfg_anr_expiration_time_cmd, + "expiration-time <0-100000>", + "Expire dynamic neighbors not detected since a given amount of time\n" + "Time to expire a dynamic neighbor, in minutes (0 = disabled, default)", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->anr.expiration_time = atoi(argv[0]) * 60; + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_anr_rxlev_threshold, + cfg_anr_rxlev_threshold_cmd, + "rxlev-threshold <0-255>", + "Accept only dynamic neighbors with a minimum RXLEV provided by MS\n" + "RXLEV (0 = default)", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->anr.rxlev_threshold = atoi(argv[0]); + return CMD_SUCCESS; +} + /* per-BTS configuration */ DEFUN_ATTR(cfg_bts, cfg_bts_cmd, @@ -5020,6 +5108,21 @@ DEFUN_ATTR(cfg_bts_srvcc_fast_return, cfg_bts_srvcc_fast_return_cmd, return CMD_SUCCESS; } +DEFUN_ATTR(cfg_bts_anr_enable_disable, + cfg_bts_anr_enable_disable_cmd, + "anr (enable|disable)", + "Allow or forbid ANR (Automatic Neighbor Registration) on this BTS\n" + "Allow ANR\n" "Forbid ANR\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + if (strcmp(argv[0], "enable") == 0) + bts->anr_enabled = true; + else + bts->anr_enabled = false; + return CMD_SUCCESS; +} + #define BS_POWER_CONTROL_CMD \ "bs-power-control" #define MS_POWER_CONTROL_CMD \ @@ -8033,6 +8136,13 @@ int bsc_vty_init(struct gsm_network *network) install_element(GSMNET_NODE, &cfg_net_nri_null_add_cmd); install_element(GSMNET_NODE, &cfg_net_nri_null_del_cmd); + install_element(GSMNET_NODE, &cfg_anr_cmd); + install_node(&anr_node, config_write_anr); + install_element(ANR_NODE, &cfg_anr_scan_frequency_cmd); + install_element(ANR_NODE, &cfg_anr_expiration_frequency_cmd); + install_element(ANR_NODE, &cfg_anr_expiration_time_cmd); + install_element(ANR_NODE, &cfg_anr_rxlev_threshold_cmd); + install_element(GSMNET_NODE, &cfg_bts_cmd); install_node(&bts_node, config_write_bts); install_element(BTS_NODE, &cfg_bts_type_cmd); @@ -8169,6 +8279,7 @@ int bsc_vty_init(struct gsm_network *network) install_element(BTS_NODE, &cfg_bts_interf_meas_avg_period_cmd); install_element(BTS_NODE, &cfg_bts_interf_meas_level_bounds_cmd); install_element(BTS_NODE, &cfg_bts_srvcc_fast_return_cmd); + install_element(BTS_NODE, &cfg_bts_anr_enable_disable_cmd); neighbor_ident_vty_init(); /* See also handover commands added on bts level from handover_vty.c */ diff --git a/src/osmo-bsc/bts.c b/src/osmo-bsc/bts.c index cf3a6b8cd..d320b85b5 100644 --- a/src/osmo-bsc/bts.c +++ b/src/osmo-bsc/bts.c @@ -24,6 +24,8 @@ #include <osmocom/bsc/bts.h> #include <osmocom/bsc/debug.h> #include <osmocom/bsc/nm_common_fsm.h> +#include <osmocom/bsc/abis_osmo.h> +#include <osmocom/bsc/neighbor_ident.h> const struct value_string bts_attribute_names[] = { OSMO_VALUE_STRING(BTS_TYPE_VARIANT), @@ -364,6 +366,8 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, struct gsm_bts_sm *bts_sm /* SRVCC is enabled by default */ bts->srvcc_fast_return_allowed = true; + /* Enabled ANR by default for this BTS, since default BSC config disables it globally anyway */ + bts->anr_enabled = true; return bts; } @@ -583,6 +587,41 @@ unsigned long long bts_uptime(const struct gsm_bts *bts) return difftime(tp.tv_sec, bts->uptime); } +int bts_anr_request(struct gsm_bts *bts, const struct gsm48_cell_desc *cell_desc_li, unsigned int num_cells) +{ + if (!bts->anr_enabled) { + LOGP(DNM, LOGL_DEBUG, "BTS%u: ANR Req: ANR disabled by config\n", bts->nr); + return 0; + } + if (!bts->osmo_link) { + LOGP(DNM, LOGL_INFO, "BTS%u: ANR Req: OSMO link is down\n", bts->nr); + return 0; + } + if (!osmo_bts_has_feature(&bts->features, BTS_FEAT_ABIS_OSMO_PCU)) { + LOGP(DNM, LOGL_INFO, "BTS%u: ANR Req: Abis OSMO_PCU proto not supported\n", bts->nr); + return 0; + } + LOGP(DNM, LOGL_INFO, "BTS%u: ANR Req (%u cells)\n", bts->nr, num_cells); + return abis_osmo_pcu_tx_anr_req(bts, cell_desc_li, num_cells); +} + +void bts_anr_expiration(struct gsm_bts *bts, const struct timespec *expire_ts) +{ + struct neighbor *n, *n2; + llist_for_each_entry_safe(n, n2, &bts->neighbors, entry) { + if (!n->dynamic) + continue; + if (timespeccmp(&n->last_meas_detected, expire_ts, >=)) + continue; + LOGP(DNM, LOGL_INFO, "BTS%u: ANR Expire: Remove dynamic neighbor %s\n", + bts->nr, neighbor_to_str_c(OTC_SELECT, n)); + llist_del(&n->entry); + talloc_free(n); + /* TODO: force SI list update ?*/ + break; + } +} + char *get_model_oml_status(const struct gsm_bts *bts) { if (bts->model->oml_status) diff --git a/src/osmo-bsc/gsm_04_08_rr.c b/src/osmo-bsc/gsm_04_08_rr.c index 16fe99930..9f9f709dd 100644 --- a/src/osmo-bsc/gsm_04_08_rr.c +++ b/src/osmo-bsc/gsm_04_08_rr.c @@ -387,7 +387,7 @@ int gsm48_send_rr_ciph_mode(struct gsm_lchan *lchan, int want_imeisv) return rsl_encryption_cmd(msg); } -static void gsm48_cell_desc(struct gsm48_cell_desc *cd, +void gsm48_cell_desc(struct gsm48_cell_desc *cd, const struct gsm_bts *bts) { cd->ncc = (bts->bsic >> 3 & 0x7); diff --git a/src/osmo-bsc/neighbor_ident_vty.c b/src/osmo-bsc/neighbor_ident_vty.c index b9160ec67..dc783a527 100644 --- a/src/osmo-bsc/neighbor_ident_vty.c +++ b/src/osmo-bsc/neighbor_ident_vty.c @@ -534,6 +534,11 @@ void neighbor_ident_vty_write_bts(struct vty *vty, const char *indent, struct gs struct neighbor *n; llist_for_each_entry(n, &bts->neighbors, entry) { + + /* Don't store dynamically found neighbours */ + if (n->dynamic) + continue; + switch (n->type) { case NEIGHBOR_TYPE_BTS_NR: vty_out(vty, "%sneighbor bts %u%s", indent, n->bts_nr, VTY_NEWLINE); @@ -601,7 +606,8 @@ DEFUN(show_bts_neighbor, show_bts_neighbor_cmd, vty_out(vty, " local BTS %u lac-ci %u %u%s", local_neighbor->nr, local_neighbor->location_area_code, - local_neighbor->cell_identity, VTY_NEWLINE); + local_neighbor->cell_identity, + VTY_NEWLINE); } if (remote_neighbors.id_list_len) { diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c index 2a6c50183..5514229cf 100644 --- a/src/osmo-bsc/osmo_bsc_main.c +++ b/src/osmo-bsc/osmo_bsc_main.c @@ -633,6 +633,9 @@ static int bsc_vty_go_parent(struct vty *vty) vty->node = CONFIG_NODE; vty->index = NULL; break; + case ANR_NODE: + vty->node = GSMNET_NODE; + break; default: osmo_ss7_vty_go_parent(vty); } diff --git a/src/utils/bs11_config.c b/src/utils/bs11_config.c index c279179d5..50e2551f7 100644 --- a/src/utils/bs11_config.c +++ b/src/utils/bs11_config.c @@ -995,3 +995,5 @@ int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type, const uint8_t *data int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type, const uint8_t *data, int len) { return 0; } int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type) { return 0; } +struct neighbor; +char *neighbor_to_str_c(void *ctx, const struct neighbor *n) { return NULL; } diff --git a/src/utils/meas_json.c b/src/utils/meas_json.c index 82b1e570b..50a1928ef 100644 --- a/src/utils/meas_json.c +++ b/src/utils/meas_json.c @@ -210,3 +210,5 @@ int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type, const uint8_t *data int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type, const uint8_t *data, int len) { return 0; } int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type) { return 0; } +struct neighbor; +char *neighbor_to_str_c(void *ctx, const struct neighbor *n) { return NULL; } diff --git a/tests/abis/abis_test.c b/tests/abis/abis_test.c index 9d26eddcd..a027551dc 100644 --- a/tests/abis/abis_test.c +++ b/tests/abis/abis_test.c @@ -196,3 +196,5 @@ int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type, const uint8_t *data int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type, const uint8_t *data, int len) { return 0; } int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type) { return 0; } +struct neighbor; +char *neighbor_to_str_c(void *ctx, const struct neighbor *n) { return NULL; } diff --git a/tests/acc/acc_test.c b/tests/acc/acc_test.c index 759ff139f..5e8b5726d 100644 --- a/tests/acc/acc_test.c +++ b/tests/acc/acc_test.c @@ -557,3 +557,5 @@ int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type, const uint8_t *data int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type, const uint8_t *data, int len) { return 0; } int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type) { return 0; } +struct neighbor; +char *neighbor_to_str_c(void *ctx, const struct neighbor *n) { return NULL; } diff --git a/tests/nanobts_omlattr/nanobts_omlattr_test.c b/tests/nanobts_omlattr/nanobts_omlattr_test.c index 73ba86950..069393a3e 100644 --- a/tests/nanobts_omlattr/nanobts_omlattr_test.c +++ b/tests/nanobts_omlattr/nanobts_omlattr_test.c @@ -345,3 +345,5 @@ int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type, const uint8_t *data int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type, const uint8_t *data, int len) { return 0; } int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type) { return 0; } +struct neighbor; +char *neighbor_to_str_c(void *ctx, const struct neighbor *n) { return NULL; } |