Cell Broadcast: CBSP and CBCH scheduling support
This adds code to handle CBSP (Cell Broadcast Service Protocol) from the CBC (Cell Broadcast Centre), as well as BSC-internal data structures for scheduling the various SMSCB on the CBCH of each BTS. There are currently one known shortcoming in the code: We don't yet verify if keepalives are received within repetition period. Change-Id: Ia0a0de862a104d0f447a5d6e56c7c83981b825c7
This commit is contained in:
parent
9508e2232f
commit
d41b7c7f83
|
@ -0,0 +1,82 @@
|
|||
[[smscb]]
|
||||
== SMSCB (Cell Broadcast)
|
||||
|
||||
OsmoBSC supports SMS Cell Broadcast (SMSCB) services (CBS). This
|
||||
includes the CBSP protocol to interact with a CBC (Cell Broadcast
|
||||
Centre) such as OsmoCBC, as well as the scheduling of SMSCB messages on
|
||||
both the BASIC and EXTENDED CBCH and transmission of related RSL
|
||||
messages to the attached BTS.
|
||||
|
||||
More high-level information can be found at
|
||||
https://en.wikipedia.org/wiki/Cell_Broadcast and the related
|
||||
specification is <<3gpp-ts-23-041>>.
|
||||
|
||||
In order to use SMSCB with OsmoBSC, you will need to
|
||||
|
||||
* Configure the CBSP server and/or client
|
||||
* Use a channel combination including a CBCH on the BTSs
|
||||
|
||||
=== Enabling a CBCH channel combination
|
||||
|
||||
On the Um interface, SMSCB are transmitted via the CBCH (Cell Broadcast
|
||||
Channel). The CBCH is a separate downlink-only logical channel which
|
||||
must be activated on any of the BTSs requiring CBSP support.
|
||||
|
||||
The channel combination is configured in the `timeslot` node of each TRX.
|
||||
|
||||
The two `phys_chan_config` supporting CBCH are `CCCH+SDCCH4+CBCH` and
|
||||
`SDCCH/8+CBCH`. Please note that the CBCH steals one of the SDCCH, so
|
||||
a SDCCH/4 will only have three remaining SDCCH, and a SDCCH/8 will
|
||||
have only seven remaining SDCCH.
|
||||
|
||||
=== Configuring the CBSP connection
|
||||
|
||||
CBSP is the protocol between BSC and CBC. It operates over TCP.
|
||||
|
||||
According to 3GPP TS 48.049, a BSC typically operates as a TCP server,
|
||||
and the CBC connects as TCP client. This would require the CBC to have
|
||||
out-of-band knowledge of all the BSCs in the network (and their IP
|
||||
addresses).
|
||||
|
||||
In order to comply with the specifications, OsmoBSC supports this mode
|
||||
of operation as CBSP TCP server. However, to make network operation and
|
||||
configuration more simple, it also can operate in TCP client mode,
|
||||
connecting to the CBC. This way the all the BSCs need to know is the CBC IP
|
||||
address, but not vice-versa.
|
||||
|
||||
The BSC can operate both CBSP TCP server and CBSP TCP client mode in
|
||||
parallel.
|
||||
|
||||
The CBC related configuration of OsmoBSC can be found in the `cbc` configuration
|
||||
node of the VTY interface.
|
||||
|
||||
.Example: Configure CBSP TCP client to connect to CBC at 1.2.3.4:48049
|
||||
----
|
||||
OsmoBSC> enable
|
||||
OsmoBSC# configure terminal
|
||||
OsmoBSC(config)# cbc
|
||||
OsmoBSC(config-cbc)# remote-ip 1.2.3.4
|
||||
OsmoBSC(config-cbc)# remote-port 48049
|
||||
OsmoBSC(config-cbc)# end
|
||||
----
|
||||
|
||||
.Example: Disable CBSP TCP client
|
||||
----
|
||||
OsmoBSC> enable
|
||||
OsmoBSC# configure terminal
|
||||
OsmoBSC(config)# cbc
|
||||
OsmoBSC(config-cbc)# no remote-ip
|
||||
OsmoBSC(config-cbc)# end
|
||||
----
|
||||
|
||||
.Example: Configure CBSP TCP server to listen for CBC at 127.0.0.2:9999
|
||||
----
|
||||
OsmoBSC> enable
|
||||
OsmoBSC# configure terminal
|
||||
OsmoBSC(config)# cbc
|
||||
OsmoBSC(config-cbc)# listen-ip 127.0.0.2
|
||||
OsmoBSC(config-cbc)# listen-port 9999
|
||||
OsmoBSC(config-cbc)# end
|
||||
----
|
||||
|
||||
For more details on the available configuration commands, please check the OsmoBSC VTY Reference.
|
|
@ -24,6 +24,8 @@ include::./common/chapters/bsc.adoc[]
|
|||
|
||||
include::{srcdir}/chapters/handover.adoc[]
|
||||
|
||||
include::{srcdir}/chapters/smscb.adoc[]
|
||||
|
||||
include::./common/chapters/counters-overview.adoc[]
|
||||
|
||||
include::{srcdir}/chapters/counters.adoc[]
|
||||
|
@ -34,8 +36,6 @@ include::./common/chapters/control_if.adoc[]
|
|||
|
||||
include::{srcdir}/chapters/control.adoc[]
|
||||
|
||||
include::./common/chapters/cell-broadcast.adoc[]
|
||||
|
||||
include::{srcdir}/chapters/osmux_bsc.adoc[]
|
||||
|
||||
include::./common/chapters/port_numbers.adoc[]
|
||||
|
|
|
@ -320,7 +320,7 @@
|
|||
<param name='MASK' doc='List of logging categories to log, e.g. 'abc:mno:xyz'. Available log categories depend on the specific application, refer to the 'logging level' command. Optionally add individual log levels like 'abc,1:mno,3:xyz,5', where the level numbers are LOGL_DEBUG=1 LOGL_INFO=3 LOGL_NOTICE=5 LOGL_ERROR=7 LOGL_FATAL=8' />
|
||||
</params>
|
||||
</command>
|
||||
<command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
|
||||
<command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|cbs|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
|
||||
<params>
|
||||
<param name='logging' doc='Configure logging' />
|
||||
<param name='level' doc='Set the log level for a specified category' />
|
||||
|
@ -343,6 +343,7 @@
|
|||
<param name='chan' doc='lchan FSM' />
|
||||
<param name='ts' doc='timeslot FSM' />
|
||||
<param name='as' doc='assignment FSM' />
|
||||
<param name='cbs' doc='Cell Broadcast System' />
|
||||
<param name='lglobal' doc='Library-internal global log family' />
|
||||
<param name='llapd' doc='LAPD in libosmogsm' />
|
||||
<param name='linp' doc='A-bis Intput Subsystem' />
|
||||
|
@ -514,6 +515,22 @@
|
|||
<param name='all' doc='Display a list of all FSM instances of all finite state machine' />
|
||||
</params>
|
||||
</command>
|
||||
<command id='show cbc'>
|
||||
<params>
|
||||
<param name='show' doc='Show running system information' />
|
||||
<param name='cbc' doc='Display state of CBC / CBSP' />
|
||||
</params>
|
||||
</command>
|
||||
<command id='show bts <0-255> smscb [(basic|extended)]'>
|
||||
<params>
|
||||
<param name='show' doc='Show running system information' />
|
||||
<param name='bts' doc='Display information about a BTS' />
|
||||
<param name='<0-255>' doc='BTS number' />
|
||||
<param name='smscb' doc='SMS Cell Broadcast State' />
|
||||
<param name='[basic]' doc='Show only information related to CBCH BASIC' />
|
||||
<param name='[extended]' doc='Show only information related to CBCH EXTENDED' />
|
||||
</params>
|
||||
</command>
|
||||
<command id='show statistics'>
|
||||
<params>
|
||||
<param name='show' doc='Show running system information' />
|
||||
|
@ -971,7 +988,7 @@
|
|||
<param name='MASK' doc='List of logging categories to log, e.g. 'abc:mno:xyz'. Available log categories depend on the specific application, refer to the 'logging level' command. Optionally add individual log levels like 'abc,1:mno,3:xyz,5', where the level numbers are LOGL_DEBUG=1 LOGL_INFO=3 LOGL_NOTICE=5 LOGL_ERROR=7 LOGL_FATAL=8' />
|
||||
</params>
|
||||
</command>
|
||||
<command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
|
||||
<command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|cbs|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
|
||||
<params>
|
||||
<param name='logging' doc='Configure logging' />
|
||||
<param name='level' doc='Set the log level for a specified category' />
|
||||
|
@ -994,6 +1011,7 @@
|
|||
<param name='chan' doc='lchan FSM' />
|
||||
<param name='ts' doc='timeslot FSM' />
|
||||
<param name='as' doc='assignment FSM' />
|
||||
<param name='cbs' doc='Cell Broadcast System' />
|
||||
<param name='lglobal' doc='Library-internal global log family' />
|
||||
<param name='llapd' doc='LAPD in libosmogsm' />
|
||||
<param name='linp' doc='A-bis Intput Subsystem' />
|
||||
|
@ -1356,6 +1374,16 @@
|
|||
<param name='all' doc='Display a list of all FSM instances of all finite state machine' />
|
||||
</params>
|
||||
</command>
|
||||
<command id='show bts <0-255> smscb [(basic|extended)]'>
|
||||
<params>
|
||||
<param name='show' doc='Show running system information' />
|
||||
<param name='bts' doc='Display information about a BTS' />
|
||||
<param name='<0-255>' doc='BTS number' />
|
||||
<param name='smscb' doc='SMS Cell Broadcast State' />
|
||||
<param name='[basic]' doc='Show only information related to CBCH BASIC' />
|
||||
<param name='[extended]' doc='Show only information related to CBCH EXTENDED' />
|
||||
</params>
|
||||
</command>
|
||||
<command id='show statistics'>
|
||||
<params>
|
||||
<param name='show' doc='Show running system information' />
|
||||
|
@ -1735,6 +1763,11 @@
|
|||
<param name='e1_input' doc='Configure E1/T1/J1 TDM input' />
|
||||
</params>
|
||||
</command>
|
||||
<command id='cbc'>
|
||||
<params>
|
||||
<param name='cbc' doc='Configure CBSP Link to Cell Broadcast Centre' />
|
||||
</params>
|
||||
</command>
|
||||
<command id='msc [<0-1000>]'>
|
||||
<params>
|
||||
<param name='msc' doc='Configure MSC details' />
|
||||
|
@ -1833,7 +1866,7 @@
|
|||
<param name='[last]' doc='Log source file info at the end of a log line. If omitted, log source file info just before the log text.' />
|
||||
</params>
|
||||
</command>
|
||||
<command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
|
||||
<command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|cbs|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
|
||||
<params>
|
||||
<param name='logging' doc='Configure logging' />
|
||||
<param name='level' doc='Set the log level for a specified category' />
|
||||
|
@ -1856,6 +1889,7 @@
|
|||
<param name='chan' doc='lchan FSM' />
|
||||
<param name='ts' doc='timeslot FSM' />
|
||||
<param name='as' doc='assignment FSM' />
|
||||
<param name='cbs' doc='Cell Broadcast System' />
|
||||
<param name='lglobal' doc='Library-internal global log family' />
|
||||
<param name='llapd' doc='LAPD in libosmogsm' />
|
||||
<param name='linp' doc='A-bis Intput Subsystem' />
|
||||
|
@ -4930,4 +4964,43 @@
|
|||
</params>
|
||||
</command>
|
||||
</node>
|
||||
<node id='config-cbc'>
|
||||
<name>config-cbc</name>
|
||||
<command id='remote-ip A.B.C.D'>
|
||||
<params>
|
||||
<param name='remote-ip' doc='IP Address of the Cell Broadcast Centre' />
|
||||
<param name='A.B.C.D' doc='IP Address of the Cell Broadcast Centre' />
|
||||
</params>
|
||||
</command>
|
||||
<command id='no remote-ip'>
|
||||
<params>
|
||||
<param name='no' doc='Negate a command or set its defaults' />
|
||||
<param name='remote-ip' doc='Remove IP address of CBC; disables outbound CBSP connections' />
|
||||
</params>
|
||||
</command>
|
||||
<command id='remote-port <1-65535>'>
|
||||
<params>
|
||||
<param name='remote-port' doc='TCP Port number of the Cell Broadcast Centre (Default: 48049)' />
|
||||
<param name='<1-65535>' doc='TCP Port number of the Cell Broadcast Centre (Default: 48049)' />
|
||||
</params>
|
||||
</command>
|
||||
<command id='listen-port <1-65535>'>
|
||||
<params>
|
||||
<param name='listen-port' doc='Local TCP port at which BSC listens for incoming CBSP connections from CBC' />
|
||||
<param name='<1-65535>' doc='Local TCP port at which BSC listens for incoming CBSP connections from CBC' />
|
||||
</params>
|
||||
</command>
|
||||
<command id='no listen-port'>
|
||||
<params>
|
||||
<param name='no' doc='Negate a command or set its defaults' />
|
||||
<param name='listen-port' doc='Remove CBSP Listen Port; disables inbound CBSP connections' />
|
||||
</params>
|
||||
</command>
|
||||
<command id='listen-ip A.B.C.D'>
|
||||
<params>
|
||||
<param name='listen-ip' doc='Local IP Address where BSC listens for incoming CBC connections (Default: 0.0.0.0)' />
|
||||
<param name='A.B.C.D' doc='Local IP Address where BSC listens for incoming CBC connections' />
|
||||
</params>
|
||||
</command>
|
||||
</node>
|
||||
</vtydoc>
|
||||
|
|
|
@ -55,4 +55,5 @@ noinst_HEADERS = \
|
|||
gsm_08_08.h \
|
||||
penalty_timers.h \
|
||||
osmo_bsc_lcls.h \
|
||||
smscb.h \
|
||||
$(NULL)
|
||||
|
|
|
@ -150,6 +150,7 @@ struct bsc_msc_data {
|
|||
/*
|
||||
* Per BSC data.
|
||||
*/
|
||||
struct bsc_cbc_link;
|
||||
struct osmo_bsc_data {
|
||||
struct gsm_network *network;
|
||||
|
||||
|
@ -167,6 +168,8 @@ struct osmo_bsc_data {
|
|||
char *ussd_no_msc_txt;
|
||||
|
||||
char *acc_lst_name;
|
||||
|
||||
struct bsc_cbc_link *cbc;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ enum {
|
|||
DCHAN,
|
||||
DTS,
|
||||
DAS,
|
||||
DCBS,
|
||||
Debug_LastEntry,
|
||||
};
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <osmocom/core/stat_item.h>
|
||||
#include <osmocom/gsm/bts_features.h>
|
||||
#include <osmocom/gsm/protocol/gsm_08_08.h>
|
||||
#include <osmocom/gsm/protocol/gsm_48_049.h>
|
||||
#include <osmocom/gsm/gsm0808.h>
|
||||
#include <osmocom/gsm/gsm48.h>
|
||||
#include <osmocom/core/fsm.h>
|
||||
|
@ -957,6 +958,53 @@ struct gsm_bts_ref {
|
|||
struct gsm_bts *bts;
|
||||
};
|
||||
|
||||
/* A single Page of a SMSCB message */
|
||||
struct bts_smscb_page {
|
||||
/* SMSCB message we're part of */
|
||||
struct bts_smscb_message *msg;
|
||||
/* Page Number within message (1 to 15) */
|
||||
uint8_t nr;
|
||||
/* number of valid blocks in data (up to 4) */
|
||||
uint8_t num_blocks;
|
||||
/* up to four blocks of 22 bytes each */
|
||||
uint8_t data[88];
|
||||
};
|
||||
|
||||
/* A SMSCB message (received from CBSP) */
|
||||
struct bts_smscb_message {
|
||||
/* entry in bts_smscb_chan_state.messages */
|
||||
struct llist_head list;
|
||||
struct {
|
||||
/* input data from CBSP (CBC) side */
|
||||
uint16_t msg_id;
|
||||
uint16_t serial_nr;
|
||||
enum cbsp_category category;
|
||||
uint16_t rep_period;
|
||||
uint16_t num_bcast_req;
|
||||
uint8_t dcs;
|
||||
} input;
|
||||
/* how often have all pages of this message been broadcast? */
|
||||
uint32_t bcast_count;
|
||||
/* actual page data of this message */
|
||||
uint8_t num_pages; /* up to 15 */
|
||||
struct bts_smscb_page page[15];
|
||||
};
|
||||
|
||||
/* per-channel (basic/extended) CBCH state for a single BTS */
|
||||
struct bts_smscb_chan_state {
|
||||
/* back-pointer to BTS */
|
||||
struct gsm_bts *bts;
|
||||
/* list of bts_smscb_message */
|
||||
struct llist_head messages;
|
||||
/* scheduling array; pointer of SMSCB pages */
|
||||
struct bts_smscb_page **sched_arr;
|
||||
size_t sched_arr_size;
|
||||
/* index of the next to be transmitted page into the scheduler array */
|
||||
size_t next_idx;
|
||||
/* number of messages we have to pause due to overflow */
|
||||
uint8_t overflow;
|
||||
};
|
||||
|
||||
/* One BTS */
|
||||
struct gsm_bts {
|
||||
/* list header in net->bts_list */
|
||||
|
@ -1213,6 +1261,11 @@ struct gsm_bts {
|
|||
struct load_counter chan_load_samples[7];
|
||||
int chan_load_samples_idx;
|
||||
uint8_t chan_load_avg; /* current channel load average in percent (0 - 100). */
|
||||
|
||||
/* cell broadcast system */
|
||||
struct osmo_timer_list cbch_timer;
|
||||
struct bts_smscb_chan_state cbch_basic;
|
||||
struct bts_smscb_chan_state cbch_extended;
|
||||
};
|
||||
|
||||
/* One rejected BTS */
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
#include <osmocom/bsc/gsm_data.h>
|
||||
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/netif/stream.h>
|
||||
#include <osmocom/gsm/cbsp.h>
|
||||
|
||||
struct bsc_cbc_link;
|
||||
|
||||
/* smscb.c */
|
||||
void bts_smscb_del(struct bts_smscb_message *smscb, struct bts_smscb_chan_state *cstate,
|
||||
const char *reason);
|
||||
const char *bts_smscb_msg2str(const struct bts_smscb_message *smscb);
|
||||
struct bts_smscb_chan_state *bts_get_smscb_chan(struct gsm_bts *bts, bool extended);
|
||||
int cbsp_rx_decoded(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec);
|
||||
int cbsp_tx_restart(struct bsc_cbc_link *cbc, bool is_emerg);
|
||||
const char *bts_smscb_chan_state_name(const struct bts_smscb_chan_state *cstate);
|
||||
unsigned int bts_smscb_chan_load_percent(const struct bts_smscb_chan_state *cstate);
|
||||
unsigned int bts_smscb_chan_page_count(const struct bts_smscb_chan_state *cstate);
|
||||
void smscb_vty_init(void);
|
||||
|
||||
/* cbch_scheduler.c */
|
||||
int bts_smscb_gen_sched_arr(struct bts_smscb_chan_state *cstate, struct bts_smscb_page ***arr_out);
|
||||
struct bts_smscb_page *bts_smscb_pull_page(struct bts_smscb_chan_state *cstate);
|
||||
void bts_smscb_page_done(struct bts_smscb_chan_state *cstate, struct bts_smscb_page *page);
|
||||
int bts_smscb_rx_cbch_load_ind(struct gsm_bts *bts, bool cbch_extended, bool is_overflow,
|
||||
uint8_t slot_count);
|
||||
void bts_cbch_timer_schedule(struct gsm_bts *bts);
|
||||
|
||||
/* cbsp_link.c */
|
||||
struct bsc_cbc_link {
|
||||
struct gsm_network *net;
|
||||
struct {
|
||||
/* hostname/IP of CBC */
|
||||
char *cbc_hostname;
|
||||
/* TCP port (Default: 48049) of CBC */
|
||||
int cbc_port;
|
||||
/* local listening port (0 for disabling local server) */
|
||||
int listen_port;
|
||||
/* local listening hostname/IP */
|
||||
char *listen_hostname;
|
||||
} config;
|
||||
/* for handling inbound TCP connections */
|
||||
struct {
|
||||
struct osmo_stream_srv *srv;
|
||||
struct osmo_stream_srv_link *link;
|
||||
char *sock_name;
|
||||
struct msgb *msg;
|
||||
} server;
|
||||
/* for handling outbound TCP connections */
|
||||
struct {
|
||||
struct osmo_stream_cli *cli;
|
||||
char *sock_name;
|
||||
struct msgb *msg;
|
||||
} client;
|
||||
};
|
||||
void cbc_vty_init(void);
|
||||
int bsc_cbc_link_restart(void);
|
||||
int cbsp_tx_decoded(struct bsc_cbc_link *cbc, struct osmo_cbsp_decoded *decoded);
|
|
@ -24,6 +24,7 @@ enum bsc_vty_node {
|
|||
OM2K_NODE,
|
||||
OM2K_CON_GROUP_NODE,
|
||||
BSC_NODE,
|
||||
CBC_NODE,
|
||||
};
|
||||
|
||||
struct log_info;
|
||||
|
|
|
@ -88,6 +88,9 @@ osmo_bsc_SOURCES = \
|
|||
rest_octets.c \
|
||||
system_information.c \
|
||||
timeslot_fsm.c \
|
||||
smscb.c \
|
||||
cbch_scheduler.c \
|
||||
cbsp_link.c \
|
||||
$(NULL)
|
||||
|
||||
osmo_bsc_LDADD = \
|
||||
|
@ -96,6 +99,7 @@ osmo_bsc_LDADD = \
|
|||
$(LIBOSMOGSM_LIBS) \
|
||||
$(LIBOSMOVTY_LIBS) \
|
||||
$(LIBOSMOCTRL_LIBS) \
|
||||
$(LIBOSMONETIF_LIBS) \
|
||||
$(COVERAGE_LDFLAGS) \
|
||||
$(LIBOSMOABIS_LIBS) \
|
||||
$(LIBOSMOSIGTRAN_LIBS) \
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* GSM Radio Signalling Link messages on the A-bis interface
|
||||
* 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */
|
||||
|
||||
/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
|
||||
/* (C) 2008-2019 by Harald Welte <laforge@gnumonks.org>
|
||||
* (C) 2012 by Holger Hans Peter Freyther
|
||||
*
|
||||
* All Rights Reserved
|
||||
|
@ -52,6 +52,7 @@
|
|||
#include <osmocom/bsc/lchan_fsm.h>
|
||||
#include <osmocom/bsc/lchan_rtp_fsm.h>
|
||||
#include <osmocom/bsc/handover_fsm.h>
|
||||
#include <osmocom/bsc/smscb.h>
|
||||
|
||||
#define RSL_ALLOC_SIZE 1024
|
||||
#define RSL_ALLOC_HEADROOM 128
|
||||
|
@ -1489,6 +1490,36 @@ static int rsl_rx_ccch_load(struct msgb *msg)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* 8.5.9 current load on the CBCH (Cell Broadcast) */
|
||||
static int rsl_rx_cbch_load(struct msgb *msg)
|
||||
{
|
||||
struct e1inp_sign_link *sign_link = msg->dst;
|
||||
struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
|
||||
struct gsm_bts *bts = sign_link->trx->bts;
|
||||
bool cbch_extended = false;
|
||||
bool is_overflow = false;
|
||||
int8_t load_info;
|
||||
struct tlv_parsed tp;
|
||||
uint8_t slot_count;
|
||||
|
||||
rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg) - sizeof(*rslh));
|
||||
if (!TLVP_PRESENT(&tp, RSL_IE_CBCH_LOAD_INFO)) {
|
||||
LOG_BTS(bts, DRSL, LOGL_ERROR, "CBCH LOAD IND without mandatory CBCH Load Info IE\n");
|
||||
return -1;
|
||||
}
|
||||
/* 9.4.43 */
|
||||
load_info = *TLVP_VAL(&tp, RSL_IE_CBCH_LOAD_INFO);
|
||||
if (load_info & 0x80)
|
||||
is_overflow = true;
|
||||
slot_count = load_info & 0x0F;
|
||||
|
||||
if (TLVP_PRES_LEN(&tp, RSL_IE_SMSCB_CHAN_INDICATOR, 1) &&
|
||||
(*TLVP_VAL(&tp, RSL_IE_SMSCB_CHAN_INDICATOR) & 0x0F) == 0x01)
|
||||
cbch_extended = true;
|
||||
|
||||
return bts_smscb_rx_cbch_load_ind(bts, cbch_extended, is_overflow, slot_count);
|
||||
}
|
||||
|
||||
/* Ericsson specific: Immediate Assign Sent */
|
||||
static int rsl_rx_ericsson_imm_assign_sent(struct msgb *msg)
|
||||
{
|
||||
|
@ -1537,7 +1568,7 @@ static int abis_rsl_rx_cchan(struct msgb *msg)
|
|||
break;
|
||||
case RSL_MT_CBCH_LOAD_IND:
|
||||
/* current load on the CBCH */
|
||||
/* FIXME: handle this. Ignore for now */
|
||||
rc = rsl_rx_cbch_load(msg);
|
||||
break;
|
||||
case RSL_MT_ERICSSON_IMM_ASS_SENT:
|
||||
rc = rsl_rx_ericsson_imm_assign_sent(msg);
|
||||
|
|
|
@ -37,6 +37,9 @@
|
|||
#include <osmocom/bsc/gsm_04_08_rr.h>
|
||||
#include <osmocom/bsc/neighbor_ident.h>
|
||||
|
||||
#include <osmocom/bsc/smscb.h>
|
||||
#include <osmocom/gsm/protocol/gsm_48_049.h>
|
||||
|
||||
#include <time.h>
|
||||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
|
@ -244,6 +247,11 @@ static struct gsm_network *bsc_network_init(void *ctx)
|
|||
talloc_free(net);
|
||||
return NULL;
|
||||
}
|
||||
net->bsc_data->cbc = talloc_zero(net->bsc_data, struct bsc_cbc_link);
|
||||
if (!net->bsc_data->cbc) {
|
||||
talloc_free(net);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Init back pointer */
|
||||
net->bsc_data->auto_off_timeout = -1;
|
||||
|
@ -272,6 +280,13 @@ static struct gsm_network *bsc_network_init(void *ctx)
|
|||
osmo_timer_setup(&net->t3122_chan_load_timer, update_t3122_chan_load_timer, net);
|
||||
osmo_timer_schedule(&net->t3122_chan_load_timer, T3122_CHAN_LOAD_SAMPLE_INTERVAL, 0);
|
||||
|
||||
net->bsc_data->cbc->net = net;
|
||||
/* no cbc_hostname: client not started by default */
|
||||
net->bsc_data->cbc->config.cbc_port = CBSP_TCP_PORT;
|
||||
/* listen_port == -1: server not started by default */
|
||||
net->bsc_data->cbc->config.listen_port = -1;
|
||||
net->bsc_data->cbc->config.listen_hostname = talloc_strdup(net->bsc_data->cbc, "127.0.0.1");
|
||||
|
||||
return net;
|
||||
}
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
#include <osmocom/bsc/timeslot_fsm.h>
|
||||
#include <osmocom/bsc/lchan_fsm.h>
|
||||
#include <osmocom/bsc/lchan_select.h>
|
||||
#include <osmocom/bsc/smscb.h>
|
||||
#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
|
@ -318,6 +319,14 @@ static void vty_out_neigh_list(struct vty *vty, struct bitvec *bv)
|
|||
vty_out(vty, " (%d)", count);
|
||||
}
|
||||
|
||||
static void bts_dump_vty_cbch(struct vty *vty, const struct bts_smscb_chan_state *cstate)
|
||||
{
|
||||
vty_out(vty, " CBCH %s: %u messages, %u pages, %lu-entry sched_arr, %u%% load%s",
|
||||
bts_smscb_chan_state_name(cstate), llist_count(&cstate->messages),
|
||||
bts_smscb_chan_page_count(cstate), cstate->sched_arr_size,
|
||||
bts_smscb_chan_load_percent(cstate), VTY_NEWLINE);
|
||||
}
|
||||
|
||||
static void bts_dump_vty_features(struct vty *vty, struct gsm_bts *bts)
|
||||
{
|
||||
unsigned int i;
|
||||
|
@ -504,6 +513,9 @@ static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts)
|
|||
vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE);
|
||||
dump_pchan_load_vty(vty, " ", &pl);
|
||||
|
||||
bts_dump_vty_cbch(vty, &bts->cbch_basic);
|
||||
bts_dump_vty_cbch(vty, &bts->cbch_extended);
|
||||
|
||||
vty_out(vty, " Channel Requests : %"PRIu64" total, %"PRIu64" no channel%s",
|
||||
bts->bts_ctrs->ctr[BTS_CTR_CHREQ_TOTAL].current,
|
||||
bts->bts_ctrs->ctr[BTS_CTR_CHREQ_NO_CHANNEL].current,
|
||||
|
@ -5427,6 +5439,8 @@ int bsc_vty_init(struct gsm_network *network)
|
|||
osmo_fsm_vty_add_cmds();
|
||||
|
||||
ho_vty_init();
|
||||
cbc_vty_init();
|
||||
smscb_vty_init();
|
||||
|
||||
bsc_vty_init_extra();
|
||||
|
||||
|
|
|
@ -0,0 +1,287 @@
|
|||
/* CBCH (Cell Broadcast Channel) Scheduler for OsmoBSC */
|
||||
/*
|
||||
* (C) 2019 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <osmocom/core/stats.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/talloc.h>
|
||||
|
||||
#include <osmocom/bsc/debug.h>
|
||||
#include <osmocom/bsc/gsm_data.h>
|
||||
#include <osmocom/bsc/smscb.h>
|
||||
#include <osmocom/bsc/abis_rsl.h>
|
||||
|
||||
/* add all pages of given SMSCB so they appear as soon as possible *after* (included) base_idx. */
|
||||
static int bts_smscb_sched_add_after(struct bts_smscb_page **sched_arr, int sched_arr_size,
|
||||
int base_idx, struct bts_smscb_message *smscb)
|
||||
{
|
||||
int arr_idx = base_idx;
|
||||
int i;
|
||||
|
||||
OSMO_ASSERT(smscb->num_pages <= ARRAY_SIZE(smscb->page));
|
||||
for (i = 0; i < smscb->num_pages; i++) {
|
||||
while (sched_arr[arr_idx]) {
|
||||
arr_idx++;
|
||||
if (arr_idx >= sched_arr_size)
|
||||
return -ENOSPC;
|
||||
}
|
||||
sched_arr[arr_idx] = &smscb->page[i];
|
||||
}
|
||||
return arr_idx;
|
||||
}
|
||||
|
||||
/* add all pages of given smscb so they appear *before* (included) last_idx. */
|
||||
static int bts_smscb_sched_add_before(struct bts_smscb_page **sched_arr, int sched_arr_size,
|
||||
int last_idx, struct bts_smscb_message *smscb)
|
||||
{
|
||||
int arr_idx = last_idx;
|
||||
int last_used_idx = 0;
|
||||
int i;
|
||||
|
||||
OSMO_ASSERT(smscb->num_pages <= ARRAY_SIZE(smscb->page));
|
||||
OSMO_ASSERT(smscb->num_pages >= 1);
|
||||
|
||||
for (i = smscb->num_pages - 1; i >= 0; i--) {
|
||||
while (sched_arr[arr_idx]) {
|
||||
arr_idx--;
|
||||
if (arr_idx < 0)
|
||||
return -ENOSPC;
|
||||
}
|
||||
sched_arr[arr_idx] = &smscb->page[i];
|
||||
if (i == smscb->num_pages)
|
||||
last_used_idx = i;
|
||||
}
|
||||
return last_used_idx;
|
||||
}
|
||||
|
||||
/* obtain the least frequently scheduled SMSCB for given SMSCB channel */
|
||||
static struct bts_smscb_message *
|
||||
bts_smscb_chan_get_least_frequent_smscb(struct bts_smscb_chan_state *cstate)
|
||||
{
|
||||
if (llist_empty(&cstate->messages))
|
||||
return NULL;
|
||||
/* messages are expected to be ordered with increasing period, so we're
|
||||
* able to return the last message in the list */
|
||||
return llist_entry(cstate->messages.prev, struct bts_smscb_message, list);
|
||||
}
|
||||
|
||||
/*! Generate per-BTS SMSCB scheduling array
|
||||
* \param[in] cstate BTS CBCH channel state
|
||||
* \param[out] arr_out return argument for allocated + generated scheduling array
|
||||
* \return size of returned scheduling array arr_out in number of entries; negative on error */
|
||||
int bts_smscb_gen_sched_arr(struct bts_smscb_chan_state *cstate, struct bts_smscb_page ***arr_out)
|
||||
{
|
||||
struct bts_smscb_message *smscb, *least_freq;
|
||||
struct bts_smscb_page **arr;
|
||||
int arr_size;
|
||||
int rc;
|
||||
|
||||
/* start with one instance of the least frequent message at position 0, as we
|
||||
* need to transmit it exactly once during the duration of the scheduling array */
|
||||
least_freq = bts_smscb_chan_get_least_frequent_smscb(cstate);
|
||||
if (!least_freq) {
|
||||
LOG_BTS(cstate->bts, DCBS, LOGL_DEBUG, "No SMSCB; cannot create schedule array\n");
|
||||
*arr_out = NULL;
|
||||
return 0;
|
||||
}
|
||||
arr_size = least_freq->input.rep_period;
|
||||
arr = talloc_zero_array(cstate->bts, struct bts_smscb_page *, arr_size);
|
||||
OSMO_ASSERT(arr);
|
||||
rc = bts_smscb_sched_add_after(arr, arr_size, 0, least_freq);
|
||||
if (rc < 0) {
|
||||
LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule first instance of "
|
||||
"very first SMSCB %s ?!?\n", bts_smscb_msg2str(least_freq));
|
||||
talloc_free(arr);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* continue filling with repetitions of the more frequent messages, starting from
|
||||
* the most frequent message to the least frequent one, repeating them as needed
|
||||
* throughout the duration of the array */
|
||||
llist_for_each_entry(smscb, &cstate->messages, list) {
|
||||
int last_page;
|
||||
if (smscb == least_freq)
|
||||
continue;
|
||||
/* messages are expected to be ordered with increasing period, so we're
|
||||
* starting with the most frequent / shortest period first */
|
||||
rc = bts_smscb_sched_add_after(arr, arr_size, 0, smscb);
|
||||
if (rc < 0) {
|
||||
LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule first instance of "
|
||||
"SMSCB %s\n", bts_smscb_msg2str(smscb));
|
||||
talloc_free(arr);
|
||||
return rc;
|
||||
}
|
||||
last_page = rc;
|
||||
|
||||
while (last_page < cstate->sched_arr_size) {
|
||||
/* store further instances in a way that the last block of the N+1th instance
|
||||
* happens no later than "interval" after the last block of the Nth instance */
|
||||
rc = bts_smscb_sched_add_before(arr, arr_size,
|
||||
last_page + smscb->input.rep_period, smscb);
|
||||
if (rc < 0) {
|
||||
LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule further "
|
||||
"SMSCB %s\n", bts_smscb_msg2str(smscb));
|
||||
talloc_free(arr);
|
||||
return rc;
|
||||
}
|
||||
last_page = rc;
|
||||
}
|
||||
}
|
||||
*arr_out = arr;
|
||||
return arr_size;
|
||||
}
|
||||
|
||||
/*! Pull the next to-be-transmitted SMSCB page out of the scheduler for the given channel */
|
||||
struct bts_smscb_page *bts_smscb_pull_page(struct bts_smscb_chan_state *cstate)
|
||||
{
|
||||
struct bts_smscb_page *page;
|
||||
|
||||
/* if there are no messages to schedule, there is no array */
|
||||
if (!cstate->sched_arr)
|
||||
return NULL;
|
||||
|
||||
/* obtain the page from the scheduler array */
|
||||
page = cstate->sched_arr[cstate->next_idx];
|
||||
|
||||
/* increment the index for the next call to this function */
|
||||
cstate->next_idx = (cstate->next_idx + 1) % cstate->sched_arr_size;
|
||||
|
||||
/* the array can have gaps in between where there is nothing scheduled */
|
||||
if (!page)
|
||||
return NULL;
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
/*! To be called after bts_smscb_pull_page() in order to update transmission count and
|
||||
* check if SMSCB is complete.
|
||||
* \param[in] cstate BTS CBC channel state
|
||||
* \param[in] page SMSCB Page which had been returned by bts_smscb_pull_page() and which
|
||||
* is no longer needed now */
|
||||
void bts_smscb_page_done(struct bts_smscb_chan_state *cstate, struct bts_smscb_page *page)
|
||||
{
|
||||
struct bts_smscb_message *smscb = page->msg;
|
||||
|
||||
/* If this is the last page of a SMSCB, increment the SMSCB number-of-xmit counter */
|
||||
if (page->nr == smscb->num_pages) {
|
||||
smscb->bcast_count++;
|
||||
/* Check if the SMSCB transmission duration is now over */
|
||||
if (smscb->bcast_count >= smscb->input.num_bcast_req)
|
||||
bts_smscb_del(smscb, cstate, "COMPLETE");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************
|
||||
* BTS / RSL side
|
||||
***********************************************************************/
|
||||
|
||||
static void bts_cbch_send_one(struct bts_smscb_chan_state *cstate)
|
||||
{
|
||||
struct bts_smscb_page *page;
|
||||
struct gsm_bts *bts = cstate->bts;
|
||||
struct rsl_ie_cb_cmd_type cb_cmd;
|
||||
bool is_extended = false;
|
||||
|
||||
if (cstate == &bts->cbch_extended)
|
||||
is_extended = true;
|
||||
|
||||
if (cstate->overflow) {
|
||||
LOG_BTS(bts, DCBS, LOGL_DEBUG, "Skipping SMSCB due to overflow (%u)\n",
|
||||
cstate->overflow);
|
||||
cstate->overflow--;
|
||||
return;
|
||||
}
|
||||
|
||||
page = bts_smscb_pull_page(cstate);
|
||||
if (!page) {
|
||||
LOG_BTS(bts, DCBS, LOGL_DEBUG, "Skipping SMSCB: No page available\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cb_cmd.spare = 0;
|
||||
cb_cmd.def_bcast = 0;
|
||||
cb_cmd.command = RSL_CB_CMD_TYPE_NORMAL;
|
||||
switch (page->num_blocks) {
|
||||
case 1:
|
||||
cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_1;
|
||||
break;
|
||||
case 2:
|
||||
cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_2;
|
||||
break;
|
||||
case 3:
|
||||
cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_3;
|
||||
break;
|
||||
case 4:
|
||||
cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_4;
|
||||
break;
|
||||
default:
|
||||
osmo_panic("SMSCB Page must have 1..4 blocks, not %d\n", page->num_blocks);
|
||||
}
|
||||
rsl_sms_cb_command(bts, RSL_CHAN_SDCCH4_ACCH, cb_cmd, is_extended,
|
||||
page->data, sizeof(page->data));
|
||||
|
||||
bts_smscb_page_done(cstate, page);
|
||||
}
|
||||
|
||||
static void bts_cbch_timer(void *data)
|
||||
{
|
||||
struct gsm_bts *bts = (struct gsm_bts *)data;
|
||||
|
||||
bts_cbch_send_one(&bts->cbch_basic);
|
||||
bts_cbch_send_one(&bts->cbch_extended);
|
||||
|
||||
bts_cbch_timer_schedule(bts);
|
||||
}
|
||||
|
||||
/* There is one SMSCB message (page) per eight 51-multiframes, i.e. 1.882 seconds */
|
||||
void bts_cbch_timer_schedule(struct gsm_bts *bts)
|
||||
{
|
||||
osmo_timer_setup(&bts->cbch_timer, &bts_cbch_timer, bts);
|
||||
osmo_timer_schedule(&bts->cbch_timer, 1, 882920);
|
||||
}
|
||||
|
||||
/*! Receive a (decoded) incoming CBCH LOAD IND from given bts. See TS 48.058 8.5.9
|
||||
* \param[in] bts The BTS for which the load indication was received
|
||||
* \param[in] cbch_extended Is this report for extended (true) or basic CBCH
|
||||
* \param[in] is_overflow Is this report and overflow (true) or underflow report
|
||||
* \param[in] slot_count amount of SMSCB messages needed / delay needed */
|
||||
int bts_smscb_rx_cbch_load_ind(struct gsm_bts *bts, bool cbch_extended, bool is_overflow,
|
||||
uint8_t slot_count)
|
||||
{
|
||||
struct bts_smscb_chan_state *cstate = bts_get_smscb_chan(bts, cbch_extended);
|
||||
int i;
|
||||
|
||||
if (!gsm_bts_get_cbch(bts))
|
||||
return -ENODEV;
|
||||
|
||||
if (is_overflow) {
|
||||
/* halt/delay transmission of further CBCH messages */
|
||||
cstate->overflow = slot_count;
|
||||
} else {
|
||||
for (i = 0; i < slot_count; i++)
|
||||
bts_cbch_send_one(cstate);
|
||||
/* re-schedule the timer to count from now on */
|
||||
bts_cbch_timer_schedule(bts);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,417 @@
|
|||
/* CBSP (Cell Broadcast Service Protocol) Handling for OsmoBSC */
|
||||
/*
|
||||
* (C) 2019 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <osmocom/bsc/gsm_data.h>
|
||||
#include <osmocom/bsc/vty.h>
|
||||
#include <osmocom/bsc/debug.h>
|
||||
#include <osmocom/bsc/smscb.h>
|
||||
#include <osmocom/bsc/bsc_msc_data.h>
|
||||
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/socket.h>
|
||||
#include <osmocom/gsm/cbsp.h>
|
||||
|
||||
/* if a CBC IP/port has been configured, we continuously try to re-establish the TCP
|
||||
* connection (as a client) to the CBC. If none has been configured, and we have a listen
|
||||
* TCP port, we expect the CBC to connect to us. If neither of the two is configured,
|
||||
* CBSP is effectively disabled */
|
||||
|
||||
/*********************************************************************************
|
||||
* CBSP Server (inbound TCP connection from CBC)
|
||||
*********************************************************************************/
|
||||
|
||||
static int cbsp_srv_closed_cb(struct osmo_stream_srv *conn)
|
||||
{
|
||||
struct bsc_cbc_link *cbc = osmo_stream_srv_get_data(conn);
|
||||
//struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn);
|
||||
|
||||
LOGP(DCBS, LOGL_NOTICE, "CBSP Server lost connection from %s\n", cbc->server.sock_name);
|
||||
talloc_free(cbc->server.sock_name);
|
||||
cbc->server.sock_name = NULL;
|
||||
cbc->server.srv = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cbsp_srv_cb(struct osmo_stream_srv *conn)
|
||||
{
|
||||
struct bsc_cbc_link *cbc = osmo_stream_srv_get_data(conn);
|
||||
struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn);
|
||||
struct osmo_cbsp_decoded *decoded;
|
||||
struct msgb *msg;
|
||||
int rc;
|
||||
|
||||
/* READ */
|
||||
rc = osmo_cbsp_recv_buffered(cbc, ofd->fd, &msg, &cbc->server.msg);
|
||||
if (rc <= 0) {
|
||||
if (rc == -EAGAIN || rc == -EINTR) {
|
||||
/* more data needs to be read */
|
||||
return 0;
|
||||
} else if (rc == -EPIPE || rc == -ECONNRESET) {
|
||||
/* lost connection */
|
||||
} else if (rc == 0) {
|
||||
/* connection closed */
|
||||
}
|
||||
osmo_stream_srv_destroy(conn);
|
||||
cbc->server.srv = NULL;
|
||||
return -EBADF;
|
||||
}
|
||||
OSMO_ASSERT(msg);
|
||||
decoded = osmo_cbsp_decode(conn, msg);
|
||||
if (decoded) {
|
||||
LOGP(DCBS, LOGL_DEBUG, "Received CBSP %s\n",
|
||||
get_value_string(cbsp_msg_type_names, decoded->msg_type));
|
||||
cbsp_rx_decoded(cbc, decoded);
|
||||
talloc_free(decoded);
|
||||
} else {
|
||||
LOGP(DCBS, LOGL_ERROR, "Unable to decode CBSP %s: '%s'\n",
|
||||
msgb_hexdump(msg), osmo_cbsp_errstr);
|
||||
}
|
||||
msgb_free(msg);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static int cbsp_srv_link_accept_cb(struct osmo_stream_srv_link *link, int fd)
|
||||
{
|
||||
struct bsc_cbc_link *cbc = osmo_stream_srv_link_get_data(link);
|
||||
struct osmo_stream_srv *srv;
|
||||
|
||||
LOGP(DCBS, LOGL_INFO, "CBSP Server received inbound connection from CBC: %s\n",
|
||||
osmo_sock_get_name2(fd));
|
||||
|
||||
if (cbc->server.srv) {
|
||||
LOGP(DCBS, LOGL_NOTICE, "CBSP Server refusing further connection (%s) "
|
||||
"while we already have another connection (%s)\n",
|
||||
osmo_sock_get_name2(fd), cbc->server.sock_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
srv = osmo_stream_srv_create(cbc, link, fd, cbsp_srv_cb, cbsp_srv_closed_cb, cbc);
|
||||
if (!srv) {
|
||||
LOGP(DCBS, LOGL_ERROR, "Unable to create stream server for %s\n",
|
||||
osmo_sock_get_name2(fd));
|
||||
return -1;
|
||||
}
|
||||
|
||||
cbc->server.srv = srv;
|
||||
if (cbc->server.sock_name)
|
||||
talloc_free(cbc->server.sock_name);
|
||||
cbc->server.sock_name = osmo_sock_get_name(cbc, fd);
|
||||
LOGP(DCBS, LOGL_NOTICE, "CBSP Server link established from CBC %s\n", cbc->server.sock_name);
|
||||
/* TODO: introduce ourselves to the peer using some osmcoom extensions */
|
||||
cbsp_tx_restart(cbc, false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* CBSP Client (outbound TCP connection to CBC)
|
||||
*********************************************************************************/
|
||||
|
||||
static int cbsp_client_connect_cb(struct osmo_stream_cli *cli)
|
||||
{
|
||||
struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli);
|
||||
struct osmo_fd *ofd = osmo_stream_cli_get_ofd(cli);
|
||||
|
||||
if (cbc->client.sock_name)
|
||||
talloc_free(cbc->client.sock_name);
|
||||
cbc->client.sock_name = osmo_sock_get_name(cbc, ofd->fd);
|
||||
|
||||
LOGP(DCBS, LOGL_NOTICE, "CBSP Client connected to CBC: %s\n", cbc->client.sock_name);
|
||||
|
||||
/* TODO: introduce ourselves to the peer using some osmcoom extensions */
|
||||
cbsp_tx_restart(cbc, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cbsp_client_disconnect_cb(struct osmo_stream_cli *cli)
|
||||
{
|
||||
struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli);
|
||||
|
||||
LOGP(DCBS, LOGL_NOTICE, "CBSP Client lost connection to %s\n", cbc->client.sock_name);
|
||||
talloc_free(cbc->client.sock_name);
|
||||
cbc->client.sock_name = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cbsp_client_read_cb(struct osmo_stream_cli *cli)
|
||||
{
|
||||
struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli);
|
||||
struct osmo_fd *ofd = osmo_stream_cli_get_ofd(cli);
|
||||
struct osmo_cbsp_decoded *decoded;
|
||||
struct msgb *msg = NULL;
|
||||
int rc;
|
||||
|
||||
/* READ */
|
||||
rc = osmo_cbsp_recv_buffered(cbc, ofd->fd, &msg, &cbc->client.msg);
|
||||
if (rc <= 0) {
|
||||
if (rc == -EAGAIN || rc == -EINTR) {
|
||||
/* more data needs to be read */
|
||||
return 0;
|
||||
} else if (rc == -EPIPE || rc == -ECONNRESET) {
|
||||
/* lost connection */
|
||||
} else if (rc == 0) {
|
||||
/* connection closed */
|
||||
}
|
||||
osmo_stream_cli_reconnect(cli);
|
||||
return -EBADF;
|
||||
}
|
||||
OSMO_ASSERT(msg);
|
||||
decoded = osmo_cbsp_decode(cli, msg);
|
||||
if (decoded) {
|
||||
LOGP(DCBS, LOGL_DEBUG, "Received CBSP %s\n",
|
||||
get_value_string(cbsp_msg_type_names, decoded->msg_type));
|
||||
cbsp_rx_decoded(cbc, decoded);
|
||||
talloc_free(decoded);
|
||||
} else {
|
||||
LOGP(DCBS, LOGL_ERROR, "Unable to decode CBSP %s: '%s'\n",
|
||||
msgb_hexdump(msg), osmo_cbsp_errstr);
|
||||
}
|
||||
msgb_free(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bsc_cbc_link_restart(void)
|
||||
{
|
||||
struct bsc_cbc_link *cbc = bsc_gsmnet->bsc_data->cbc;
|
||||
|
||||
/* shut down client, if no longer configured */
|
||||
if (cbc->client.cli && !cbc->config.cbc_hostname) {
|
||||
LOGP(DCBS, LOGL_NOTICE, "Stopping CBSP client\n");
|
||||
osmo_stream_cli_close(cbc->client.cli);
|
||||
osmo_stream_cli_destroy(cbc->client.cli);
|
||||
cbc->client.cli = NULL;
|
||||
}
|
||||
|
||||
/* shut down server, if no longer configured */
|
||||
if (cbc->config.listen_port == -1) {
|
||||
if (cbc->server.srv || cbc->server.link)
|
||||
LOGP(DCBS, LOGL_NOTICE, "Stopping CBSP server\n");
|
||||
if (cbc->server.srv) {
|
||||
osmo_stream_srv_destroy(cbc->server.srv);
|
||||
cbc->server.srv = NULL;
|
||||
}
|
||||
if (cbc->server.link) {
|
||||
osmo_stream_srv_link_close(cbc->server.link);
|
||||
osmo_stream_srv_link_destroy(cbc->server.link);
|
||||
cbc->server.link = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* start client, if configured */
|
||||
if (cbc->config.cbc_hostname) {
|
||||
LOGP(DCBS, LOGL_NOTICE, "Starting CBSP Client (to CBC at %s:%u)\n",
|
||||
cbc->config.cbc_hostname, cbc->config.cbc_port);
|
||||
if (!cbc->client.cli) {
|
||||
cbc->client.cli = osmo_stream_cli_create(cbc);
|
||||
osmo_stream_cli_set_data(cbc->client.cli, cbc);
|
||||
osmo_stream_cli_set_connect_cb(cbc->client.cli, cbsp_client_connect_cb);
|
||||
osmo_stream_cli_set_disconnect_cb(cbc->client.cli, cbsp_client_disconnect_cb);
|
||||
osmo_stream_cli_set_read_cb(cbc->client.cli, cbsp_client_read_cb);
|
||||
}
|
||||
/* CBC side */
|
||||
osmo_stream_cli_set_addr(cbc->client.cli, cbc->config.cbc_hostname);
|
||||
osmo_stream_cli_set_port(cbc->client.cli, cbc->config.cbc_port);
|
||||
/* Close/Reconnect? */
|
||||
osmo_stream_cli_open(cbc->client.cli);
|
||||
}
|
||||
|
||||
/* start server, if configured */
|
||||
if (cbc->config.listen_port != -1) {
|
||||
LOGP(DCBS, LOGL_NOTICE, "Starting CBSP Server (bound to %s:%u)\n",
|
||||
cbc->config.listen_hostname, cbc->config.listen_port);
|
||||
if (!cbc->server.srv) {
|
||||
cbc->server.link = osmo_stream_srv_link_create(cbc);
|
||||
osmo_stream_srv_link_set_data(cbc->server.link, cbc);
|
||||
osmo_stream_srv_link_set_accept_cb(cbc->server.link, cbsp_srv_link_accept_cb);
|
||||
}
|
||||
osmo_stream_srv_link_set_addr(cbc->server.link, cbc->config.listen_hostname);
|
||||
osmo_stream_srv_link_set_port(cbc->server.link, cbc->config.listen_port);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Encode + Transmit a 'decoded' CBSP message over given CBC link
|
||||
* \param[in] cbc Data structure representing the BSCs link to the CBC
|
||||
* \param[in] cbsp Decoded CBSP message to be transmitted. Ownership is transferred.
|
||||
* \return 0 on success, negative otherwise */
|
||||
int cbsp_tx_decoded(struct bsc_cbc_link *cbc, struct osmo_cbsp_decoded *cbsp)
|
||||
{
|
||||
struct msgb *msg;
|
||||
|
||||
msg = osmo_cbsp_encode(cbc, cbsp);
|
||||
if (!msg) {
|
||||
LOGP(DCBS, LOGL_ERROR, "Unable to encode CBSP Message Type %s: %s\n",
|
||||
get_value_string(cbsp_msg_type_names, cbsp->msg_type), osmo_cbsp_errstr);
|
||||
talloc_free(cbsp);
|
||||
return -1;
|
||||
}
|
||||
if (cbc->client.cli)
|
||||
osmo_stream_cli_send(cbc->client.cli, msg);
|
||||
else if (cbc->server.srv)
|
||||
osmo_stream_srv_send(cbc->server.srv, msg);
|
||||
else {
|
||||
LOGP(DCBS, LOGL_ERROR, "Discarding CBSP Message, link is down: %s\n", msgb_hexdump(msg));
|
||||
msgb_free(msg);
|
||||
}
|
||||
|
||||
talloc_free(cbsp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct bsc_cbc_link *vty_cbc_data(struct vty *vty)
|
||||
{
|
||||
return bsc_gsmnet->bsc_data->cbc;
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* VTY Interface (Configuration + Introspection)
|
||||
*********************************************************************************/
|
||||
|
||||
DEFUN(cfg_cbc, cfg_cbc_cmd,
|
||||
"cbc", "Configure CBSP Link to Cell Broadcast Centre\n")
|
||||
{
|
||||
vty->node = CBC_NODE;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_cbc_remote_ip, cfg_cbc_remote_ip_cmd,
|
||||
"remote-ip A.B.C.D",
|
||||
"IP Address of the Cell Broadcast Centre\n"
|
||||
"IP Address of the Cell Broadcast Centre\n")
|
||||
{
|
||||
struct bsc_cbc_link *cbc = vty_cbc_data(vty);
|
||||
osmo_talloc_replace_string(cbc, &cbc->config.cbc_hostname, argv[0]);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
DEFUN(cfg_cbc_no_remote_ip, cfg_cbc_no_remote_ip_cmd,
|
||||
"no remote-ip",
|
||||
NO_STR "Remove IP address of CBC; disables outbound CBSP connections\n")
|
||||
{
|
||||
struct bsc_cbc_link *cbc = vty_cbc_data(vty);
|
||||
talloc_free(cbc->config.cbc_hostname);
|
||||
cbc->config.cbc_hostname = NULL;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_cbc_remote_port, cfg_cbc_remote_port_cmd,
|
||||
"remote-port <1-65535>",
|
||||
"TCP Port number of the Cell Broadcast Centre (Default: 48049)\n"
|
||||
"TCP Port number of the Cell Broadcast Centre (Default: 48049)\n")
|
||||
{
|
||||
struct bsc_cbc_link *cbc = vty_cbc_data(vty);
|
||||
cbc->config.cbc_port = atoi(argv[0]);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_cbc_listen_port, cfg_cbc_listen_port_cmd,
|
||||
"listen-port <1-65535>",
|
||||
"Local TCP port at which BSC listens for incoming CBSP connections from CBC\n"
|
||||
"Local TCP port at which BSC listens for incoming CBSP connections from CBC\n")
|
||||
{
|
||||
struct bsc_cbc_link *cbc = vty_cbc_data(vty);
|
||||
cbc->config.listen_port = atoi(argv[0]);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
DEFUN(cfg_cbc_no_listen_port, cfg_cbc_no_listen_port_cmd,
|
||||
"no listen-port",
|
||||
NO_STR "Remove CBSP Listen Port; disables inbound CBSP connections\n")
|
||||
{
|
||||
struct bsc_cbc_link *cbc = vty_cbc_data(vty);
|
||||
cbc->config.listen_port = -1;
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_cbc_listen_ip, cfg_cbc_listen_ip_cmd,
|
||||
"listen-ip A.B.C.D",
|
||||
"Local IP Address where BSC listens for incoming CBC connections (Default: 0.0.0.0)\n"
|
||||
"Local IP Address where BSC listens for incoming CBC connections\n")
|
||||
{
|
||||
struct bsc_cbc_link *cbc = vty_cbc_data(vty);
|
||||
osmo_talloc_replace_string(cbc, &cbc->config.listen_hostname, argv[0]);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
static struct cmd_node cbc_node = {
|
||||
CBC_NODE,
|
||||
"%s(config-cbc)# ",
|
||||
1,
|
||||
};
|
||||
|
||||
static int config_write_cbc(struct vty *vty)
|
||||
{
|
||||
struct bsc_cbc_link *cbc = vty_cbc_data(vty);
|
||||
|
||||
vty_out(vty, "cbc%s", VTY_NEWLINE);
|
||||
|
||||
if (cbc->config.cbc_hostname) {
|
||||
vty_out(vty, " remote-ip %s%s", cbc->config.cbc_hostname, VTY_NEWLINE);
|
||||
vty_out(vty, " remote-port %u%s", cbc->config.cbc_port, VTY_NEWLINE);
|
||||
} else
|
||||
vty_out(vty, " no remote-ip%s", VTY_NEWLINE);
|
||||
|
||||
if (cbc->config.listen_port >= 0) {
|
||||
vty_out(vty, " listen-port %u%s", cbc->config.listen_port, VTY_NEWLINE);
|
||||
vty_out(vty, " listen-ip %s%s", cbc->config.listen_hostname, VTY_NEWLINE);
|
||||
} else
|
||||
vty_out(vty, " no listen-port%s", VTY_NEWLINE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFUN(show_cbc, show_cbc_cmd,
|
||||
"show cbc",
|
||||
SHOW_STR "Display state of CBC / CBSP\n")
|
||||
{
|
||||
struct bsc_cbc_link *cbc = vty_cbc_data(vty);
|
||||
|
||||
if (!cbc->config.cbc_hostname)
|
||||
vty_out(vty, "CBSP Client Config: Disabled%s", VTY_NEWLINE);
|
||||
else {
|
||||
vty_out(vty, "CBSP Client Config: CBC IP=%s, CBC Port=%u%s",
|
||||
cbc->config.cbc_hostname, cbc->config.cbc_port, VTY_NEWLINE);
|
||||
vty_out(vty, "CBSP Client Connection: %s%s",
|
||||
cbc->client.sock_name ? cbc->client.sock_name : "Disconnected", VTY_NEWLINE);
|
||||
}
|
||||
if (cbc->config.listen_port < 0)
|
||||
vty_out(vty, "CBSP Server Config: Disabled%s\n", VTY_NEWLINE);
|
||||
else {
|
||||
vty_out(vty, "CBSP Server Config: Listen IP=%s, Port=%u%s\n",
|
||||
cbc->config.listen_hostname, cbc->config.listen_port, VTY_NEWLINE);
|
||||
vty_out(vty, "CBSP Server Connection: %s%s",
|
||||
cbc->server.sock_name ? cbc->server.sock_name : "Disconnected", VTY_NEWLINE);
|
||||
}
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
void cbc_vty_init(void)
|
||||
{
|
||||
install_element(VIEW_NODE, &show_cbc_cmd);
|
||||
install_element(CONFIG_NODE, &cfg_cbc_cmd);
|
||||
install_node(&cbc_node, config_write_cbc);
|
||||
install_element(CBC_NODE, &cfg_cbc_remote_ip_cmd);
|
||||
install_element(CBC_NODE, &cfg_cbc_no_remote_ip_cmd);
|
||||
install_element(CBC_NODE, &cfg_cbc_remote_port_cmd);
|
||||
install_element(CBC_NODE, &cfg_cbc_listen_port_cmd);
|
||||
install_element(CBC_NODE, &cfg_cbc_no_listen_port_cmd);
|
||||
install_element(CBC_NODE, &cfg_cbc_listen_ip_cmd);
|
||||
}
|
|
@ -767,6 +767,12 @@ static const struct gprs_rlc_cfg rlc_cfg_default = {
|
|||
.initial_mcs = 6,
|
||||
};
|
||||
|
||||
static void bts_init_cbch_state(struct bts_smscb_chan_state *cstate, struct gsm_bts *bts)
|
||||
{
|
||||
cstate->bts = bts;
|
||||
INIT_LLIST_HEAD(&cstate->messages);
|
||||
}
|
||||
|
||||
/* Initialize those parts that don't require osmo-bsc specific dependencies.
|
||||
* This part is shared among the thin programs in osmo-bsc/src/utils/.
|
||||
* osmo-bsc requires further initialization that pulls in more dependencies (see
|
||||
|
@ -945,6 +951,9 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num)
|
|||
}
|
||||
bts->mr_half.num_modes = 3;
|
||||
|
||||
bts_init_cbch_state(&bts->cbch_basic, bts);
|
||||
bts_init_cbch_state(&bts->cbch_extended, bts);
|
||||
|
||||
return bts;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include <osmocom/bsc/bsc_subscriber.h>
|
||||
#include <osmocom/bsc/assignment_fsm.h>
|
||||
#include <osmocom/bsc/handover_fsm.h>
|
||||
#include <osmocom/bsc/smscb.h>
|
||||
|
||||
#include <osmocom/ctrl/control_cmd.h>
|
||||
#include <osmocom/ctrl/control_if.h>
|
||||
|
@ -310,6 +311,10 @@ static void bootstrap_rsl(struct gsm_bts_trx *trx)
|
|||
OSMO_ASSERT(ts->fi);
|
||||
osmo_fsm_inst_dispatch(ts->fi, TS_EV_RSL_READY, NULL);
|
||||
}
|
||||
|
||||
/* Start CBCH transmit timer if CBCH is present */
|
||||
if (trx->nr == 0 && gsm_bts_get_cbch(trx->bts))
|
||||
bts_cbch_timer_schedule(trx->bts);
|
||||
}
|
||||
|
||||
static void all_ts_dispatch_event(struct gsm_bts_trx *trx, uint32_t event)
|
||||
|
@ -379,6 +384,8 @@ static int inp_sig_cb(unsigned int subsys, unsigned int signal,
|
|||
rate_ctr_inc(&trx->bts->bts_ctrs->ctr[BTS_CTR_BTS_RSL_FAIL]);
|
||||
acc_ramp_abort(&trx->bts->acc_ramp);
|
||||
all_ts_dispatch_event(trx, TS_EV_RSL_DOWN);
|
||||
if (trx->nr == 0)
|
||||
osmo_timer_del(&trx->bts->cbch_timer);
|
||||
}
|
||||
|
||||
gsm_bts_mo_reset(trx->bts);
|
||||
|
@ -764,6 +771,11 @@ static const struct log_info_cat osmo_bsc_categories[] = {
|
|||
.description = "Local Call, Local Switch",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
},
|
||||
[DCBS] = {
|
||||
.name = "DCBS",
|
||||
.description = "Cell Broadcast System",
|
||||
.enabled = 1, .loglevel = LOGL_NOTICE,
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
@ -912,6 +924,7 @@ int main(int argc, char **argv)
|
|||
|
||||
handover_decision_1_init();
|
||||
hodec2_init(bsc_gsmnet);
|
||||
bsc_cbc_link_restart();
|
||||
|
||||
signal(SIGINT, &signal_handler);
|
||||
signal(SIGTERM, &signal_handler);
|
||||
|
|
|
@ -0,0 +1,823 @@
|
|||
/* SMSCB (SMS Cell Broadcast) Handling for OsmoBSC */
|
||||
/*
|
||||
* (C) 2019 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include <osmocom/core/stats.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/talloc.h>
|
||||
|
||||
#include <osmocom/gsm/cbsp.h>
|
||||
#include <osmocom/gsm/protocol/gsm_23_041.h>
|
||||
#include <osmocom/gsm/protocol/gsm_48_049.h>
|
||||
|
||||
#include <osmocom/netif/stream.h>
|
||||
|
||||
#include <osmocom/bsc/debug.h>
|
||||
#include <osmocom/bsc/gsm_data.h>
|
||||
#include <osmocom/bsc/smscb.h>
|
||||
#include <osmocom/bsc/vty.h>
|
||||
|
||||
/*********************************************************************************
|
||||
* Helper Functions
|
||||
*********************************************************************************/
|
||||
|
||||
/* replace the old head of an entire list with a new head; effectively moves the entire
|
||||
* list from old to new head */
|
||||
static void llist_replace_head(struct llist_head *new, struct llist_head *old)
|
||||
{
|
||||
if (llist_empty(old))
|
||||
INIT_LLIST_HEAD(new);
|
||||
else
|
||||
__llist_add(new, old->prev, old->next);
|
||||
INIT_LLIST_HEAD(old);
|
||||
}
|
||||
|
||||
/*! Obtain SMSCB Channel State for given BTS (basic or extended CBCH) */
|
||||
struct bts_smscb_chan_state *bts_get_smscb_chan(struct gsm_bts *bts, bool extended)
|
||||
{
|
||||
struct bts_smscb_chan_state *chan_state;
|
||||
|
||||
if (extended)
|
||||
chan_state = &bts->cbch_extended;
|
||||
else
|
||||
chan_state = &bts->cbch_basic;
|
||||
|
||||
return chan_state;
|
||||
}
|
||||
|
||||
/* do an ordered list insertion. we keep the list with increasing period, i.e. the most
|
||||
* frequent message first */
|
||||
static void __bts_smscb_add(struct bts_smscb_chan_state *cstate, struct bts_smscb_message *new)
|
||||
{
|
||||
struct bts_smscb_message *tmp, *tmp2;
|
||||
|
||||
if (llist_empty(&cstate->messages)) {
|
||||
llist_add(&new->list, &cstate->messages);
|
||||
return;
|
||||
}
|
||||
|
||||
llist_for_each_entry_safe(tmp, tmp2, &cstate->messages, list) {
|
||||
if (tmp->input.rep_period > new->input.rep_period) {
|
||||
/* we found the first message with longer period than the new message,
|
||||
* we must insert ourselves before that one */
|
||||
__llist_add(&new->list, tmp->list.prev, &tmp->list);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* stringify a SMSCB for logging */
|
||||
const char *bts_smscb_msg2str(const struct bts_smscb_message *smscb)
|
||||
{
|
||||
static char buf[128];
|
||||
snprintf(buf, sizeof(buf), "MsgId=0x%04x/SerialNr=0x%04x/Pages=%u/Period=%u/NumBcastReq=%u",
|
||||
smscb->input.msg_id, smscb->input.serial_nr, smscb->num_pages,
|
||||
smscb->input.rep_period, smscb->input.num_bcast_req);
|
||||
return buf;
|
||||
}
|
||||
|
||||
const char *bts_smscb_chan_state_name(const struct bts_smscb_chan_state *cstate)
|
||||
{
|
||||
if (cstate == &cstate->bts->cbch_basic)
|
||||
return "BASIC";
|
||||
else if (cstate == &cstate->bts->cbch_extended)
|
||||
return "EXTENDED";
|
||||
else
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
unsigned int bts_smscb_chan_load_percent(const struct bts_smscb_chan_state *cstate)
|
||||
{
|
||||
unsigned int sched_arr_used = 0;
|
||||
unsigned int i;
|
||||
|
||||
if (cstate->sched_arr_size == 0)
|
||||
return 0;
|
||||
|
||||
/* count the number of used slots */
|
||||
for (i = 0; i < cstate->sched_arr_size; i++) {
|
||||
if (cstate->sched_arr[i])
|
||||
sched_arr_used++;
|
||||
}
|
||||
|
||||
OSMO_ASSERT(sched_arr_used <= UINT_MAX/100);
|
||||
return (sched_arr_used * 100) / cstate->sched_arr_size;
|
||||
}
|
||||
|
||||
unsigned int bts_smscb_chan_page_count(const struct bts_smscb_chan_state *cstate)
|
||||
{
|
||||
struct bts_smscb_message *smscb;
|
||||
unsigned int page_count = 0;
|
||||
|
||||
llist_for_each_entry(smscb, &cstate->messages, list)
|
||||
page_count += smscb->num_pages;
|
||||
|
||||
return page_count;
|
||||
}
|
||||
|
||||
|
||||
/*! Obtain the Cell Global Identifier (CGI) of given BTS; returned in static buffer. */
|
||||
static struct osmo_cell_global_id *bts_get_cgi(struct gsm_bts *bts)
|
||||
{
|
||||
static struct osmo_cell_global_id cgi;
|
||||
cgi.lai.plmn = bts->network->plmn;
|
||||
cgi.lai.lac = bts->location_area_code;
|
||||
cgi.cell_identity = bts->cell_identity;
|
||||
return &cgi;
|
||||
}
|
||||
|
||||
/* represents the various lists that the BSC can create as part of a response */
|
||||
struct response_state {
|
||||
struct osmo_cbsp_cell_list success; /* osmo_cbsp_cell_ent */
|
||||
struct llist_head fail; /* osmo_cbsp_fail_ent */
|
||||
struct osmo_cbsp_num_compl_list num_completed; /* osmo_cbsp_num_compl_ent */
|
||||
struct osmo_cbsp_loading_list loading; /* osmo_cbsp_loading_ent */
|
||||
};
|
||||
|
||||
/*! per-BTS callback function used by cbsp_per_bts().
|
||||
* \param[in] bts BTS currently being processed
|
||||
* \param[in] dec decoded CBSP message currently being processed
|
||||
* \param r_state response state accumulating cell lists (success/failure/...)
|
||||
* \param priv opaque private data provided by caller of cbsp_per_bts()
|
||||
* \returns 0 on success; negative TS 48.049 cause value on error */
|
||||
typedef int bts_cb_fn(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
|
||||
struct response_state *r_state, void *priv);
|
||||
|
||||
/* append a success for given cell to response state */
|
||||
static void append_success(struct response_state *r_state, struct gsm_bts *bts)
|
||||
{
|
||||
struct osmo_cbsp_cell_ent *cent = talloc_zero(r_state, struct osmo_cbsp_cell_ent);
|
||||
struct osmo_cell_global_id *cgi = bts_get_cgi(bts);
|
||||
|
||||
LOG_BTS(bts, DCBS, LOGL_INFO, "Success\n");
|
||||
|
||||
OSMO_ASSERT(cent);
|
||||
|
||||
cent->cell_id.global = *cgi;
|
||||
llist_add_tail(¢->list, &r_state->success.list);
|
||||
}
|
||||
|
||||
/* append a failure for given cell to response state */
|
||||
static void append_fail(struct response_state *r_state, struct gsm_bts *bts, uint8_t cause)
|
||||
{
|
||||
struct osmo_cbsp_fail_ent *fent = talloc_zero(r_state, struct osmo_cbsp_fail_ent);
|
||||
struct osmo_cell_global_id *cgi = bts_get_cgi(bts);
|
||||
|
||||
LOG_BTS(bts, DCBS, LOGL_NOTICE, "Failure Cause 0x%02x\n", cause);
|
||||
|
||||
OSMO_ASSERT(fent);
|
||||
|
||||
fent->id_discr = CELL_IDENT_WHOLE_GLOBAL;
|
||||
fent->cell_id.global = *cgi;
|
||||
fent->cause = cause;
|
||||
llist_add_tail(&fent->list, &r_state->fail);
|
||||
}
|
||||
|
||||
/* append a 'number of broadcasts completed' for given cell to response state */
|
||||
static void append_bcast_compl(struct response_state *r_state, struct gsm_bts *bts,
|
||||
struct bts_smscb_message *smscb)
|
||||
{
|
||||
struct osmo_cbsp_num_compl_ent *cent = talloc_zero(r_state, struct osmo_cbsp_num_compl_ent);
|
||||
struct osmo_cell_global_id *cgi = bts_get_cgi(bts);
|
||||
|
||||
LOG_BTS(bts, DCBS, LOGL_DEBUG, "Number of Broadcasts Completed: %u\n", smscb->bcast_count);
|
||||
|
||||
OSMO_ASSERT(cent);
|
||||
|
||||
r_state->num_completed.id_discr = CELL_IDENT_WHOLE_GLOBAL;
|
||||
cent->cell_id.global = *cgi;
|
||||
if (smscb->bcast_count > INT16_MAX) {
|
||||
cent->num_compl = INT16_MAX;
|
||||
cent->num_bcast_info = 0x01; /* Overflow */
|
||||
} else {
|
||||
cent->num_compl = smscb->bcast_count;
|
||||
cent->num_bcast_info = 0x00;
|
||||
}
|
||||
llist_add_tail(¢->list, &r_state->num_completed.list);
|
||||
}
|
||||
|
||||
/*! Iterate over all BTSs, find matching ones, execute command on BTS, add result
|
||||
* to succeeded/failed lists.
|
||||
* \param[in] net GSM network in which we operate
|
||||
* \param[in] caller-allocated Response state structure collecting results
|
||||
* \param[in] cell_list Decoded CBSP cell list describing BTSs to operate on
|
||||
* \param[in] cb_fn Call-back function to call for each matching BTS
|
||||
* \param[in] priv Opqaue private data; passed to cb_fn
|
||||
* */
|
||||
static int cbsp_per_bts(struct gsm_network *net, struct response_state *r_state,
|
||||
const struct osmo_cbsp_cell_list *cell_list,
|
||||
bts_cb_fn *cb_fn, const struct osmo_cbsp_decoded *dec, void *priv)
|
||||
{
|
||||
struct osmo_cbsp_cell_ent *ent;
|
||||
struct gsm_bts *bts;
|
||||
uint8_t bts_status[net->num_bts];
|
||||
int rc, ret = 0;
|
||||
|
||||
memset(bts_status, 0, sizeof(bts_status));
|
||||
INIT_LLIST_HEAD(&r_state->success.list);
|
||||
INIT_LLIST_HEAD(&r_state->fail);
|
||||
INIT_LLIST_HEAD(&r_state->num_completed.list);
|
||||
INIT_LLIST_HEAD(&r_state->loading.list);
|
||||
|
||||
/* special case as cell_list->list is empty in this case */
|
||||
if (cell_list->id_discr == CELL_IDENT_BSS) {
|
||||
llist_for_each_entry(bts, &net->bts_list, list) {
|
||||
bts_status[bts->nr] = 1;
|
||||
/* call function on this BTS */
|
||||
rc = cb_fn(bts, dec, r_state, priv);
|
||||
if (rc < 0) {
|
||||
append_fail(r_state, bts, -rc);
|
||||
ret = -1;
|
||||
} else
|
||||
append_success(r_state, bts);
|
||||
}
|
||||
} else {
|
||||
/* normal case: iterate over cell list */
|
||||
llist_for_each_entry(ent, &cell_list->list, list) {
|
||||
bool found_at_least_one = false;
|
||||
/* find all matching BTSs for this entry */
|
||||
llist_for_each_entry(bts, &net->bts_list, list) {
|
||||
struct gsm0808_cell_id cell_id = {
|
||||
.id_discr = cell_list->id_discr,
|
||||
.id = ent->cell_id
|
||||
};
|
||||
if (!gsm_bts_matches_cell_id(bts, &cell_id))
|
||||
continue;
|
||||
found_at_least_one = true;
|
||||
/* skip any BTSs which we've already processed */
|
||||
if (bts_status[bts->nr])
|
||||
continue;
|
||||
bts_status[bts->nr] = 1;
|
||||
/* call function on this BTS */
|
||||
rc = cb_fn(bts, dec, r_state, priv);
|
||||
if (rc < 0) {
|
||||
append_fail(r_state, bts, -rc);
|
||||
ret = -1;
|
||||
} else
|
||||
append_success(r_state, bts);
|
||||
}
|
||||
if (!found_at_least_one) {
|
||||
struct osmo_cbsp_fail_ent *fent;
|
||||
LOGP(DCBS, LOGL_NOTICE, "CBSP: Couldn't find a single matching BTS\n");
|
||||
fent = talloc_zero(r_state, struct osmo_cbsp_fail_ent);
|
||||
OSMO_ASSERT(fent);
|
||||
fent->id_discr = cell_list->id_discr;
|
||||
fent->cell_id = ent->cell_id;
|
||||
llist_add_tail(&fent->list, &r_state->fail);
|
||||
ret = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*! Find an existing SMSCB message within given BTS.
|
||||
* \param[in] chan_state BTS CBCH channel state
|
||||
* \param[in] msg_id Message Id of to-be-found message
|
||||
* \param[in] serial_nr Serial Number of to-be-found message
|
||||
* \returns SMSCB message if found; NULL otherwise */
|
||||
struct bts_smscb_message *bts_find_smscb(struct bts_smscb_chan_state *chan_state,
|
||||
uint16_t msg_id, uint16_t serial_nr)
|
||||
{
|
||||
struct bts_smscb_message *smscb;
|
||||
|
||||
llist_for_each_entry(smscb, &chan_state->messages, list) {
|
||||
if (smscb->input.msg_id == msg_id && smscb->input.serial_nr == serial_nr)
|
||||
return smscb;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*! create a new SMSCB message for specified BTS; don't link it yet.
|
||||
* \param[in] bts BTS for which the SMSCB is to be allocated
|
||||
* \param[in] wrepl CBSP write-replace message
|
||||
* \returns callee-allocated SMSCB message filled with data from wrepl */
|
||||
static struct bts_smscb_message *bts_smscb_msg_from_wrepl(struct gsm_bts *bts,
|
||||
const struct osmo_cbsp_write_replace *wrepl)
|
||||
{
|
||||
struct bts_smscb_message *smscb = talloc_zero(bts, struct bts_smscb_message);
|
||||
struct osmo_cbsp_content *cont;
|
||||
int i;
|
||||
|
||||
if (!smscb)
|
||||
return NULL;
|
||||
|
||||
OSMO_ASSERT(wrepl->is_cbs);
|
||||
|
||||
/* initialize all pages inside the message */
|
||||
for (i = 0; i < ARRAY_SIZE(smscb->page); i++) {
|
||||
struct bts_smscb_page *page = &smscb->page[i];
|
||||
page->nr = i+1; /* page numbers are 1-based */
|
||||
page->msg = smscb;
|
||||
}
|
||||
|
||||
/* initialize "header" part */
|
||||
smscb->input.msg_id = wrepl->msg_id;
|
||||
smscb->input.serial_nr = wrepl->new_serial_nr;
|
||||
smscb->input.category = wrepl->u.cbs.category;
|
||||
smscb->input.rep_period = wrepl->u.cbs.rep_period;
|
||||
smscb->input.num_bcast_req = wrepl->u.cbs.num_bcast_req;
|
||||
smscb->input.dcs = wrepl->u.cbs.dcs;
|
||||
smscb->num_pages = llist_count(&wrepl->u.cbs.msg_content);
|
||||
if (smscb->num_pages > ARRAY_SIZE(smscb->page)) {
|
||||
LOG_BTS(bts, DCBS, LOGL_ERROR, "SMSCB with too many pages (%u > %lu)\n",
|
||||
smscb->num_pages, ARRAY_SIZE(smscb->page));
|
||||
talloc_free(smscb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
llist_for_each_entry(cont, &wrepl->u.cbs.msg_content, list) {
|
||||
struct gsm23041_msg_param_gsm *msg_param;
|
||||
struct bts_smscb_page *page;
|
||||
size_t bytes_used;
|
||||
|
||||
/* we have just ensured a few lines above that this cannot overflow */
|
||||
page = &smscb->page[i++];
|
||||
msg_param = (struct gsm23041_msg_param_gsm *) &page->data[0];
|
||||
|
||||
/* build 6 byte header according to TS 23.041 9.4.1.2 */
|
||||
osmo_store16be(wrepl->new_serial_nr, &msg_param->serial_nr);
|
||||
osmo_store16be(wrepl->msg_id, &msg_param->message_id);
|
||||
msg_param->dcs = wrepl->u.cbs.dcs;
|
||||
msg_param->page_param.num_pages = smscb->num_pages;
|
||||
msg_param->page_param.page_nr = page->nr;
|
||||
|
||||
OSMO_ASSERT(cont->user_len <= ARRAY_SIZE(cont->data));
|
||||
OSMO_ASSERT(cont->user_len <= ARRAY_SIZE(page->data) - sizeof(*msg_param));
|
||||
memcpy(&msg_param->content, cont->data, cont->user_len);
|
||||
bytes_used = sizeof(*msg_param) + cont->user_len;
|
||||
/* compute number of valid blocks in page */
|
||||
page->num_blocks = bytes_used / 22;
|
||||
if (bytes_used % 22)
|
||||
page->num_blocks += 1;
|
||||
}
|
||||
|
||||
return smscb;
|
||||
}
|
||||
|
||||
/*! remove a SMSCB message */
|
||||
void bts_smscb_del(struct bts_smscb_message *smscb, struct bts_smscb_chan_state *cstate,
|
||||
const char *reason)
|
||||
{
|
||||
struct bts_smscb_page **arr;
|
||||
int rc;
|
||||
|
||||
LOG_BTS(cstate->bts, DCBS, LOGL_INFO, "%s Deleting %s (Reason: %s)\n",
|
||||
bts_smscb_chan_state_name(cstate), bts_smscb_msg2str(smscb), reason);
|
||||
llist_del(&smscb->list);
|
||||
|
||||
/* we must recompute the scheduler array here, as the old one will have pointers
|
||||
* to the pages of the just-to-be-deleted message */
|
||||
rc = bts_smscb_gen_sched_arr(cstate, &arr);
|
||||
if (rc < 0) {
|
||||
LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Cannot generate new CBCH scheduler array after "
|
||||
"removing message %s. WTF?\n", bts_smscb_msg2str(smscb));
|
||||
/* we cannot free the message now, to ensure the page pointers in the old
|
||||
* array are still valid. let's re-add it to keep things sane */
|
||||
__bts_smscb_add(cstate, smscb);
|
||||
} else {
|
||||
/* success */
|
||||
talloc_free(smscb);
|
||||
|
||||
/* replace array with new one */
|
||||
talloc_free(cstate->sched_arr);
|
||||
cstate->sched_arr = arr;
|
||||
cstate->sched_arr_size = rc;
|
||||
cstate->next_idx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*********************************************************************************
|
||||
* Transmit of CBSP to CBC
|
||||
*********************************************************************************/
|
||||
|
||||
/* transmit a CBSP RESTART message stating all message data was lost for entire BSS */
|
||||
int cbsp_tx_restart(struct bsc_cbc_link *cbc, bool is_emerg)
|
||||
{
|
||||
struct osmo_cbsp_decoded *cbsp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESTART);
|
||||
|
||||
if (is_emerg)
|
||||
cbsp->u.restart.bcast_msg_type = 0x01;
|
||||
cbsp->u.restart.recovery_ind = 0x01; /* message data lost */
|
||||
cbsp->u.restart.cell_list.id_discr = CELL_IDENT_BSS;
|
||||
|
||||
return cbsp_tx_decoded(cbc, cbsp);
|
||||
}
|
||||
|
||||
/* transmit a CBSP KEEPALIVE COMPLETE to the CBC */
|
||||
static int tx_cbsp_keepalive_compl(struct bsc_cbc_link *cbc)
|
||||
{
|
||||
struct osmo_cbsp_decoded *cbsp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KEEP_ALIVE_COMPL);
|
||||
return cbsp_tx_decoded(cbc, cbsp);
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Per-BTS Processing of CBSP from CBC, called via cbsp_per_bts()
|
||||
*********************************************************************************/
|
||||
|
||||
/*! Try to execute a write-replace operation; roll-back if it fails.
|
||||
* \param[in] chan_state BTS CBCH channel state
|
||||
* \param[in] extended_cbch Basic (false) or Extended (true) CBCH
|
||||
* \param[in] new_msg New SMSCB message which should be added
|
||||
* \param[in] exclude_msg Existing SMSCB message that shall be replaced (if possible). Can be NULL
|
||||
* \return 0 on success; negative on error */
|
||||
static int bts_try_write_replace(struct bts_smscb_chan_state *chan_state,
|
||||
struct bts_smscb_message *new_msg,
|
||||
struct bts_smscb_message *exclude_msg,
|
||||
struct response_state *r_state)
|
||||
{
|
||||
struct bts_smscb_page **arr;
|
||||
int rc;
|
||||
|
||||
if (exclude_msg) {
|
||||
/* temporarily remove from list of SMSCB */
|
||||
llist_del(&exclude_msg->list);
|
||||
}
|
||||
/* temporarily add new_msg to list of SMSCB */
|
||||
__bts_smscb_add(chan_state, new_msg);
|
||||
|
||||
/* attempt to create scheduling array */
|
||||
rc = bts_smscb_gen_sched_arr(chan_state, &arr);
|
||||
if (rc < 0) {
|
||||
/* it didn't work out; we couldn't schedule it */
|
||||
/* remove the new message again */
|
||||
llist_del(&new_msg->list);
|
||||
/* up to the caller to free() it */
|
||||
if (exclude_msg) {
|
||||
/* re-add the temporarily removed message */
|
||||
__bts_smscb_add(chan_state, new_msg);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* success! */
|
||||
if (exclude_msg) {
|
||||
LOG_BTS(chan_state->bts, DCBS, LOGL_INFO, "%s Replaced MsgId=0x%04x/Serial=0x%04x, "
|
||||
"pages(%u -> %u), period(%u -> %u), num_bcast(%u -> %u)\n",
|
||||
bts_smscb_chan_state_name(chan_state),
|
||||
new_msg->input.msg_id, new_msg->input.serial_nr,
|
||||
exclude_msg->num_pages, new_msg->num_pages,
|
||||
exclude_msg->input.rep_period, new_msg->input.rep_period,
|
||||
exclude_msg->input.num_bcast_req, new_msg->input.num_bcast_req);
|
||||
append_bcast_compl(r_state, chan_state->bts, exclude_msg);
|
||||
talloc_free(exclude_msg);
|
||||
} else
|
||||
LOG_BTS(chan_state->bts, DCBS, LOGL_INFO, "%s Added %s\n",
|
||||
bts_smscb_chan_state_name(chan_state), bts_smscb_msg2str(new_msg));
|
||||
|
||||
/* replace array with new one */
|
||||
talloc_free(chan_state->sched_arr);
|
||||
chan_state->sched_arr = arr;
|
||||
chan_state->sched_arr_size = rc;
|
||||
chan_state->next_idx = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int bts_rx_write_replace(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
|
||||
struct response_state *r_state, void *priv)
|
||||
{
|
||||
const struct osmo_cbsp_write_replace *wrepl = &dec->u.write_replace;
|
||||
bool extended_cbch = wrepl->u.cbs.channel_ind;
|
||||
struct bts_smscb_chan_state *chan_state = bts_get_smscb_chan(bts, extended_cbch);
|
||||
struct bts_smscb_message *smscb;
|
||||
int rc;
|
||||
|
||||
if (!wrepl->is_cbs) {
|
||||
LOG_BTS(bts, DCBS, LOGL_ERROR, "(Primary) Emergency Message not supported\n");
|
||||
return -CBSP_CAUSE_CB_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/* check if cell has a CBCH at all */
|
||||
if (!gsm_bts_get_cbch(bts))
|
||||
return -CBSP_CAUSE_CB_NOT_SUPPORTED;
|
||||
|
||||
/* check for duplicate */
|
||||
if (bts_find_smscb(chan_state, wrepl->msg_id, wrepl->new_serial_nr))
|
||||
return -CBSP_CAUSE_MSG_REF_ALREADY_USED;
|
||||
|
||||
if (!wrepl->old_serial_nr) { /* new message */
|
||||
/* create new message */
|
||||
smscb = bts_smscb_msg_from_wrepl(bts, wrepl);
|
||||
if (!smscb)
|
||||
return -CBSP_CAUSE_BSC_MEMORY_EXCEEDED;
|
||||
/* check if scheduling permits this additional message */
|
||||
rc = bts_try_write_replace(chan_state, smscb, NULL, r_state);
|
||||
if (rc < 0) {
|
||||
talloc_free(smscb);
|
||||
return -CBSP_CAUSE_BSC_CAPACITY_EXCEEDED;
|
||||
}
|
||||
} else { /* modify / replace existing message */
|
||||
struct bts_smscb_message *smscb_old;
|
||||
/* find existing message */
|
||||
smscb_old = bts_find_smscb(chan_state, wrepl->msg_id, *wrepl->old_serial_nr);
|
||||
if (!smscb_old)
|
||||
return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED;
|
||||
/* create new message */
|
||||
smscb = bts_smscb_msg_from_wrepl(bts, wrepl);
|
||||
if (!smscb)
|
||||
return -CBSP_CAUSE_BSC_MEMORY_EXCEEDED;
|
||||
/* check if scheduling permits this modified message */
|
||||
rc = bts_try_write_replace(chan_state, smscb, smscb_old, r_state);
|
||||
if (rc < 0) {
|
||||
talloc_free(smscb);
|
||||
return -CBSP_CAUSE_BSC_CAPACITY_EXCEEDED;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bts_rx_kill(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
|
||||
struct response_state *r_state, void *priv)
|
||||
{
|
||||
const struct osmo_cbsp_kill *kill = &dec->u.kill;
|
||||
struct bts_smscb_chan_state *chan_state;
|
||||
struct bts_smscb_message *smscb;
|
||||
bool extended = false;
|
||||
|
||||
if (kill->channel_ind && *kill->channel_ind == 0x01)
|
||||
extended = true;
|
||||
chan_state = bts_get_smscb_chan(bts, extended);
|
||||
|
||||
/* Find message by msg_id + old_serial_nr */
|
||||
smscb = bts_find_smscb(chan_state, kill->msg_id, kill->old_serial_nr);
|
||||
if (!smscb)
|
||||
return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED;
|
||||
|
||||
/* Remove it */
|
||||
bts_smscb_del(smscb, chan_state, "KILL");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bts_rx_reset(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
|
||||
struct response_state *r_state, void *priv)
|
||||
{
|
||||
struct bts_smscb_chan_state *chan_state;
|
||||
struct bts_smscb_message *smscb, *smscb2;
|
||||
|
||||
/* remove all SMSCB from CBCH BASIC this BTS */
|
||||
chan_state = bts_get_smscb_chan(bts, false);
|
||||
llist_for_each_entry_safe(smscb, smscb2, &chan_state->messages, list)
|
||||
bts_smscb_del(smscb, chan_state, "RESET");
|
||||
|
||||
/* remove all SMSCB from CBCH EXTENDED this BTS */
|
||||
chan_state = bts_get_smscb_chan(bts, true);
|
||||
llist_for_each_entry_safe(smscb, smscb2, &chan_state->messages, list)
|
||||
bts_smscb_del(smscb, chan_state, "RESET");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* Receive of CBSP from CBC
|
||||
*********************************************************************************/
|
||||
|
||||
static int cbsp_rx_write_replace(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
|
||||
{
|
||||
const struct osmo_cbsp_write_replace *wrepl = &dec->u.write_replace;
|
||||
struct gsm_network *net = cbc->net;
|
||||
struct response_state *r_state = talloc_zero(cbc, struct response_state);
|
||||
struct osmo_cbsp_decoded *resp;
|
||||
enum cbsp_channel_ind channel_ind;
|
||||
int rc;
|
||||
|
||||
LOGP(DCBS, LOGL_INFO, "CBSP Rx WRITE_REPLACE (%s)\n", wrepl->is_cbs ? "CBS" : "EMERGENCY");
|
||||
|
||||
rc = cbsp_per_bts(net, r_state, &dec->u.write_replace.cell_list,
|
||||
bts_rx_write_replace, dec, NULL);
|
||||
/* generate response */
|
||||
if (rc < 0) {
|
||||
resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_WRITE_REPLACE_FAIL);
|
||||
struct osmo_cbsp_write_replace_failure *fail = &resp->u.write_replace_fail;
|
||||
fail->msg_id = wrepl->msg_id;
|
||||
fail->new_serial_nr = wrepl->new_serial_nr;
|
||||
fail->old_serial_nr = wrepl->old_serial_nr;
|
||||
llist_replace_head(&fail->fail_list, &r_state->fail);
|
||||
fail->cell_list.id_discr = r_state->success.id_discr;
|
||||
llist_replace_head(&fail->cell_list.list, &r_state->success.list);
|
||||
if (wrepl->is_cbs) {
|
||||
channel_ind = wrepl->u.cbs.channel_ind;
|
||||
fail->channel_ind = &channel_ind;
|
||||
}
|
||||
if (wrepl->old_serial_nr) {
|
||||
fail->num_compl_list.id_discr = r_state->num_completed.id_discr;
|
||||
llist_replace_head(&fail->num_compl_list.list, &r_state->num_completed.list);
|
||||
}
|
||||
} else {
|
||||
resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_WRITE_REPLACE_COMPL);
|
||||
struct osmo_cbsp_write_replace_complete *compl = &resp->u.write_replace_compl;
|
||||
compl->msg_id = wrepl->msg_id;
|
||||
compl->new_serial_nr = wrepl->new_serial_nr;
|
||||
compl->old_serial_nr = wrepl->old_serial_nr;
|
||||
compl->cell_list.id_discr = r_state->success.id_discr;
|
||||
llist_replace_head(&compl->cell_list.list, &r_state->success.list);
|
||||
if (wrepl->is_cbs) {
|
||||
channel_ind = wrepl->u.cbs.channel_ind;
|
||||
compl->channel_ind = &channel_ind;
|
||||
}
|
||||
if (wrepl->old_serial_nr) {
|
||||
compl->num_compl_list.id_discr = r_state->num_completed.id_discr;
|
||||
llist_replace_head(&compl->num_compl_list.list, &r_state->num_completed.list);
|
||||
}
|
||||
}
|
||||
|
||||
cbsp_tx_decoded(cbc, resp);
|
||||
talloc_free(r_state);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int cbsp_rx_keep_alive(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
|
||||
{
|
||||
LOGP(DCBS, LOGL_DEBUG, "CBSP Rx KEEP_ALIVE\n");
|
||||
|
||||
/* FIXME: repetition period */
|
||||
return tx_cbsp_keepalive_compl(cbc);
|
||||
}
|
||||
|
||||
static int cbsp_rx_kill(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
|
||||
{
|
||||
const struct osmo_cbsp_kill *kill = &dec->u.kill;
|
||||
struct gsm_network *net = cbc->net;
|
||||
struct response_state *r_state = talloc_zero(cbc, struct response_state);
|
||||
struct osmo_cbsp_decoded *resp;
|
||||
int rc;
|
||||
|
||||
LOGP(DCBS, LOGL_DEBUG, "CBSP Rx KILL\n");
|
||||
|
||||
rc = cbsp_per_bts(net, r_state, &dec->u.kill.cell_list, bts_rx_kill, dec, NULL);
|
||||
if (rc < 0) {
|
||||
resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KILL_FAIL);
|
||||
struct osmo_cbsp_kill_failure *fail = &resp->u.kill_fail;
|
||||
fail->msg_id = kill->msg_id;
|
||||
fail->old_serial_nr = kill->old_serial_nr;
|
||||
fail->channel_ind = kill->channel_ind;
|
||||
llist_replace_head(&fail->fail_list, &r_state->fail);
|
||||
|
||||
fail->cell_list.id_discr = r_state->success.id_discr;
|
||||
llist_replace_head(&fail->cell_list.list, &r_state->success.list);
|
||||
|
||||
fail->num_compl_list.id_discr = r_state->num_completed.id_discr;
|
||||
llist_replace_head(&fail->num_compl_list.list, &r_state->num_completed.list);
|
||||
} else {
|
||||
resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KILL_COMPL);
|
||||
struct osmo_cbsp_kill_complete *compl = &resp->u.kill_compl;
|
||||
compl->msg_id = kill->msg_id;
|
||||
compl->old_serial_nr = kill->old_serial_nr;
|
||||
compl->channel_ind = kill->channel_ind;
|
||||
|
||||
compl->cell_list.id_discr = r_state->success.id_discr;
|
||||
llist_replace_head(&compl->cell_list.list, &r_state->success.list);
|
||||
|
||||
compl->num_compl_list.id_discr = r_state->num_completed.id_discr;
|
||||
llist_replace_head(&compl->num_compl_list.list, &r_state->num_completed.list);
|
||||
}
|
||||
|
||||
cbsp_tx_decoded(cbc, resp);
|
||||
talloc_free(r_state);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int cbsp_rx_reset(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
|
||||
{
|
||||
struct gsm_network *net = cbc->net;
|
||||
struct response_state *r_state = talloc_zero(cbc, struct response_state);
|
||||
struct osmo_cbsp_decoded *resp;
|
||||
int rc;
|
||||
|
||||
LOGP(DCBS, LOGL_DEBUG, "CBSP Rx RESET\n");
|
||||
|
||||
rc = cbsp_per_bts(net, r_state, &dec->u.reset.cell_list, bts_rx_reset, dec, NULL);
|
||||
if (rc < 0) {
|
||||
resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESET_FAIL);
|
||||
struct osmo_cbsp_reset_failure *fail = &resp->u.reset_fail;
|
||||
llist_replace_head(&fail->fail_list, &r_state->fail);
|
||||
|
||||
fail->cell_list.id_discr = r_state->success.id_discr;
|
||||
llist_replace_head(&fail->cell_list.list, &r_state->success.list);
|
||||
} else {
|
||||
resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESET_COMPL);
|
||||
struct osmo_cbsp_reset_complete *compl = &resp->u.reset_compl;
|
||||
if (dec->u.reset.cell_list.id_discr == CELL_IDENT_BSS) {
|
||||
/* replace the list of individual cell identities with CELL_IDENT_BSS */
|
||||
compl->cell_list.id_discr = CELL_IDENT_BSS;
|
||||
/* no need to free success_list entries, hierarchical talloc works */
|
||||
} else {
|
||||
compl->cell_list.id_discr = r_state->success.id_discr;
|
||||
llist_replace_head(&compl->cell_list.list, &r_state->success.list);
|
||||
}
|
||||
}
|
||||
cbsp_tx_decoded(cbc, resp);
|
||||
talloc_free(r_state);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*! process an incoming, already decoded CBSP message from the CBC.
|
||||
* \param[in] cbc link to the CBC
|
||||
* \param[in] dec decoded CBSP message structure. Ownership not transferred.
|
||||
* \returns 0 on success; negative on error. */
|
||||
int cbsp_rx_decoded(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
switch (dec->msg_type) {
|
||||
case CBSP_MSGT_WRITE_REPLACE: /* create or modify message */
|
||||
rc = cbsp_rx_write_replace(cbc, dec);
|
||||
break;
|
||||
case CBSP_MSGT_KEEP_ALIVE: /* solicit an acknowledgement */
|
||||
rc = cbsp_rx_keep_alive(cbc, dec);
|
||||
break;
|
||||
case CBSP_MSGT_KILL: /* remove message */
|
||||
rc = cbsp_rx_kill(cbc, dec);
|
||||
break;
|
||||
case CBSP_MSGT_RESET: /* stop broadcasting of all messages */
|
||||
rc = cbsp_rx_reset(cbc, dec);
|
||||
break;
|
||||
case CBSP_MSGT_LOAD_QUERY:
|
||||
case CBSP_MSGT_MSG_STATUS_QUERY:
|
||||
case CBSP_MSGT_SET_DRX:
|
||||
LOGP(DCBS, LOGL_ERROR, "Received Unimplemented CBSP Message Type %s",
|
||||
get_value_string(cbsp_msg_type_names, dec->msg_type));
|
||||
/* we should implement those eventually */
|
||||
break;
|
||||
default:
|
||||
LOGP(DCBS, LOGL_ERROR, "Received Unknown/Unexpected CBSP Message Type %s",
|
||||
get_value_string(cbsp_msg_type_names, dec->msg_type));
|
||||
break;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*********************************************************************************
|
||||
* VTY Interface (Introspection)
|
||||
*********************************************************************************/
|
||||
|
||||
static void vty_dump_smscb_chan_state(struct vty *vty, const struct bts_smscb_chan_state *cs)
|
||||
{
|
||||
const struct bts_smscb_message *sm;
|
||||
|
||||
vty_out(vty, "%s CBCH:%s", cs == &cs->bts->cbch_basic ? "BASIC" : "EXTENDED", VTY_NEWLINE);
|
||||
|
||||
vty_out(vty, " MsgId | SerNo | Pg | Category | Perd | #Tx | #Req | DCS%s", VTY_NEWLINE);
|
||||
vty_out(vty, "-------|-------|----|---------------|------|------|------|----%s", VTY_NEWLINE);
|
||||
llist_for_each_entry(sm, &cs->messages, list) {
|
||||
vty_out(vty, " %04x | %04x | %2u | %13s | %4u | %4u | %4u | %02x%s",
|
||||
sm->input.msg_id, sm->input.serial_nr, sm->num_pages,
|
||||
get_value_string(cbsp_category_names, sm->input.category),
|
||||
sm->input.rep_period, sm->bcast_count, sm->input.num_bcast_req,
|
||||
sm->input.dcs, VTY_NEWLINE);
|
||||
}
|
||||
vty_out(vty, "%s", VTY_NEWLINE);
|
||||
}
|
||||
|
||||
DEFUN(bts_show_cbs, bts_show_cbs_cmd,
|
||||
"show bts <0-255> smscb [(basic|extended)]",
|
||||
SHOW_STR "Display information about a BTS\n" "BTS number\n"
|
||||
"SMS Cell Broadcast State\n"
|
||||
"Show only information related to CBCH BASIC\n"
|
||||
"Show only information related to CBCH EXTENDED\n")
|
||||
{
|
||||
struct gsm_network *net = gsmnet_from_vty(vty);
|
||||
int bts_nr = atoi(argv[0]);
|
||||
struct gsm_bts *bts;
|
||||
|
||||
if (bts_nr >= net->num_bts) {
|
||||
vty_out(vty, "%% can't find BTS '%s'%s", argv[0], VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
bts = gsm_bts_num(net, bts_nr);
|
||||
|
||||
if (argc < 2 || !strcmp(argv[1], "basic"))
|
||||
vty_dump_smscb_chan_state(vty, &bts->cbch_basic);
|
||||
if (argc < 2 || !strcmp(argv[1], "extended"))
|
||||
vty_dump_smscb_chan_state(vty, &bts->cbch_extended);
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
void smscb_vty_init(void)
|
||||
{
|
||||
install_element_ve(&bts_show_cbs_cmd);
|
||||
}
|
|
@ -11,6 +11,7 @@ AM_CFLAGS = \
|
|||
$(LIBOSMOCTRL_CFLAGS) \
|
||||
$(LIBOSMOVTY_CFLAGS) \
|
||||
$(LIBOSMOABIS_CFLAGS) \
|
||||
$(LIBOSMONETIF_CFLAGS) \
|
||||
$(LIBOSMOSIGTRAN_CFLAGS) \
|
||||
$(LIBOSMOMGCPCLIENT_CFLAGS) \
|
||||
$(NULL)
|
||||
|
@ -91,11 +92,15 @@ handover_test_LDADD = \
|
|||
$(top_builddir)/src/osmo-bsc/rest_octets.o \
|
||||
$(top_builddir)/src/osmo-bsc/system_information.o \
|
||||
$(top_builddir)/src/osmo-bsc/timeslot_fsm.o \
|
||||
$(top_builddir)/src/osmo-bsc/smscb.o \
|
||||
$(top_builddir)/src/osmo-bsc/cbch_scheduler.o \
|
||||
$(top_builddir)/src/osmo-bsc/cbsp_link.o \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOGSM_LIBS) \
|
||||
$(LIBOSMOCTRL_LIBS) \
|
||||
$(LIBOSMOVTY_LIBS) \
|
||||
$(LIBOSMOABIS_LIBS) \
|
||||
$(LIBOSMONETIF_LIBS) \
|
||||
$(LIBOSMOSIGTRAN_LIBS) \
|
||||
$(LIBOSMOMGCPCLIENT_LIBS) \
|
||||
$(NULL)
|
||||
|
|
Loading…
Reference in New Issue