osmocom-bb/src/host/layer23/src/mobile/gsm322.c

5206 lines
139 KiB
C

/*
* (C) 2010 by Andreas Eversberg <jolly@eversberg.eu>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <stdint.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <time.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/gsm/gsm48.h>
#include <osmocom/core/signal.h>
#include <osmocom/bb/common/logging.h>
#include <osmocom/bb/common/l1ctl.h>
#include <osmocom/bb/common/osmocom_data.h>
#include <osmocom/bb/common/networks.h>
#include <osmocom/bb/mobile/vty.h>
#include <osmocom/bb/mobile/app_mobile.h>
#include <osmocom/bb/common/utils.h>
#include <l1ctl_proto.h>
const char *ba_version = "osmocom BA V1\n";
static void gsm322_cs_timeout(void *arg);
static int gsm322_cs_select(struct osmocom_ms *ms, int index, uint16_t mcc,
uint16_t mnc, int any);
static int gsm322_m_switch_on(struct osmocom_ms *ms, struct msgb *msg);
static void gsm322_any_timeout(void *arg);
static int gsm322_nb_scan(struct osmocom_ms *ms);
static int gsm322_nb_synced(struct gsm322_cellsel *cs, int yes);
static int gsm322_nb_read(struct gsm322_cellsel *cs, int yes);
static int gsm322_c_camp_any_cell(struct osmocom_ms *ms, struct msgb *msg);
static int gsm322_nb_start(struct osmocom_ms *ms, int synced);
static void gsm322_cs_loss(void *arg);
static int gsm322_nb_meas_ind(struct osmocom_ms *ms, uint16_t arfcn,
uint8_t rx_lev);
#define SYNC_RETRIES 1
#define SYNC_RETRIES_SERVING 2
/* time for trying to sync and read BCCH of neighbour cell again
* NOTE: This value is not defined by TS, i think. */
#define GSM58_TRY_AGAIN 30
/* time for reading BCCH of neighbour cell again */
#define GSM58_READ_AGAIN 300
/* number of neighbour cells to monitor */
#define GSM58_NB_NUMBER 6
/* Timeout for reading BCCH of neighbour cells */
#define GSM322_NB_TIMEOUT 2
/* number of neighbour cells to measure for average */
#define RLA_C_NUM 4
/* wait before doing neighbour cell reselecton due to a better cell again */
#define GSM58_RESEL_THRESHOLD 15
#define ARFCN_TEXT_LEN 10
//#define TEST_INCLUDE_SERV
/*
* notes
*/
/* Cell selection process
*
* The process depends on states and events (finites state machine).
*
* During states of cell selection or cell re-selection, the search for a cell
* is performed in two steps:
*
* 1. Measurement of received level of all relevant frequencies (rx-lev)
*
* 2. Receive system information messages of all relevant frequencies
*
* During this process, the results are stored in a list of all frequencies.
* This list is checked whenever a cell is selected. It depends on the results
* if the cell is 'suitable' and 'allowable' to 'camp' on.
*
* This list is also used to generate a list of available networks.
*
* The states are:
*
* - cs->list[0..(1023+299)].xxx for each cell, where
* - flags and rxlev are used to store outcome of cell scanning process
* - sysinfo pointing to sysinfo memory, allocated temporarily
* - cs->selected and cs->sel_* states of the current / last selected cell.
*
*
* There are special states: GSM322_HPLMN_SEARCH, GSM322_PLMN_SEARCH
* and GSM322_ANY_SEARCH:
*
* GSM322_HPLMN_SEARCH is used to find a HPLMN. This is triggered
* by automatic cell selection.
*
* GSM322_PLMN_SEARCH is triggered when network search process is started.
* It will do a complete search. Also it is used before selecting PLMN from list.
*
* GSM322_ANY_SEARCH is similar to GSM322_PLMN_SEARCH, but it is done while
* camping on any cell. If there is a suitable and allowable cell found,
* it is indicated to the PLMN search process.
*
*/
/* PLMN selection process
*
* The PLMN (Public Land Mobile Network = Operator's Network) has two different
* search processes:
*
* 1. Automatic search
*
* 2. Manual search
*
* The process depends on states and events (finites state machine).
*
*/
/* File format of BA list:
*
* uint16_t mcc
* uint16_t mcc
* uint8_t freq[128+38];
* where frequency 0 is bit 0 of first byte
*
* If not end-of-file, the next BA list is stored.
*/
/* List of lists:
*
* * subscr->plmn_list
*
* The "PLMN Selector list" stores preferred networks to select during PLMN
* search process. This list is also stored in the SIM.
*
* * subscr->plmn_na
*
* The "forbidden PLMNs" list stores all networks that rejected us. The stored
* network will not be used when searching PLMN automatically. This list is
* also stored din the SIM.
*
* * plmn->forbidden_la
*
* The "forbidden LAs for roaming" list stores all location areas where roaming
* was not allowed.
*
* * cs->list[1024+299]
*
* This list stores measurements and cell information during cell selection
* process. It can be used to speed up repeated cell selection.
*
* * cs->ba_list
*
* This list stores a map of frequencies used for a PLMN. If this lists exists
* for a PLMN, it helps to speedup cell scan process.
*
* * plmn->sorted_plmn
*
* This list is generated whenever a PLMN search is started and a list of PLMNs
* is required. It consists of home PLMN, PLMN Selector list, and PLMNs found
* during scan process.
*
*
* Cell re-selection process
*
* The cell re-selection process takes place when a "serving cell" is selected.
* The neighbour cells to be monitored for re-selection are given via SI2* of
* the serving cell.
*
* Therefore a list of neighbour cells is created or updated, when the cell
* allocation is received or changed by the network.
*
* All neighbour cells are monitored, but only up to 6 of the strongest cells
* are synced to, in order to read the BCCH data. A timer is used to re-read
* the BCCH data after 5 minutes. This timer is also used if sync or read
* fails.
*
* The C1 and C2 criterion is calculated for the currently monitored neighbour
* cells. During this process, a better neighbour cell will trigger cell
* re-selection.
*
* The cell re-selection is similar to the cell selection process, except that
* only neighbour cells are searched in order of their quality criterion C2.
*
* During camping, and monitoring neighbour cells, it is possible to enter
* dedicated mode at any time.
*
*/
/*
* event messages
*/
static const struct value_string gsm322_event_names[] = {
{ GSM322_EVENT_SWITCH_ON, "EVENT_SWITCH_ON" },
{ GSM322_EVENT_SWITCH_OFF, "EVENT_SWITCH_OFF" },
{ GSM322_EVENT_SIM_INSERT, "EVENT_SIM_INSERT" },
{ GSM322_EVENT_SIM_REMOVE, "EVENT_SIM_REMOVE" },
{ GSM322_EVENT_REG_FAILED, "EVENT_REG_FAILED" },
{ GSM322_EVENT_ROAMING_NA, "EVENT_ROAMING_NA" },
{ GSM322_EVENT_INVALID_SIM, "EVENT_INVALID_SIM" },
{ GSM322_EVENT_REG_SUCCESS, "EVENT_REG_SUCCESS" },
{ GSM322_EVENT_NEW_PLMN, "EVENT_NEW_PLMN" },
{ GSM322_EVENT_ON_PLMN, "EVENT_ON_PLMN" },
{ GSM322_EVENT_PLMN_SEARCH_START,"EVENT_PLMN_SEARCH_START" },
{ GSM322_EVENT_PLMN_SEARCH_END, "EVENT_PLMN_SEARCH_END" },
{ GSM322_EVENT_USER_RESEL, "EVENT_USER_RESEL" },
{ GSM322_EVENT_PLMN_AVAIL, "EVENT_PLMN_AVAIL" },
{ GSM322_EVENT_CHOOSE_PLMN, "EVENT_CHOOSE_PLMN" },
{ GSM322_EVENT_SEL_MANUAL, "EVENT_SEL_MANUAL" },
{ GSM322_EVENT_SEL_AUTO, "EVENT_SEL_AUTO" },
{ GSM322_EVENT_CELL_FOUND, "EVENT_CELL_FOUND" },
{ GSM322_EVENT_NO_CELL_FOUND, "EVENT_NO_CELL_FOUND" },
{ GSM322_EVENT_LEAVE_IDLE, "EVENT_LEAVE_IDLE" },
{ GSM322_EVENT_RET_IDLE, "EVENT_RET_IDLE" },
{ GSM322_EVENT_CELL_RESEL, "EVENT_CELL_RESEL" },
{ GSM322_EVENT_SYSINFO, "EVENT_SYSINFO" },
{ GSM322_EVENT_HPLMN_SEARCH, "EVENT_HPLMN_SEARCH" },
{ 0, NULL }
};
const char *get_event_name(int value)
{
return get_value_string(gsm322_event_names, value);
}
/* allocate a 03.22 event message */
struct msgb *gsm322_msgb_alloc(int msg_type)
{
struct msgb *msg;
struct gsm322_msg *gm;
msg = msgb_alloc_headroom(sizeof(*gm), 0, "GSM 03.22 event");
if (!msg)
return NULL;
gm = (struct gsm322_msg *)msgb_put(msg, sizeof(*gm));
gm->msg_type = msg_type;
return msg;
}
/* queue PLMN selection message */
int gsm322_plmn_sendmsg(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
msgb_enqueue(&plmn->event_queue, msg);
return 0;
}
/* queue cell selection message */
int gsm322_cs_sendmsg(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
msgb_enqueue(&cs->event_queue, msg);
return 0;
}
/*
* support
*/
uint16_t index2arfcn(int index)
{
if (index >= 1024)
return (index-1024+512) | ARFCN_PCS;
return index;
}
int arfcn2index(uint16_t arfcn)
{
int is_pcs = arfcn & ARFCN_PCS;
arfcn &= ~ARFCN_FLAG_MASK;
if ((is_pcs) && (arfcn >= 512) && (arfcn <= 810))
return (arfcn & 1023)-512+1024;
return arfcn & 1023;
}
static char *bargraph(int value, int min, int max)
{
static char bar[128];
/* shift value to the range of min..max */
if (value < min)
value = 0;
else if (value > max)
value = max - min;
else
value -= min;
/* Prevent 'bar' buffer over-/under-run */
OSMO_ASSERT(value >= 0 && value < 128);
/* Prevent calling memset() with zero length */
if (value == 0)
return "";
memset(bar, '=', value);
bar[value] = '\0';
return bar;
}
static int class_of_band(struct osmocom_ms *ms, int band)
{
struct gsm_settings *set = &ms->settings;
switch (band) {
case GSM_BAND_450:
case GSM_BAND_480:
return set->class_400;
break;
case GSM_BAND_850:
return set->class_850;
break;
case GSM_BAND_1800:
return set->class_dcs;
break;
case GSM_BAND_1900:
return set->class_pcs;
break;
}
return set->class_900;
}
char *gsm_print_rxlev(uint8_t rxlev)
{
static char string[6];
if (rxlev == 0)
return "<=-110";
if (rxlev >= 63)
return ">=-47";
sprintf(string, "-%d", 110 - rxlev);
return string;
}
/* GSM 05.08 6.4 (special class 3 DCS 1800 MS case is omitted ) */
static int16_t calculate_c1(int log, int8_t rla_c, int8_t rxlev_acc_min,
int8_t ms_txpwr_max_cch, int8_t p)
{
int16_t a, b, c1, max_b_0;
a = rla_c - rxlev_acc_min;
b = ms_txpwr_max_cch - p;
max_b_0 = (b > 0) ? b : 0;
c1 = a - max_b_0;
LOGP(log, LOGL_INFO, "A (RLA_C (%d) - RXLEV_ACC_MIN (%d)) = %d\n",
rla_c, rxlev_acc_min, a);
LOGP(log, LOGL_INFO, "B (MS_TXPWR_MAX_CCH (%d) - p (%d)) = %d\n",
ms_txpwr_max_cch, p, b);
LOGP(log, LOGL_INFO, "C1 (A - MAX(B,0)) = %d\n", c1);
return c1;
}
static int16_t calculate_c2(int16_t c1, int serving, int last_serving,
int cell_resel_param_ind, uint8_t cell_resel_off, int t,
uint8_t penalty_time, uint8_t temp_offset) {
int16_t c2;
c2 = c1;
/* no reselect parameters. same process for serving and neighbour cells */
if (!cell_resel_param_ind) {
LOGP(DNB, LOGL_INFO, "C2 = C1 = %d (because no extended "
"re-selection parameters available)\n", c2);
return c2;
}
/* special case, if PENALTY_TIME is '11111' */
if (penalty_time == 31) {
c2 -= (cell_resel_off << 1);
LOGP(DNB, LOGL_INFO, "C2 = C1 - CELL_RESELECT_OFFSET (%d) = %d "
"(special case)\n", cell_resel_off, c2);
return c2;
}
c2 += (cell_resel_off << 1);
/* parameters for serving cell */
if (serving) {
LOGP(DNB, LOGL_INFO, "C2 = C1 + CELL_RESELECT_OFFSET (%d) = %d "
"(serving cell)\n", cell_resel_off, c2);
return c2;
}
/* the cell is the last serving cell */
if (last_serving) {
LOGP(DNB, LOGL_INFO, "C2 = C1 + CELL_RESELECT_OFFSET (%d) = %d "
"(last serving cell)\n", cell_resel_off, c2);
return c2;
}
/* penalty time reached */
if (t >= (penalty_time + 1) * 20) {
LOGP(DNB, LOGL_INFO, "C2 = C1 + CELL_RESELECT_OFFSET (%d) = %d "
"(PENALTY_TIME reached)\n", cell_resel_off, c2);
return c2;
}
/* penalty time not reached, subtract temporary offset */
if (temp_offset < 7)
c2 -= temp_offset * 10;
else
c2 = -1000; /* infinite */
LOGP(DNB, LOGL_INFO, "C2 = C1 + CELL_RESELECT_OFFSET (%d) = %d "
"(PENALTY_TIME not reached, %d seconds left)\n", cell_resel_off,
c2, (penalty_time + 1) * 20 - t);
return c2;
}
static int gsm322_sync_to_cell(struct gsm322_cellsel *cs,
struct gsm322_neighbour * neighbour, int camping)
{
struct osmocom_ms *ms = cs->ms;
struct gsm48_sysinfo *s = cs->si;
struct rx_meas_stat *meas = &ms->meas;
if (cs->sync_pending) {
LOGP(DCS, LOGL_INFO, "Sync to ARFCN=%s, but there is a sync "
"already pending\n",gsm_print_arfcn(cs->arfcn));
return 0;
}
cs->ccch_state = GSM322_CCCH_ST_INIT;
if (s && s->si3) {
if (s->ccch_conf == 1) {
LOGP(DCS, LOGL_INFO, "Sync to ARFCN=%s rxlev=%s "
"(Sysinfo, ccch mode COMB)\n",
gsm_print_arfcn(cs->arfcn),
gsm_print_rxlev(cs->list[cs->arfci].rxlev));
cs->ccch_mode = CCCH_MODE_COMBINED;
} else {
LOGP(DCS, LOGL_INFO, "Sync to ARFCN=%s rxlev=%s "
"(Sysinfo, ccch mode NON-COMB)\n",
gsm_print_arfcn(cs->arfcn),
gsm_print_rxlev(cs->list[cs->arfci].rxlev));
cs->ccch_mode = CCCH_MODE_NON_COMBINED;
}
} else {
LOGP(DCS, LOGL_INFO, "Sync to ARFCN=%s rxlev=%s (No sysinfo "
"yet, ccch mode NONE)\n", gsm_print_arfcn(cs->arfcn),
gsm_print_rxlev(cs->list[cs->arfci].rxlev));
cs->ccch_mode = CCCH_MODE_NONE;
}
meas->frames = meas->snr = meas->berr = meas->rxlev = 0;
cs->rxlev_sum_dbm = cs->rxlev_count = 0;
cs->neighbour = neighbour;
if (camping) {
cs->rla_c_dbm = -128;
cs->c12_valid = 0;
/* keep neighbour cells! if they are old, they are re-read
* anyway, because re-read timer has expired. */
}
cs->sync_pending = 1;
l1ctl_tx_reset_req(ms, L1CTL_RES_T_FULL);
return l1ctl_tx_fbsb_req(ms, cs->arfcn,
L1CTL_FBSB_F_FB01SB, 100, 0,
cs->ccch_mode,
cs->list[cs->arfci].rxlev);
}
/* this is called whenever the serving cell is unselectied */
static void gsm322_unselect_cell(struct gsm322_cellsel *cs)
{
if (!cs->selected)
return;
LOGP(DCS, LOGL_INFO, "Unselecting serving cell.\n");
cs->selected = 0;
if (cs->si)
cs->si->si5 = 0; /* unset SI5* */
cs->si = NULL;
memset(&cs->sel_si, 0, sizeof(cs->sel_si));
cs->sel_mcc = cs->sel_mnc = cs->sel_lac = cs->sel_id = 0;
}
/* print to DCS logging */
static void print_dcs(void *priv, const char *fmt, ...)
{
static char buffer[256] = "";
int in = strlen(buffer);
va_list args;
va_start(args, fmt);
vsnprintf(buffer + in, sizeof(buffer) - in - 1, fmt, args);
buffer[sizeof(buffer) - in - 1] = '\0';
va_end(args);
if (buffer[0] && buffer[strlen(buffer) - 1] == '\n') {
LOGP(DCS, LOGL_INFO, "%s", buffer);
buffer[0] = '\0';
}
}
/* del forbidden LA */
int gsm322_del_forbidden_la(struct osmocom_ms *ms, uint16_t mcc,
uint16_t mnc, uint16_t lac)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm322_la_list *la;
llist_for_each_entry(la, &plmn->forbidden_la, entry) {
if (la->mcc == mcc && la->mnc == mnc && la->lac == lac) {
LOGP(DPLMN, LOGL_INFO, "Delete from list of forbidden "
"LAs (mcc=%s, mnc=%s, lac=%04x)\n",
gsm_print_mcc(mcc), gsm_print_mnc(mnc), lac);
llist_del(&la->entry);
talloc_free(la);
return 0;
}
}
return -EINVAL;
}
/* add forbidden LA */
int gsm322_add_forbidden_la(struct osmocom_ms *ms, uint16_t mcc,
uint16_t mnc, uint16_t lac, uint8_t cause)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm322_la_list *la;
LOGP(DPLMN, LOGL_INFO, "Add to list of forbidden LAs "
"(mcc=%s, mnc=%s, lac=%04x)\n", gsm_print_mcc(mcc),
gsm_print_mnc(mnc), lac);
la = talloc_zero(ms, struct gsm322_la_list);
if (!la)
return -ENOMEM;
la->mcc = mcc;
la->mnc = mnc;
la->lac = lac;
la->cause = cause;
llist_add_tail(&la->entry, &plmn->forbidden_la);
return 0;
}
/* search forbidden LA */
int gsm322_is_forbidden_la(struct osmocom_ms *ms, uint16_t mcc, uint16_t mnc,
uint16_t lac)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm322_la_list *la;
llist_for_each_entry(la, &plmn->forbidden_la, entry) {
if (la->mcc == mcc && la->mnc == mnc && la->lac == lac)
return 1;
}
return 0;
}
/* search for PLMN in all BA lists */
static struct gsm322_ba_list *gsm322_find_ba_list(struct gsm322_cellsel *cs,
uint16_t mcc, uint16_t mnc)
{
struct gsm322_ba_list *ba, *ba_found = NULL;
/* search for BA list */
llist_for_each_entry(ba, &cs->ba_list, entry) {
if (ba->mcc == mcc
&& ba->mnc == mnc) {
ba_found = ba;
break;
}
}
return ba_found;
}
/* search available PLMN */
int gsm322_is_plmn_avail_and_allow(struct gsm322_cellsel *cs, uint16_t mcc,
uint16_t mnc)
{
int i;
for (i = 0; i <= 1023+299; i++) {
if ((cs->list[i].flags & GSM322_CS_FLAG_TEMP_AA)
&& cs->list[i].sysinfo
&& cs->list[i].sysinfo->mcc == mcc
&& cs->list[i].sysinfo->mnc == mnc)
return 1;
}
return 0;
}
/* search available HPLMN */
int gsm322_is_hplmn_avail(struct gsm322_cellsel *cs, char *imsi)
{
int i;
for (i = 0; i <= 1023+299; i++) {
if ((cs->list[i].flags & GSM322_CS_FLAG_SYSINFO)
&& cs->list[i].sysinfo
&& gsm_match_mnc(cs->list[i].sysinfo->mcc,
cs->list[i].sysinfo->mnc, imsi))
return 1;
}
return 0;
}
static const struct value_string gsm322_nb_state_names[] = {
{ GSM322_NB_NEW, "new" },
{ GSM322_NB_NOT_SUP, "not sup" },
{ GSM322_NB_RLA_C, "RLA_C" },
{ GSM322_NB_NO_SYNC, "no sync" },
{ GSM322_NB_NO_BCCH, "no BCCH" },
{ GSM322_NB_SYSINFO, "SYSINFO" },
{ 0, NULL }
};
const char *get_nb_state_name(int value)
{
return get_value_string(gsm322_nb_state_names, value);
}
/*
* timer
*/
/*plmn search timer event */
static void plmn_timer_timeout(void *arg)
{
struct gsm322_plmn *plmn = arg;
struct msgb *nmsg;
LOGP(DPLMN, LOGL_INFO, "HPLMN search timer has fired.\n");
/* indicate PLMN selection T timeout */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_HPLMN_SEARCH);
if (!nmsg)
return;
gsm322_plmn_sendmsg(plmn->ms, nmsg);
}
/* start plmn search timer */
static void start_plmn_timer(struct gsm322_plmn *plmn, int secs)
{
LOGP(DPLMN, LOGL_INFO, "Starting HPLMN search timer with %d minutes.\n",
secs / 60);
plmn->timer.cb = plmn_timer_timeout;
plmn->timer.data = plmn;
osmo_timer_schedule(&plmn->timer, secs, 0);
}
/* stop plmn search timer */
static void stop_plmn_timer(struct gsm322_plmn *plmn)
{
if (osmo_timer_pending(&plmn->timer)) {
LOGP(DPLMN, LOGL_INFO, "Stopping pending timer.\n");
osmo_timer_del(&plmn->timer);
}
}
/* start cell selection timer */
void start_cs_timer(struct gsm322_cellsel *cs, int sec, int micro)
{
LOGP(DCS, LOGL_DEBUG, "Starting CS timer with %d seconds.\n", sec);
cs->timer.cb = gsm322_cs_timeout;
cs->timer.data = cs;
osmo_timer_schedule(&cs->timer, sec, micro);
}
/* stop cell selection timer */
static void stop_cs_timer(struct gsm322_cellsel *cs)
{
if (osmo_timer_pending(&cs->timer)) {
LOGP(DCS, LOGL_DEBUG, "stopping pending CS timer.\n");
osmo_timer_del(&cs->timer);
}
}
/* the following timer is used to search again for allowable cell, after
* loss of coverage. (loss of any allowed PLMN) */
/* start any cell selection timer */
void start_any_timer(struct gsm322_cellsel *cs, int sec, int micro)
{
LOGP(DCS, LOGL_DEBUG, "Starting 'any cell selection' timer with %d "
"seconds.\n", sec);
cs->any_timer.cb = gsm322_any_timeout;
cs->any_timer.data = cs;
osmo_timer_schedule(&cs->any_timer, sec, micro);
}
/* stop cell selection timer */
static void stop_any_timer(struct gsm322_cellsel *cs)
{
if (osmo_timer_pending(&cs->any_timer)) {
LOGP(DCS, LOGL_DEBUG, "stopping pending 'any cell selection' "
"timer.\n");
osmo_timer_del(&cs->any_timer);
}
}
/*
* state change
*/
static const struct value_string gsm322_a_state_names[] = {
{ GSM322_A0_NULL, "A0 null"},
{ GSM322_A1_TRYING_RPLMN, "A1 trying RPLMN"},
{ GSM322_A2_ON_PLMN, "A2 on PLMN"},
{ GSM322_A3_TRYING_PLMN, "A3 trying PLMN"},
{ GSM322_A4_WAIT_FOR_PLMN, "A4 wait for PLMN to appear"},
{ GSM322_A5_HPLMN_SEARCH, "A5 HPLMN search"},
{ GSM322_A6_NO_SIM, "A6 no SIM inserted"},
{ 0, NULL }
};
const char *get_a_state_name(int value)
{
return get_value_string(gsm322_a_state_names, value);
}
static const struct value_string gsm322_m_state_names[] = {
{ GSM322_M0_NULL, "M0 null"},
{ GSM322_M1_TRYING_RPLMN, "M1 trying RPLMN"},
{ GSM322_M2_ON_PLMN, "M2 on PLMN"},
{ GSM322_M3_NOT_ON_PLMN, "M3 not on PLMN"},
{ GSM322_M4_TRYING_PLMN, "M4 trying PLMN"},
{ GSM322_M5_NO_SIM, "M5 no SIM inserted"},
{ 0, NULL }
};
const char *get_m_state_name(int value)
{
return get_value_string(gsm322_m_state_names, value);
}
static const struct value_string gsm322_cs_state_names[] = {
{ GSM322_C0_NULL, "C0 null"},
{ GSM322_C1_NORMAL_CELL_SEL, "C1 normal cell selection"},
{ GSM322_C2_STORED_CELL_SEL, "C2 stored cell selection"},
{ GSM322_C3_CAMPED_NORMALLY, "C3 camped normally"},
{ GSM322_C4_NORMAL_CELL_RESEL, "C4 normal cell re-selection"},
{ GSM322_C5_CHOOSE_CELL, "C5 choose cell"},
{ GSM322_C6_ANY_CELL_SEL, "C6 any cell selection"},
{ GSM322_C7_CAMPED_ANY_CELL, "C7 camped on any cell"},
{ GSM322_C8_ANY_CELL_RESEL, "C8 any cell re-selection"},
{ GSM322_C9_CHOOSE_ANY_CELL, "C9 choose any cell"},
{ GSM322_CONNECTED_MODE_1, "connected mode 1"},
{ GSM322_CONNECTED_MODE_2, "connected mode 2"},
{ GSM322_PLMN_SEARCH, "PLMN search"},
{ GSM322_HPLMN_SEARCH, "HPLMN search"},
{ GSM322_ANY_SEARCH, "ANY search"},
{ 0, NULL }
};
const char *get_cs_state_name(int value)
{
return get_value_string(gsm322_cs_state_names, value);
}
/* new automatic PLMN search state */
static void new_a_state(struct gsm322_plmn *plmn, int state)
{
if (plmn->ms->settings.plmn_mode != PLMN_MODE_AUTO) {
LOGP(DPLMN, LOGL_FATAL, "not in auto mode, please fix!\n");
return;
}
stop_plmn_timer(plmn);
LOGP(DPLMN, LOGL_INFO, "new state '%s' -> '%s'\n",
get_a_state_name(plmn->state), get_a_state_name(state));
plmn->state = state;
}
/* new manual PLMN search state */
static void new_m_state(struct gsm322_plmn *plmn, int state)
{
if (plmn->ms->settings.plmn_mode != PLMN_MODE_MANUAL) {
LOGP(DPLMN, LOGL_FATAL, "not in manual mode, please fix!\n");
return;
}
LOGP(DPLMN, LOGL_INFO, "new state '%s' -> '%s'\n",
get_m_state_name(plmn->state), get_m_state_name(state));
plmn->state = state;
}
/* new Cell selection state */
static void new_c_state(struct gsm322_cellsel *cs, int state)
{
LOGP(DCS, LOGL_INFO, "new state '%s' -> '%s'\n",
get_cs_state_name(cs->state), get_cs_state_name(state));
/* stop cell selection timer, if running */
stop_cs_timer(cs);
/* stop scanning of power measurement */
if (cs->powerscan) {
LOGP(DCS, LOGL_INFO, "changing state while power scanning\n");
l1ctl_tx_reset_req(cs->ms, L1CTL_RES_T_FULL);
cs->powerscan = 0;
}
cs->state = state;
}
/*
* list of PLMNs
*/
/* 4.4.3 create sorted list of PLMN
*
* the source of entries are
*
* - HPLMN
* - entries found in the SIM's PLMN Selector list
* - scanned PLMNs above -85 dB (random order)
* - scanned PLMNs below or equal -85 (by received level)
*
* NOTE:
*
* The list only includes networks found at last scan.
*
* The list always contains HPLMN if available, even if not used by PLMN
* search process at some conditions.
*
* The list contains all PLMNs even if not allowed, so entries have to be
* removed when selecting from the list. (In case we use manual cell selection,
* we need to provide non-allowed networks also.)
*/
static int gsm322_sort_list(struct osmocom_ms *ms)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm_subscriber *subscr = &ms->subscr;
struct gsm_sub_plmn_list *sim_entry;
struct gsm_sub_plmn_na *na_entry;
struct llist_head temp_list;
struct gsm322_plmn_list *temp, *found;
struct llist_head *lh, *lh2;
int i, entries, move;
uint8_t search = 0;
/* flush list */
llist_for_each_safe(lh, lh2, &plmn->sorted_plmn) {
llist_del(lh);
talloc_free(lh);
}
/* Create a temporary list of all networks */
INIT_LLIST_HEAD(&temp_list);
for (i = 0; i <= 1023+299; i++) {
if (!(cs->list[i].flags & GSM322_CS_FLAG_TEMP_AA)
|| !cs->list[i].sysinfo)
continue;
/* search if network has multiple cells */
found = NULL;
llist_for_each_entry(temp, &temp_list, entry) {
if (temp->mcc == cs->list[i].sysinfo->mcc
&& temp->mnc == cs->list[i].sysinfo->mnc) {
found = temp;
break;
}
}
/* update or create */
if (found) {
if (cs->list[i].rxlev > found->rxlev)
found->rxlev = cs->list[i].rxlev;
} else {
temp = talloc_zero(ms, struct gsm322_plmn_list);
if (!temp)
return -ENOMEM;
temp->mcc = cs->list[i].sysinfo->mcc;
temp->mnc = cs->list[i].sysinfo->mnc;
temp->rxlev = cs->list[i].rxlev;
llist_add_tail(&temp->entry, &temp_list);
}
}
/* move Home PLMN, if in list, else add it */
if (subscr->sim_valid) {
found = NULL;
llist_for_each_entry(temp, &temp_list, entry) {
if (gsm_match_mnc(temp->mcc, temp->mnc, subscr->imsi)) {
found = temp;
break;
}
}
if (found) {
/* move */
llist_del(&found->entry);
llist_add_tail(&found->entry, &plmn->sorted_plmn);
}
}
/* move entries if in SIM's PLMN Selector list */
llist_for_each_entry(sim_entry, &subscr->plmn_list, entry) {
found = NULL;
llist_for_each_entry(temp, &temp_list, entry) {
if (temp->mcc == sim_entry->mcc
&& temp->mnc == sim_entry->mnc) {
found = temp;
break;
}
}
if (found) {
llist_del(&found->entry);
llist_add_tail(&found->entry, &plmn->sorted_plmn);
}
}
/* move PLMN above -85 dBm in random order */
entries = 0;
llist_for_each_entry(temp, &temp_list, entry) {
if (rxlev2dbm(temp->rxlev) > -85)
entries++;
}
while(entries) {
move = layer23_random() % entries;
i = 0;
llist_for_each_entry(temp, &temp_list, entry) {
if (rxlev2dbm(temp->rxlev) > -85) {
if (i == move) {
llist_del(&temp->entry);
llist_add_tail(&temp->entry,
&plmn->sorted_plmn);
break;
}
i++;
}
}
entries--;
}
/* move other PLMN in decreasing order */
while(1) {
found = NULL;
llist_for_each_entry(temp, &temp_list, entry) {
if (!found
|| temp->rxlev > search) {
search = temp->rxlev;
found = temp;
}
}
if (!found)
break;
llist_del(&found->entry);
llist_add_tail(&found->entry, &plmn->sorted_plmn);
}
/* mark forbidden PLMNs, if in list of forbidden networks */
i = 0;
llist_for_each_entry(temp, &plmn->sorted_plmn, entry) {
llist_for_each_entry(na_entry, &subscr->plmn_na, entry) {
if (temp->mcc == na_entry->mcc
&& temp->mnc == na_entry->mnc) {
temp->cause = na_entry->cause;
break;
}
}
LOGP(DPLMN, LOGL_INFO, "Creating Sorted PLMN list. "
"(%02d: mcc %s mnc %s allowed %s rx-lev %s)\n",
i, gsm_print_mcc(temp->mcc),
gsm_print_mnc(temp->mnc), (temp->cause) ? "no ":"yes",
gsm_print_rxlev(temp->rxlev));
i++;
}
gsm322_dump_sorted_plmn(ms);
return 0;
}
/*
* handler for automatic search
*/
/* go On PLMN state */
static int gsm322_a_go_on_plmn(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm_subscriber *subscr = &ms->subscr;
new_a_state(plmn, GSM322_A2_ON_PLMN);
/* start timer, if on VPLMN of home country OR special case */
if (!gsm_match_mnc(plmn->mcc, plmn->mnc, subscr->imsi)
&& (subscr->always_search_hplmn
|| gsm_match_mcc(plmn->mcc, subscr->imsi))
&& subscr->sim_valid && subscr->t6m_hplmn)
start_plmn_timer(plmn, subscr->t6m_hplmn * 360);
else
stop_plmn_timer(plmn);
return 0;
}
/* go to Wait for PLMNs to appear state */
static int gsm322_a_go_wait_for_plmns(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct msgb *nmsg;
struct gsm322_msg *ngm;
new_a_state(plmn, GSM322_A4_WAIT_FOR_PLMN);
/* we must forward this, otherwise "Any cell selection"
* will not start automatically.
*/
nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN);
if (!nmsg)
return -ENOMEM;
ngm = (struct gsm322_msg *) nmsg->data;
ngm->limited = 1;
gsm322_cs_sendmsg(ms, nmsg);
return 0;
}
/* no (more) PLMN in list */
static int gsm322_a_no_more_plmn(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm_subscriber *subscr = &ms->subscr;
struct gsm322_cellsel *cs = &ms->cellsel;
struct msgb *nmsg;
int found;
/* any allowable PLMN available? */
found = gsm322_cs_select(ms, -1, 0, 0, 0);
/* if no PLMN in list:
* this means that we are at a point where we camp on any cell or
* no cell is available. */
if (found < 0) {
if (subscr->plmn_valid) {
LOGP(DPLMN, LOGL_INFO, "Not any PLMN allowable. "
"Do limited search with RPLMN.\n");
plmn->mcc = subscr->plmn_mcc;
plmn->mnc = subscr->plmn_mnc;
} else
if (subscr->sim_valid) {
LOGP(DPLMN, LOGL_INFO, "Not any PLMN allowable. "
"Do limited search with HPLMN.\n");
plmn->mcc = subscr->mcc;
plmn->mnc = subscr->mnc;
} else {
LOGP(DPLMN, LOGL_INFO, "Not any PLMN allowable. "
"Do limited search with no PLMN.\n");
plmn->mcc = 0;
plmn->mnc = 0;
}
return gsm322_a_go_wait_for_plmns(ms, msg);
}
/* select first PLMN in list */
plmn->mcc = cs->list[found].sysinfo->mcc;
plmn->mnc = cs->list[found].sysinfo->mnc;
LOGP(DPLMN, LOGL_INFO, "PLMN available after searching PLMN list "
"(mcc=%s mnc=%s %s, %s)\n",
gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc),
gsm_get_mcc(plmn->mcc), gsm_get_mnc(plmn->mcc, plmn->mnc));
/* indicate New PLMN */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN);
if (!nmsg)
return -ENOMEM;
gsm322_cs_sendmsg(ms, nmsg);
/* go On PLMN */
return gsm322_a_go_on_plmn(ms, msg);
}
/* select first PLMN in list */
static int gsm322_a_sel_first_plmn(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm_subscriber *subscr = &ms->subscr;
struct msgb *nmsg;
struct gsm322_plmn_list *plmn_entry;
struct gsm322_plmn_list *plmn_first = NULL;
int i;
/* generate list */
gsm322_sort_list(ms);
/* select first entry */
i = 0;
llist_for_each_entry(plmn_entry, &plmn->sorted_plmn, entry) {
/* if last selected PLMN was HPLMN, we skip that */
if (gsm_match_mnc(plmn_entry->mcc, plmn_entry->mnc,
subscr->imsi)
&& plmn_entry->mcc == plmn->mcc
&& plmn_entry->mnc == plmn->mnc) {
LOGP(DPLMN, LOGL_INFO, "Skip HPLMN, because it was "
"previously selected.\n");
i++;
continue;
}
/* select first allowed network */
if (!plmn_entry->cause) {
plmn_first = plmn_entry;
break;
}
LOGP(DPLMN, LOGL_INFO, "Skip PLMN (%02d: mcc=%s, mnc=%s), "
"because it is not allowed (cause %d).\n", i,
gsm_print_mcc(plmn_entry->mcc),
gsm_print_mnc(plmn_entry->mnc),
plmn_entry->cause);
i++;
}
plmn->plmn_curr = i;
/* if no PLMN in list */
if (!plmn_first) {
LOGP(DPLMN, LOGL_INFO, "No PLMN in list.\n");
gsm322_a_no_more_plmn(ms, msg);
return 0;
}
LOGP(DPLMN, LOGL_INFO, "Selecting PLMN from list. (%02d: mcc=%s "
"mnc=%s %s, %s)\n", plmn->plmn_curr,
gsm_print_mcc(plmn_first->mcc), gsm_print_mnc(plmn_first->mnc),
gsm_get_mcc(plmn_first->mcc),
gsm_get_mnc(plmn_first->mcc, plmn_first->mnc));
/* set current network */
plmn->mcc = plmn_first->mcc;
plmn->mnc = plmn_first->mnc;
new_a_state(plmn, GSM322_A3_TRYING_PLMN);
/* indicate New PLMN */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN);
if (!nmsg)
return -ENOMEM;
gsm322_cs_sendmsg(ms, nmsg);
return 0;
}
/* select next PLMN in list */
static int gsm322_a_sel_next_plmn(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct msgb *nmsg;
struct gsm322_plmn_list *plmn_entry;
struct gsm322_plmn_list *plmn_next = NULL;
int i, ii;
/* select next entry from list */
i = 0;
ii = plmn->plmn_curr + 1;
llist_for_each_entry(plmn_entry, &plmn->sorted_plmn, entry) {
/* skip previously selected networks */
if (i < ii) {
i++;
continue;
}
/* select next allowed network */
if (!plmn_entry->cause) {
plmn_next = plmn_entry;
break;
}
LOGP(DPLMN, LOGL_INFO, "Skip PLMN (%02d: mcc=%s, mnc=%s), "
"because it is not allowed (cause %d).\n", i,
gsm_print_mcc(plmn_entry->mcc),
gsm_print_mnc(plmn_entry->mnc),
plmn_entry->cause);
i++;
}
plmn->plmn_curr = i;
/* if no more PLMN in list */
if (!plmn_next) {
LOGP(DPLMN, LOGL_INFO, "No more PLMN in list.\n");
gsm322_a_no_more_plmn(ms, msg);
return 0;
}
/* set next network */
plmn->mcc = plmn_next->mcc;
plmn->mnc = plmn_next->mnc;
LOGP(DPLMN, LOGL_INFO, "Selecting PLMN from list. (%02d: mcc=%s "
"mnc=%s %s, %s)\n", plmn->plmn_curr,
gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc),
gsm_get_mcc(plmn->mcc), gsm_get_mnc(plmn->mcc, plmn->mnc));
new_a_state(plmn, GSM322_A3_TRYING_PLMN);
/* indicate New PLMN */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN);
if (!nmsg)
return -ENOMEM;
gsm322_cs_sendmsg(ms, nmsg);
return 0;
}
/* User re-selection event */
static int gsm322_a_user_resel(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm_subscriber *subscr = &ms->subscr;
struct gsm322_plmn_list *plmn_entry;
struct gsm322_plmn_list *plmn_found = NULL;
struct msgb *nmsg;
if (!subscr->sim_valid) {
return 0;
}
/* try again later, if not idle */
if (cs->state == GSM322_CONNECTED_MODE_1
|| cs->state == GSM322_CONNECTED_MODE_2) {
LOGP(DPLMN, LOGL_INFO, "Not idle, rejecting.\n");
return 0;
}
/* search current PLMN in list */
llist_for_each_entry(plmn_entry, &plmn->sorted_plmn, entry) {
if (plmn_entry->mcc == plmn->mcc
&& plmn_entry->mnc == plmn->mnc) {
plmn_found = plmn_entry;
break;
}
}
/* abort if list is empty */
if (!plmn_found) {
LOGP(DPLMN, LOGL_INFO, "Selected PLMN not in list, strange!\n");
return 0;
}
LOGP(DPLMN, LOGL_INFO, "Movin selected PLMN to the bottom of the list "
"and restarting PLMN search process.\n");
/* move entry to end of list */
llist_del(&plmn_found->entry);
llist_add_tail(&plmn_found->entry, &plmn->sorted_plmn);
/* tell MM that we selected a PLMN */
nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_USER_PLMN_SEL);
if (!nmsg)
return -ENOMEM;
gsm48_mmevent_msg(ms, nmsg);
/* select first PLMN in list */
return gsm322_a_sel_first_plmn(ms, msg);
}
/* PLMN becomes available */
static int gsm322_a_plmn_avail(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm_subscriber *subscr = &ms->subscr;
struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
if (subscr->plmn_valid && plmn->mcc == gm->mcc
&& plmn->mnc == gm->mnc) {
struct msgb *nmsg;
new_m_state(plmn, GSM322_A1_TRYING_RPLMN);
LOGP(DPLMN, LOGL_INFO, "Last selected PLMN becomes available "
"again.\n");
/* indicate New PLMN */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN);
if (!nmsg)
return -ENOMEM;
gsm322_cs_sendmsg(ms, nmsg);
return 0;
} else {
/* select first PLMN in list */
LOGP(DPLMN, LOGL_INFO, "Some PLMN became available, start PLMN "
"search process.\n");
return gsm322_a_sel_first_plmn(ms, msg);
}
}
/* loss of radio coverage */
static int gsm322_a_loss_of_radio(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
int found;
/* any allowable PLMN available */
found = gsm322_cs_select(ms, -1, 0, 0, 0);
/* if PLMN in list */
if (found >= 0) {
LOGP(DPLMN, LOGL_INFO, "PLMN available (mcc=%s mnc=%s "
"%s, %s)\n", gsm_print_mcc(
cs->list[found].sysinfo->mcc),
gsm_print_mnc(cs->list[found].sysinfo->mnc),
gsm_get_mcc(cs->list[found].sysinfo->mcc),
gsm_get_mnc(cs->list[found].sysinfo->mcc,
cs->list[found].sysinfo->mnc));
return gsm322_a_sel_first_plmn(ms, msg);
}
LOGP(DPLMN, LOGL_INFO, "PLMN not available after loss of coverage.\n");
return gsm322_a_go_wait_for_plmns(ms, msg);
}
/* MS is switched on OR SIM is inserted OR removed */
static int gsm322_a_switch_on(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm_subscriber *subscr = &ms->subscr;
struct gsm322_plmn *plmn = &ms->plmn;
struct msgb *nmsg;
if (!subscr->sim_valid) {
LOGP(DSUM, LOGL_INFO, "SIM is removed\n");
LOGP(DPLMN, LOGL_INFO, "SIM is removed\n");
new_a_state(plmn, GSM322_A6_NO_SIM);
return 0;
}
/* if there is a registered PLMN */
if (subscr->plmn_valid) {
/* select the registered PLMN */
plmn->mcc = subscr->plmn_mcc;
plmn->mnc = subscr->plmn_mnc;
LOGP(DSUM, LOGL_INFO, "Start search of last registered PLMN "
"(mcc=%s mnc=%s %s, %s)\n", gsm_print_mcc(plmn->mcc),
gsm_print_mnc(plmn->mnc), gsm_get_mcc(plmn->mcc),
gsm_get_mnc(plmn->mcc, plmn->mnc));
LOGP(DPLMN, LOGL_INFO, "Use RPLMN (mcc=%s mnc=%s "
"%s, %s)\n", gsm_print_mcc(plmn->mcc),
gsm_print_mnc(plmn->mnc), gsm_get_mcc(plmn->mcc),
gsm_get_mnc(plmn->mcc, plmn->mnc));
new_a_state(plmn, GSM322_A1_TRYING_RPLMN);
/* indicate New PLMN */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN);
if (!nmsg)
return -ENOMEM;
gsm322_cs_sendmsg(ms, nmsg);
return 0;
}
plmn->mcc = plmn->mnc = 0;
/* initiate search at cell selection */
LOGP(DSUM, LOGL_INFO, "Search for network\n");
LOGP(DPLMN, LOGL_INFO, "Switch on, no RPLMN, start PLMN search "
"first.\n");
nmsg = gsm322_msgb_alloc(GSM322_EVENT_PLMN_SEARCH_START);
if (!nmsg)
return -ENOMEM;
gsm322_cs_sendmsg(ms, nmsg);
return 0;
}
/* MS is switched off */
static int gsm322_a_switch_off(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
new_a_state(plmn, GSM322_A0_NULL);
return 0;
}
static int gsm322_a_sim_insert(struct osmocom_ms *ms, struct msgb *msg)
{
LOGP(DPLMN, LOGL_INFO, "SIM already inserted when switched on.\n");
return 0;
}
/* SIM is removed */
static int gsm322_a_sim_removed(struct osmocom_ms *ms, struct msgb *msg)
{
struct msgb *nmsg;
/* indicate SIM remove to cell selection process */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_SIM_REMOVE);
if (!nmsg)
return -ENOMEM;
gsm322_cs_sendmsg(ms, nmsg);
/* flush list of PLMNs */
gsm_subscr_del_forbidden_plmn(&ms->subscr, 0, 0);
return gsm322_a_switch_on(ms, msg);
}
/* location update response: "Roaming not allowed" */
static int gsm322_a_roaming_na(struct osmocom_ms *ms, struct msgb *msg)
{
/* store in list of forbidden LAs is done in gsm48* */
return gsm322_a_sel_first_plmn(ms, msg);
}
/* On VPLMN of home country and timeout occurs */
static int gsm322_a_hplmn_search_start(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm322_cellsel *cs = &ms->cellsel;
struct msgb *nmsg;
/* try again later, if not idle and not camping */
if (cs->state != GSM322_C3_CAMPED_NORMALLY) {
LOGP(DPLMN, LOGL_INFO, "Not camping normally, wait some more."
"\n");
start_plmn_timer(plmn, 60);
return 0;
}
new_a_state(plmn, GSM322_A5_HPLMN_SEARCH);
/* initiate search at cell selection */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_HPLMN_SEARCH);
if (!nmsg)
return -ENOMEM;
gsm322_cs_sendmsg(ms, nmsg);
return 0;
}
/* manual mode selected */
static int gsm322_a_sel_manual(struct osmocom_ms *ms, struct msgb *msg)
{
struct msgb *nmsg;
/* restart state machine */
gsm322_a_switch_off(ms, msg);
ms->settings.plmn_mode = PLMN_MODE_MANUAL;
gsm322_m_switch_on(ms, msg);
nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_USER_PLMN_SEL);
if (!nmsg)
return -ENOMEM;
gsm48_mmevent_msg(ms, nmsg);
return 0;
}
/*
* handler for manual search
*/
/* display PLMNs and to Not on PLMN */
static int gsm322_m_display_plmns(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
int msg_type = gm->msg_type;
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm_sub_plmn_list *temp;
struct msgb *nmsg;
struct gsm322_msg *ngm;
/* generate list */
gsm322_sort_list(ms);
vty_notify(ms, NULL);
switch (msg_type) {
case GSM322_EVENT_REG_FAILED:
vty_notify(ms, "Failed to register to network %s, %s "
"(%s, %s)\n",
gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc),
gsm_get_mcc(plmn->mcc),
gsm_get_mnc(plmn->mcc, plmn->mnc));
break;
case GSM322_EVENT_NO_CELL_FOUND:
vty_notify(ms, "No cell found for network %s, %s "
"(%s, %s)\n",
gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc),
gsm_get_mcc(plmn->mcc),
gsm_get_mnc(plmn->mcc, plmn->mnc));
break;
case GSM322_EVENT_ROAMING_NA:
vty_notify(ms, "Roaming not allowed to network %s, %s "
"(%s, %s)\n",
gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc),
gsm_get_mcc(plmn->mcc),
gsm_get_mnc(plmn->mcc, plmn->mnc));
break;
}
if (llist_empty(&plmn->sorted_plmn))
vty_notify(ms, "Search network!\n");
else {
vty_notify(ms, "Search or select from network:\n");
llist_for_each_entry(temp, &plmn->sorted_plmn, entry)
vty_notify(ms, " Network %s, %s (%s, %s)\n",
gsm_print_mcc(temp->mcc),
gsm_print_mnc(temp->mnc),
gsm_get_mcc(temp->mcc),
gsm_get_mnc(temp->mcc, temp->mnc));
}
/* go Not on PLMN state */
new_m_state(plmn, GSM322_M3_NOT_ON_PLMN);
/* we must forward this, otherwise "Any cell selection"
* will not start automatically.
* this way we get back to the last PLMN, in case we gained
* our coverage back.
*/
nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN);
if (!nmsg)
return -ENOMEM;
ngm = (struct gsm322_msg *) nmsg->data;
ngm->limited = 1;
gsm322_cs_sendmsg(ms, nmsg);
return 0;
}
/* user starts reselection */
static int gsm322_m_user_resel(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm_subscriber *subscr = &ms->subscr;
struct msgb *nmsg;
/* unselect PLMN. after search, the process will wait until a PLMN is
* selected by the user. this prevents from switching back to the
* last selected PLMN and destroying the list of scanned networks.
*/
plmn->mcc = plmn->mnc = 0;
if (!subscr->sim_valid) {
return 0;
}
/* try again later, if not idle */
if (cs->state == GSM322_CONNECTED_MODE_1
|| cs->state == GSM322_CONNECTED_MODE_2) {
LOGP(DPLMN, LOGL_INFO, "Not idle, rejecting.\n");
return 0;
}
/* initiate search at cell selection */
LOGP(DPLMN, LOGL_INFO, "User re-select, start PLMN search first.\n");
/* tell MM that we selected a PLMN */
nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_USER_PLMN_SEL);
if (!nmsg)
return -ENOMEM;
gsm48_mmevent_msg(ms, nmsg);
/* triffer PLMN search */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_PLMN_SEARCH_START);
if (!nmsg)
return -ENOMEM;
gsm322_cs_sendmsg(ms, nmsg);
return 0;
}
/* MS is switched on OR SIM is inserted OR removed */
static int gsm322_m_switch_on(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm_subscriber *subscr = &ms->subscr;
struct gsm322_plmn *plmn = &ms->plmn;
struct msgb *nmsg;
if (!subscr->sim_valid) {
LOGP(DSUM, LOGL_INFO, "SIM is removed\n");
LOGP(DPLMN, LOGL_INFO, "Switch on without SIM.\n");
new_m_state(plmn, GSM322_M5_NO_SIM);
return 0;
}
/* if there is a registered PLMN */
if (subscr->plmn_valid) {
struct msgb *nmsg;
/* select the registered PLMN */
plmn->mcc = subscr->plmn_mcc;
plmn->mnc = subscr->plmn_mnc;
LOGP(DSUM, LOGL_INFO, "Start search of last registered PLMN "
"(mcc=%s mnc=%s %s, %s)\n", gsm_print_mcc(plmn->mcc),
gsm_print_mnc(plmn->mnc), gsm_get_mcc(plmn->mcc),
gsm_get_mnc(plmn->mcc, plmn->mnc));
LOGP(DPLMN, LOGL_INFO, "Use RPLMN (mcc=%s mnc=%s "
"%s, %s)\n", gsm_print_mcc(plmn->mcc),
gsm_print_mnc(plmn->mnc), gsm_get_mcc(plmn->mcc),
gsm_get_mnc(plmn->mcc, plmn->mnc));
new_m_state(plmn, GSM322_M1_TRYING_RPLMN);
/* indicate New PLMN */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN);
if (!nmsg)
return -ENOMEM;
gsm322_cs_sendmsg(ms, nmsg);
return 0;
}
plmn->mcc = plmn->mnc = 0;
/* initiate search at cell selection */
LOGP(DSUM, LOGL_INFO, "Search for network\n");
LOGP(DPLMN, LOGL_INFO, "Switch on, start PLMN search first.\n");
nmsg = gsm322_msgb_alloc(GSM322_EVENT_PLMN_SEARCH_START);
if (!nmsg)
return -ENOMEM;
gsm322_cs_sendmsg(ms, nmsg);
return 0;
}
/* MS is switched off */
static int gsm322_m_switch_off(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
stop_plmn_timer(plmn);
new_m_state(plmn, GSM322_M0_NULL);
return 0;
}
static int gsm322_m_sim_insert(struct osmocom_ms *ms, struct msgb *msg)
{
LOGP(DPLMN, LOGL_INFO, "SIM already inserted when switched on.\n");
return 0;
}
/* SIM is removed */
static int gsm322_m_sim_removed(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct msgb *nmsg;
stop_plmn_timer(plmn);
/* indicate SIM remove to cell selection process */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_SIM_REMOVE);
if (!nmsg)
return -ENOMEM;
gsm322_cs_sendmsg(ms, nmsg);
/* flush list of PLMNs */
gsm_subscr_del_forbidden_plmn(&ms->subscr, 0, 0);
return gsm322_m_switch_on(ms, msg);
}
/* go to On PLMN state */
static int gsm322_m_go_on_plmn(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm_subscriber *subscr = &ms->subscr;
/* set last registered PLMN */
subscr->plmn_valid = 1;
subscr->plmn_mcc = plmn->mcc;
subscr->plmn_mnc = plmn->mnc;
new_m_state(plmn, GSM322_M2_ON_PLMN);
return 0;
}
/* previously selected PLMN becomes available again */
static int gsm322_m_plmn_avail(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct msgb *nmsg;
new_m_state(plmn, GSM322_M1_TRYING_RPLMN);
LOGP(DPLMN, LOGL_INFO, "Last selected PLMN becomes available again.\n");
/* indicate New PLMN */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN);
if (!nmsg)
return -ENOMEM;
gsm322_cs_sendmsg(ms, nmsg);
return 0;
}
/* the user has selected given PLMN */
static int gsm322_m_choose_plmn(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm_subscriber *subscr = &ms->subscr;
struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
struct msgb *nmsg;
/* use user selection */
plmn->mcc = gm->mcc;
plmn->mnc = gm->mnc;
LOGP(DPLMN, LOGL_INFO, "User selects PLMN. (mcc=%s mnc=%s "
"%s, %s)\n", gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc),
gsm_get_mcc(plmn->mcc), gsm_get_mnc(plmn->mcc, plmn->mnc));
/* if selected PLMN is in list of forbidden PLMNs */
gsm_subscr_del_forbidden_plmn(subscr, plmn->mcc, plmn->mnc);
new_m_state(plmn, GSM322_M4_TRYING_PLMN);
/* tell MM that we selected a PLMN */
nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_USER_PLMN_SEL);
if (!nmsg)
return -ENOMEM;
gsm48_mmevent_msg(ms, nmsg);
/* indicate New PLMN */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN);
if (!nmsg)
return -ENOMEM;
gsm322_cs_sendmsg(ms, nmsg);
return 0;
}
/* auto mode selected */
static int gsm322_m_sel_auto(struct osmocom_ms *ms, struct msgb *msg)
{
struct msgb *nmsg;
/* restart state machine */
gsm322_m_switch_off(ms, msg);
ms->settings.plmn_mode = PLMN_MODE_AUTO;
gsm322_a_switch_on(ms, msg);
nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_USER_PLMN_SEL);
if (!nmsg)
return -ENOMEM;
gsm48_mmevent_msg(ms, nmsg);
return 0;
}
/* if no cell is found in other states than in *_TRYING_* states */
static int gsm322_am_no_cell_found(struct osmocom_ms *ms, struct msgb *msg)
{
struct msgb *nmsg;
/* Tell cell selection process to handle "no cell found". */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_NO_CELL_FOUND);
if (!nmsg)
return -ENOMEM;
gsm322_cs_sendmsg(ms, nmsg);
return 0;
}
/*
* cell scanning process
*/
/* select a suitable and allowable cell */
static int gsm322_cs_select(struct osmocom_ms *ms, int index, uint16_t mcc,
uint16_t mnc, int any)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm_settings *set = &ms->settings;
struct gsm_subscriber *subscr = &ms->subscr;
struct gsm48_sysinfo *s;
int start, end, i, found = -1, power = 0;
uint8_t flags, mask;
uint16_t acc_class;
int16_t c1;
enum gsm_band band;
int class;
/* set our access class depending on the cell selection type */
if (any) {
acc_class = subscr->acc_class | 0x0400; /* add emergency */
LOGP(DCS, LOGL_DEBUG, "Select using access class with "
"Emergency class.\n");
} else {
acc_class = subscr->acc_class;
LOGP(DCS, LOGL_DEBUG, "Select using access class \n");
}
/* flags to match */
mask = GSM322_CS_FLAG_SUPPORT | GSM322_CS_FLAG_POWER
| GSM322_CS_FLAG_SIGNAL | GSM322_CS_FLAG_SYSINFO;
if (cs->state == GSM322_C2_STORED_CELL_SEL
|| cs->state == GSM322_C5_CHOOSE_CELL)
mask |= GSM322_CS_FLAG_BA;
flags = mask; /* all masked flags are required */
/* loop through all scanned frequencies and select cell.
* if an index is given (arfci), we just check this cell only */
if (index >= 0) {
start = end = index;
} else {
start = 0; end = 1023+299;
}
for (i = start; i <= end; i++) {
cs->list[i].flags &= ~GSM322_CS_FLAG_TEMP_AA;
s = cs->list[i].sysinfo;
/* channel has no information for us */
if (!s || (cs->list[i].flags & mask) != flags) {
continue;
}
/* check C1 criteria not fulfilled */
// TODO: class 3 DCS mobile
gsm_arfcn2band_rc(index2arfcn(i), &band);
class = class_of_band(ms, band);
c1 = calculate_c1(DCS, rxlev2dbm(cs->list[i].rxlev),
s->rxlev_acc_min_db,
ms_pwr_dbm(band, s->ms_txpwr_max_cch),
ms_class_gmsk_dbm(band, class));
if (!set->stick && c1 < 0) {
LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: C1 criterion "
"not met. (C1 = %d)\n",
gsm_print_arfcn(index2arfcn(i)), c1);
continue;
}
/* if cell is barred and we don't override */
if (!subscr->acc_barr
&& (cs->list[i].flags & GSM322_CS_FLAG_BARRED)) {
LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is "
"barred.\n", gsm_print_arfcn(index2arfcn(i)));
continue;
}
/* if we have no access to the cell and we don't override */
if (!subscr->acc_barr
&& !(acc_class & (s->class_barr ^ 0xffff))) {
LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Class is "
"barred for our access. (access=%04x "
"barred=%04x)\n",
gsm_print_arfcn(index2arfcn(i)),
acc_class, s->class_barr);
continue;
}
/* store temporary available and allowable flag */
cs->list[i].flags |= GSM322_CS_FLAG_TEMP_AA;
/* if cell is in list of forbidden LAs */
if ((cs->list[i].flags & GSM322_CS_FLAG_FORBIDD)) {
if (!any) {
LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is "
"in list of forbidden LAs. (mcc=%s "
"mnc=%s lai=%04x)\n",
gsm_print_arfcn(index2arfcn(i)),
gsm_print_mcc(s->mcc),
gsm_print_mnc(s->mnc), s->lac);
continue;
}
LOGP(DCS, LOGL_INFO, "Accept ARFCN %s: Cell is in "
"list of forbidden LAs, but we search for any "
"cell. (mcc=%s mnc=%s lai=%04x)\n",
gsm_print_arfcn(index2arfcn(i)),
gsm_print_mcc(s->mcc),
gsm_print_mnc(s->mnc), s->lac);
cs->list[i].flags &= ~GSM322_CS_FLAG_TEMP_AA;
}
/* if cell is in list of forbidden PLMNs */
if (gsm_subscr_is_forbidden_plmn(subscr, s->mcc, s->mnc)) {
if (!any) {
LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is "
"in list of forbidden PLMNs. (mcc=%s "
"mnc=%s)\n",
gsm_print_arfcn(index2arfcn(i)),
gsm_print_mcc(s->mcc),
gsm_print_mnc(s->mnc));
continue;
}
LOGP(DCS, LOGL_INFO, "Accept ARFCN %s: Cell is in list "
"of forbidden PLMNs, but we search for any "
"cell. (mcc=%s mnc=%s)\n",
gsm_print_arfcn(index2arfcn(i)),
gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc));
cs->list[i].flags &= ~GSM322_CS_FLAG_TEMP_AA;
}
/* if we search a specific PLMN, but it does not match */
if (!any && mcc && (mcc != s->mcc
|| mnc != s->mnc)) {
LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: PLMN of cell "
"does not match target PLMN. (mcc=%s "
"mnc=%s)\n", gsm_print_arfcn(index2arfcn(i)),
gsm_print_mcc(s->mcc),
gsm_print_mnc(s->mnc));
continue;
}
LOGP(DCS, LOGL_INFO, "Cell ARFCN %s: Cell found, (rxlev=%s "
"mcc=%s mnc=%s lac=%04x %s, %s)\n",
gsm_print_arfcn(index2arfcn(i)),
gsm_print_rxlev(cs->list[i].rxlev),
gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc), s->lac,
gsm_get_mcc(s->mcc), gsm_get_mnc(s->mcc, s->mnc));
/* find highest power cell */
if (found < 0 || cs->list[i].rxlev > power) {
power = cs->list[i].rxlev;
found = i;
}
}
if (found >= 0)
LOGP(DCS, LOGL_INFO, "Cell ARFCN %s selected.\n",
gsm_print_arfcn(index2arfcn(found)));
return found;
}
/* re-select a suitable and allowable cell */
static int gsm322_cs_reselect(struct osmocom_ms *ms, uint16_t mcc,
uint16_t mnc, int any)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm_subscriber *subscr = &ms->subscr;
struct gsm48_sysinfo *s = cs->si;
int i = cs->arfci;
uint16_t acc_class;
/* set our access class depending on the cell selection type */
if (any) {
acc_class = subscr->acc_class | 0x0400; /* add emergency */
LOGP(DCS, LOGL_DEBUG, "Select using access class with "
"Emergency class.\n");
} else {
acc_class = subscr->acc_class;
LOGP(DCS, LOGL_DEBUG, "Select using access class \n");
}
/* if cell is barred and we don't override */
if (!subscr->acc_barr
&& (cs->list[i].flags & GSM322_CS_FLAG_BARRED)) {
LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is barred.\n",
gsm_print_arfcn(index2arfcn(i)));
return -1;
}
/* if cell is in list of forbidden LAs */
if (!any && (cs->list[i].flags & GSM322_CS_FLAG_FORBIDD)) {
LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is in list of "
"forbidden LAs. (mcc=%s mnc=%s lai=%04x)\n",
gsm_print_arfcn(index2arfcn(i)), gsm_print_mcc(s->mcc),
gsm_print_mnc(s->mnc), s->lac);
return -1;
}
/* if cell is in list of forbidden PLMNs */
if (!any && gsm_subscr_is_forbidden_plmn(subscr, s->mcc, s->mnc)) {
LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is in "
"list of forbidden PLMNs. (mcc=%s mnc=%s)\n",
gsm_print_arfcn(index2arfcn(i)),
gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc));
return -1;
}
/* if we have no access to the cell and we don't override */
if (!subscr->acc_barr
&& !(acc_class & (s->class_barr ^ 0xffff))) {
LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Class is barred for our "
"access. (access=%04x barred=%04x)\n",
gsm_print_arfcn(index2arfcn(i)), acc_class,
s->class_barr);
return -1;
}
/* if we search a specific PLMN, but it does not match */
if (!any && mcc && (mcc != s->mcc
|| mnc != s->mnc)) {
LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: PLMN of cell "
"does not match target PLMN. (mcc=%s mnc=%s)\n",
gsm_print_arfcn(index2arfcn(i)), gsm_print_mcc(s->mcc),
gsm_print_mnc(s->mnc));
return -1;
}
LOGP(DCS, LOGL_INFO, "Cell ARFCN %s: Neighbour cell accepted, "
"(rxlev=%s mcc=%s mnc=%s lac=%04x %s, %s)\n",
gsm_print_arfcn(index2arfcn(i)),
gsm_print_rxlev(cs->list[i].rxlev),
gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc), s->lac,
gsm_get_mcc(s->mcc), gsm_get_mnc(s->mcc, s->mnc));
return i;
}
/* this processes the end of frequency scanning or cell searches */
static int gsm322_search_end(struct osmocom_ms *ms)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm322_plmn *plmn = &ms->plmn;
struct msgb *nmsg;
struct gsm322_msg *ngm;
int msg_type = -1; /* no message to be sent */
int tune_back = 0, mcc = 0, mnc = 0;
int found;
switch (cs->state) {
case GSM322_ANY_SEARCH:
/* special case for 'any cell' search */
LOGP(DCS, LOGL_INFO, "Any cell search finished.\n");
/* create AA flag */
found = gsm322_cs_select(ms, -1, 0, 0, 0);
/* if no cell is found, or if we don't wait for any available
* and allowable PLMN to appear, we just continue to camp */
if (ms->settings.plmn_mode != PLMN_MODE_AUTO
|| plmn->state != GSM322_A4_WAIT_FOR_PLMN
|| found < 0) {
tune_back = 1;
gsm322_c_camp_any_cell(ms, NULL);
break;
}
/* indicate available PLMN, include selected PLMN, if found */
msg_type = GSM322_EVENT_PLMN_AVAIL;
if (gsm322_is_plmn_avail_and_allow(cs, plmn->mcc, plmn->mnc)) {
/* set what PLMN becomes available */
mcc = plmn->mcc;
mnc = plmn->mnc;
}
new_c_state(cs, GSM322_C0_NULL);
break;
case GSM322_PLMN_SEARCH:
/* special case for PLMN search */
msg_type = GSM322_EVENT_PLMN_SEARCH_END;
LOGP(DCS, LOGL_INFO, "PLMN search finished.\n");
/* create AA flag */
gsm322_cs_select(ms, -1, 0, 0, 0);
new_c_state(cs, GSM322_C0_NULL);
break;
case GSM322_HPLMN_SEARCH:
/* special case for HPLMN search */
msg_type = GSM322_EVENT_NO_CELL_FOUND;
LOGP(DCS, LOGL_INFO, "HPLMN search finished, no cell.\n");
new_c_state(cs, GSM322_C3_CAMPED_NORMALLY);
tune_back = 1;
break;
default:
/* we start normal cell selection if this fails */
if (cs->state == GSM322_C2_STORED_CELL_SEL
|| cs->state == GSM322_C5_CHOOSE_CELL) {
/* tell CS to start over */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_NO_CELL_FOUND);
if (!nmsg)
return -ENOMEM;
gsm322_c_event(ms, nmsg);
msgb_free(nmsg);
break;
}
/* on other cell selection, indicate "no cell found" */
/* NOTE: PLMN search process handles it.
* If not handled there, CS process gets indicated.
* If we would continue to process CS, then we might get
* our list of scanned cells disturbed.
*/
LOGP(DCS, LOGL_INFO, "Cell search finished without result.\n");
msg_type = GSM322_EVENT_NO_CELL_FOUND;
/* stay in null-state until any cell selectio is triggered or
* new plmn is indicated.
*/
new_c_state(cs, GSM322_C0_NULL);
}
if (msg_type > -1) {
/* send result to PLMN process, to trigger next CS event */
nmsg = gsm322_msgb_alloc(msg_type);
if (!nmsg)
return -ENOMEM;
ngm = (struct gsm322_msg *) nmsg->data;
ngm->mcc = mcc;
ngm->mnc = mnc;
gsm322_plmn_sendmsg(ms, nmsg);
}
if (cs->selected && tune_back) {
/* tuning back */
cs->arfcn = cs->sel_arfcn;
cs->arfci = arfcn2index(cs->arfcn);
if (!cs->list[cs->arfci].sysinfo)
cs->list[cs->arfci].sysinfo = talloc_zero(ms,
struct gsm48_sysinfo);
if (!cs->list[cs->arfci].sysinfo)
exit(-ENOMEM);
cs->list[cs->arfci].flags |= GSM322_CS_FLAG_SYSINFO;
memcpy(cs->list[cs->arfci].sysinfo, &cs->sel_si,
sizeof(struct gsm48_sysinfo));
cs->si = cs->list[cs->arfci].sysinfo;
cs->sel_mcc = cs->si->mcc;
cs->sel_mnc = cs->si->mnc;
cs->sel_lac = cs->si->lac;
cs->sel_id = cs->si->cell_id;
LOGP(DCS, LOGL_INFO, "Tuning back to frequency %s after full "
"search.\n", gsm_print_arfcn(cs->arfcn));
cs->sync_retries = SYNC_RETRIES;
gsm322_sync_to_cell(cs, NULL, 0);
}
return 0;
}
/* tune to first/next unscanned frequency and search for PLMN */
static int gsm322_cs_scan(struct osmocom_ms *ms)
{
struct gsm322_cellsel *cs = &ms->cellsel;
int i;
int j, band = 0;
uint8_t mask, flags;
uint32_t weight = 0, test = cs->scan_state;
/* search for strongest unscanned cell */
mask = GSM322_CS_FLAG_SUPPORT | GSM322_CS_FLAG_POWER
| GSM322_CS_FLAG_SIGNAL;
if (cs->state == GSM322_C2_STORED_CELL_SEL
|| cs->state == GSM322_C5_CHOOSE_CELL)
mask |= GSM322_CS_FLAG_BA;
flags = mask; /* all masked flags are required */
for (i = 0; i <= 1023+299; i++) {
j = 0; /* make gcc happy */
if (!ms->settings.skip_max_per_band) {
/* skip if band has enough freqs. scanned (3.2.1) */
for (j = 0; gsm_sup_smax[j].max; j++) {
if (gsm_sup_smax[j].end >
gsm_sup_smax[j].start) {
if (gsm_sup_smax[j].start <= i
&& gsm_sup_smax[j].end >= i)
break;
} else {
if (gsm_sup_smax[j].start <= i
&& 1023 >= i)
break;
if (0 <= i
&& gsm_sup_smax[j].end >= i)
break;
}
}
if (gsm_sup_smax[j].max) {
if (gsm_sup_smax[j].temp == gsm_sup_smax[j].max)
continue;
}
}
/* search for unscanned frequency */
if ((cs->list[i].flags & mask) == flags) {
/* weight depends on the power level
* if it is the same, it depends on arfcn
*/
test = cs->list[i].rxlev + 1;
test = (test << 16) | i;
if (test >= cs->scan_state)
continue;
if (test > weight) {
weight = test;
band = j;
}
}
}
cs->scan_state = weight;
/* if all frequencies have been searched */
if (!weight) {
gsm322_dump_cs_list(cs, GSM322_CS_FLAG_SYSINFO, print_dcs,
NULL);
/* selection process done, process (negative) result */
return gsm322_search_end(ms);
}
/* NOTE: We might already have system information from previous
* scan. But we need recent information, so we scan again!
*/
/* Tune to frequency for a while, to receive broadcasts. */
cs->arfci = weight & 0xffff;
cs->arfcn = index2arfcn(cs->arfci);
LOGP(DCS, LOGL_DEBUG, "Scanning frequency %s (rxlev %s).\n",
gsm_print_arfcn(cs->arfcn),
gsm_print_rxlev(cs->list[cs->arfci].rxlev));
/* Allocate/clean system information. */
cs->list[cs->arfci].flags &= ~GSM322_CS_FLAG_SYSINFO;
if (cs->list[cs->arfci].sysinfo)
memset(cs->list[cs->arfci].sysinfo, 0,
sizeof(struct gsm48_sysinfo));
else
cs->list[cs->arfci].sysinfo = talloc_zero(ms,
struct gsm48_sysinfo);
if (!cs->list[cs->arfci].sysinfo)
exit(-ENOMEM);
cs->si = cs->list[cs->arfci].sysinfo;
cs->sync_retries = 0;
gsm322_sync_to_cell(cs, NULL, 0);
/* increase scan counter for each maximum scan range */
if (!ms->settings.skip_max_per_band && gsm_sup_smax[band].max) {
LOGP(DCS, LOGL_DEBUG, "%d frequencies left in band %d..%d\n",
gsm_sup_smax[band].max - gsm_sup_smax[band].temp,
gsm_sup_smax[band].start, gsm_sup_smax[band].end);
gsm_sup_smax[band].temp++;
}
return 0;
}
/* check if cell is now suitable and allowable */
static int gsm322_cs_store(struct osmocom_ms *ms)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm48_sysinfo *s = cs->si;
struct gsm322_plmn *plmn = &ms->plmn;
struct msgb *nmsg;
struct gsm322_msg *ngm;
int found, any = 0;
if (cs->state != GSM322_C2_STORED_CELL_SEL
&& cs->state != GSM322_C1_NORMAL_CELL_SEL
&& cs->state != GSM322_C6_ANY_CELL_SEL
&& cs->state != GSM322_C4_NORMAL_CELL_RESEL
&& cs->state != GSM322_C8_ANY_CELL_RESEL
&& cs->state != GSM322_C5_CHOOSE_CELL
&& cs->state != GSM322_C9_CHOOSE_ANY_CELL
&& cs->state != GSM322_ANY_SEARCH
&& cs->state != GSM322_PLMN_SEARCH
&& cs->state != GSM322_HPLMN_SEARCH) {
LOGP(DCS, LOGL_FATAL, "This must only happen during cell "
"(re-)selection, please fix!\n");
return -EINVAL;
}
/* store sysinfo */
cs->list[cs->arfci].flags |= GSM322_CS_FLAG_SYSINFO;
if (s->cell_barr && !(s->sp && s->sp_cbq))
cs->list[cs->arfci].flags |= GSM322_CS_FLAG_BARRED;
else
cs->list[cs->arfci].flags &= ~GSM322_CS_FLAG_BARRED;
/* store selected network */
if (s->mcc) {
if (gsm322_is_forbidden_la(ms, s->mcc, s->mnc, s->lac))
cs->list[cs->arfci].flags |= GSM322_CS_FLAG_FORBIDD;
else
cs->list[cs->arfci].flags &= ~GSM322_CS_FLAG_FORBIDD;
}
LOGP(DCS, LOGL_DEBUG, "Scan frequency %s: Cell found. (rxlev %s "
"mcc %s mnc %s lac %04x)\n", gsm_print_arfcn(cs->arfcn),
gsm_print_rxlev(cs->list[cs->arfci].rxlev),
gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc), s->lac);
/* selected PLMN (auto) becomes available during "any search" */
if (ms->settings.plmn_mode == PLMN_MODE_AUTO
&& (cs->state == GSM322_ANY_SEARCH
|| cs->state == GSM322_C6_ANY_CELL_SEL
|| cs->state == GSM322_C8_ANY_CELL_RESEL
|| cs->state == GSM322_C9_CHOOSE_ANY_CELL)
&& plmn->state == GSM322_A4_WAIT_FOR_PLMN
&& s->mcc == plmn->mcc && s->mnc == plmn->mnc) {
LOGP(DCS, LOGL_INFO, "Candidate network to become available "
"again\n");
found = gsm322_cs_select(ms, cs->arfci, s->mcc, s->mnc, 0);
if (found >= 0) {
LOGP(DCS, LOGL_INFO, "Selected PLMN in \"A4_WAIT_F"
"OR_PLMN\" state becomes available.\n");
indicate_plmn_avail:
/* PLMN becomes available */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_PLMN_AVAIL);
if (!nmsg)
return -ENOMEM;
/* set what PLMN becomes available */
ngm = (struct gsm322_msg *) nmsg->data;
ngm->mcc = plmn->mcc;
ngm->mnc = plmn->mcc;
gsm322_plmn_sendmsg(ms, nmsg);
new_c_state(cs, GSM322_C0_NULL);
return 0;
}
}
/* selected PLMN (manual) becomes available during "any search" */
if (ms->settings.plmn_mode == PLMN_MODE_MANUAL
&& (cs->state == GSM322_ANY_SEARCH
|| cs->state == GSM322_C6_ANY_CELL_SEL
|| cs->state == GSM322_C8_ANY_CELL_RESEL
|| cs->state == GSM322_C9_CHOOSE_ANY_CELL)
&& plmn->state == GSM322_M3_NOT_ON_PLMN
&& s->mcc == plmn->mcc && s->mnc == plmn->mnc) {
LOGP(DCS, LOGL_INFO, "Candidate network to become available "
"again\n");
found = gsm322_cs_select(ms, cs->arfci, s->mcc, s->mnc, 0);
if (found >= 0) {
LOGP(DCS, LOGL_INFO, "Current selected PLMN in \"M3_N"
"OT_ON_PLMN\" state becomes available.\n");
goto indicate_plmn_avail;
}
}
/* special case for PLMN search */
if (cs->state == GSM322_PLMN_SEARCH
|| cs->state == GSM322_ANY_SEARCH)
/* tune to next cell */
return gsm322_cs_scan(ms);
/* special case for HPLMN search */
if (cs->state == GSM322_HPLMN_SEARCH) {
struct gsm_subscriber *subscr = &ms->subscr;
struct msgb *nmsg;
if (!gsm322_is_hplmn_avail(cs, subscr->imsi))
/* tune to next cell */
return gsm322_cs_scan(ms);
nmsg = gsm322_msgb_alloc(GSM322_EVENT_CELL_FOUND);
LOGP(DCS, LOGL_INFO, "HPLMN search finished, cell found.\n");
if (!nmsg)
return -ENOMEM;
gsm322_plmn_sendmsg(ms, nmsg);
return 0;
}
/* just see, if we search for any cell */
if (cs->state == GSM322_C6_ANY_CELL_SEL
|| cs->state == GSM322_C8_ANY_CELL_RESEL
|| cs->state == GSM322_C9_CHOOSE_ANY_CELL)
any = 1;
if (cs->state == GSM322_C4_NORMAL_CELL_RESEL
|| cs->state == GSM322_C8_ANY_CELL_RESEL)
found = gsm322_cs_reselect(ms, cs->mcc, cs->mnc, any);
else
found = gsm322_cs_select(ms, -1, cs->mcc, cs->mnc, any);
/* if not found */
if (found < 0) {
LOGP(DCS, LOGL_INFO, "Cell not suitable and allowable.\n");
/* tune to next cell */
if (cs->state == GSM322_C4_NORMAL_CELL_RESEL
|| cs->state == GSM322_C8_ANY_CELL_RESEL)
return gsm322_nb_scan(ms);
else
return gsm322_cs_scan(ms);
}
LOGP(DCS, LOGL_INFO, "Tune to frequency %d.\n", found);
/* tune */
cs->arfci = found;
cs->arfcn = index2arfcn(cs->arfci);
cs->si = cs->list[cs->arfci].sysinfo;
cs->sync_retries = SYNC_RETRIES;
gsm322_sync_to_cell(cs, NULL, 0);
/* set selected cell */
cs->selected = 1;
cs->sel_arfcn = cs->arfcn;
memcpy(&cs->sel_si, cs->si, sizeof(cs->sel_si));
cs->sel_mcc = cs->si->mcc;
cs->sel_mnc = cs->si->mnc;
cs->sel_lac = cs->si->lac;
cs->sel_id = cs->si->cell_id;
if (ms->rrlayer.monitor) {
vty_notify(ms, "MON: %scell selected ARFCN=%s MCC=%s MNC=%s "
"LAC=0x%04x cellid=0x%04x (%s %s)\n",
(any) ? "any " : "", gsm_print_arfcn(cs->sel_arfcn),
gsm_print_mcc(cs->sel_mcc), gsm_print_mnc(cs->sel_mnc),
cs->sel_lac, cs->sel_id,
gsm_get_mcc(cs->sel_mcc),
gsm_get_mnc(cs->sel_mcc, cs->sel_mnc));
}
/* tell CS process about available cell */
LOGP(DCS, LOGL_INFO, "Cell available.\n");
nmsg = gsm322_msgb_alloc(GSM322_EVENT_CELL_FOUND);
if (!nmsg)
return -ENOMEM;
gsm322_c_event(ms, nmsg);
msgb_free(nmsg);
return 0;
}
/* process system information when returning to idle mode */
struct gsm322_ba_list *gsm322_cs_sysinfo_sacch(struct osmocom_ms *ms)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm48_sysinfo *s;
struct gsm322_ba_list *ba = NULL;
int i, refer_pcs;
uint8_t freq[128+38];
if (!cs) {
LOGP(DCS, LOGL_INFO, "No BA, because no cell selected\n");
return ba;
}
s = cs->si;
if (!s) {
LOGP(DCS, LOGL_INFO, "No BA, because no sysinfo\n");
return ba;
}
/* collect system information received during dedicated mode */
if (s->si5 && (!s->nb_ext_ind_si5 || s->si5bis)) {
/* find or create ba list */
ba = gsm322_find_ba_list(cs, s->mcc, s->mnc);
if (!ba) {
ba = talloc_zero(ms, struct gsm322_ba_list);
if (!ba)
return NULL;
ba->mcc = s->mcc;
ba->mnc = s->mnc;
llist_add_tail(&ba->entry, &cs->ba_list);
}
/* update (add) ba list */
refer_pcs = gsm_refer_pcs(cs->arfcn, s);
memset(freq, 0, sizeof(freq));
for (i = 0; i <= 1023; i++) {
if ((s->freq[i].mask & (FREQ_TYPE_SERV
| FREQ_TYPE_NCELL | FREQ_TYPE_REP))) {
if (refer_pcs && i >= 512 && i <= 810)
freq[(i-512+1024) >> 3] |= (1 << (i&7));
else
freq[i >> 3] |= (1 << (i & 7));
}
}
if (!!memcmp(freq, ba->freq, sizeof(freq))) {
LOGP(DCS, LOGL_INFO, "New BA list (mcc=%s mnc=%s "
"%s, %s).\n", gsm_print_mcc(ba->mcc),
gsm_print_mnc(ba->mnc), gsm_get_mcc(ba->mcc),
gsm_get_mnc(ba->mcc, ba->mnc));
memcpy(ba->freq, freq, sizeof(freq));
}
}
return ba;
}
/* store BA whenever a system information changes */
static int gsm322_store_ba_list(struct gsm322_cellsel *cs,
struct gsm48_sysinfo *s)
{
struct gsm322_ba_list *ba;
int i, refer_pcs;
uint8_t freq[128+38];
/* find or create ba list */
ba = gsm322_find_ba_list(cs, s->mcc, s->mnc);
if (!ba) {
ba = talloc_zero(cs->ms, struct gsm322_ba_list);
if (!ba)
return -ENOMEM;
ba->mcc = s->mcc;
ba->mnc = s->mnc;
llist_add_tail(&ba->entry, &cs->ba_list);
}
/* update ba list */
refer_pcs = gsm_refer_pcs(cs->arfcn, s);
memset(freq, 0, sizeof(freq));
freq[(cs->arfci) >> 3] |= (1 << (cs->arfci & 7));
for (i = 0; i <= 1023; i++) {
if ((s->freq[i].mask &
(FREQ_TYPE_SERV | FREQ_TYPE_NCELL | FREQ_TYPE_REP))) {
if (refer_pcs && i >= 512 && i <= 810)
freq[(i-512+1024) >> 3] |= (1 << (i & 7));
else
freq[i >> 3] |= (1 << (i & 7));
}
}
if (!!memcmp(freq, ba->freq, sizeof(freq))) {
LOGP(DCS, LOGL_INFO, "New BA list (mcc=%s mnc=%s "
"%s, %s).\n", gsm_print_mcc(ba->mcc),
gsm_print_mnc(ba->mnc), gsm_get_mcc(ba->mcc),
gsm_get_mnc(ba->mcc, ba->mnc));
memcpy(ba->freq, freq, sizeof(freq));
}
return 0;
}
/* process system information during camping on a cell */
static int gsm322_c_camp_sysinfo_bcch(struct osmocom_ms *ms, struct msgb *msg)
{
// struct gsm48_rrlayer *rr = &ms->rrlayer;
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm48_sysinfo *s = cs->si;
struct gsm_subscriber *subscr = &ms->subscr;
struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
struct msgb *nmsg;
/* start in case we are camping on neighbour cell */
if ((cs->state == GSM322_C3_CAMPED_NORMALLY
|| cs->state == GSM322_C7_CAMPED_ANY_CELL)
&& (cs->neighbour)) {
if (s->si3 || s->si4) {
stop_cs_timer(cs);
LOGP(DCS, LOGL_INFO, "Relevant sysinfo of neighbour "
"cell is now received or updated.\n");
return gsm322_nb_read(cs, 1);
}
return 0;
}
/* Store BA if we have full system info about cells and neighbor cells.
* Depending on the extended bit in the channel description,
* we require more or less system information about neighbor cells
*/
if (s->mcc
&& s->mnc
&& (gm->sysinfo == GSM48_MT_RR_SYSINFO_1
|| gm->sysinfo == GSM48_MT_RR_SYSINFO_2
|| gm->sysinfo == GSM48_MT_RR_SYSINFO_2bis
|| gm->sysinfo == GSM48_MT_RR_SYSINFO_2ter)
&& s->si1
&& s->si2
&& (!s->nb_ext_ind_si2
|| (s->si2bis && s->nb_ext_ind_si2 && !s->nb_ext_ind_si2bis)
|| (s->si2bis && s->si2ter && s->nb_ext_ind_si2
&& s->nb_ext_ind_si2bis)))
gsm322_store_ba_list(cs, s);
/* update sel_si, if all relevant system information received */
if (s->si1 && s->si2 && s->si3
&& (!s->nb_ext_ind_si2
|| (s->si2bis && s->nb_ext_ind_si2 && !s->nb_ext_ind_si2bis)
|| (s->si2bis && s->si2ter && s->nb_ext_ind_si2
&& s->nb_ext_ind_si2bis))) {
if (cs->selected) {
LOGP(DCS, LOGL_INFO, "Sysinfo of selected cell is "
"now received or updated.\n");
memcpy(&cs->sel_si, s, sizeof(cs->sel_si));
/* start in case we are camping on serving cell */
if (cs->state == GSM322_C3_CAMPED_NORMALLY
|| cs->state == GSM322_C7_CAMPED_ANY_CELL)
gsm322_nb_start(ms, 0);
}
}
/* check for barred cell */
if (gm->sysinfo == GSM48_MT_RR_SYSINFO_1) {
/* check if cell becomes barred */
if (!subscr->acc_barr && s->cell_barr
&& !(cs->list[cs->arfci].sysinfo
&& cs->list[cs->arfci].sysinfo->sp
&& cs->list[cs->arfci].sysinfo->sp_cbq)) {
LOGP(DCS, LOGL_INFO, "Cell becomes barred.\n");
if (ms->rrlayer.monitor)
vty_notify(ms, "MON: trigger cell re-selection"
": cell becomes barred\n");
trigger_resel:
/* mark cell as unscanned */
cs->list[cs->arfci].flags &= ~GSM322_CS_FLAG_SYSINFO;
if (cs->list[cs->arfci].sysinfo) {
LOGP(DCS, LOGL_DEBUG, "free sysinfo arfcn=%s\n",
gsm_print_arfcn(cs->arfcn));
if (cs->si == cs->list[cs->arfci].sysinfo)
cs->si = NULL;
talloc_free(cs->list[cs->arfci].sysinfo);
cs->list[cs->arfci].sysinfo = NULL;
}
/* trigger reselection without queueing,
* because other sysinfo message may be queued
* before
*/
nmsg = gsm322_msgb_alloc(GSM322_EVENT_CELL_RESEL);
if (!nmsg)
return -ENOMEM;
gsm322_c_event(ms, nmsg);
msgb_free(nmsg);
return 0;
}
/* check if cell access becomes barred */
if (!((subscr->acc_class & 0xfbff)
& (s->class_barr ^ 0xffff))) {
LOGP(DCS, LOGL_INFO, "Cell access becomes barred.\n");
if (ms->rrlayer.monitor)
vty_notify(ms, "MON: trigger cell re-selection"
": access to cell becomes barred\n");
goto trigger_resel;
}
}
/* check if MCC, MNC, LAC, cell ID changes */
if (cs->sel_mcc != s->mcc || cs->sel_mnc != s->mnc
|| cs->sel_lac != s->lac) {
LOGP(DCS, LOGL_NOTICE, "Cell changes location area. "
"This is not good!\n");
if (ms->rrlayer.monitor)
vty_notify(ms, "MON: trigger cell re-selection: "
"cell changes LAI\n");
goto trigger_resel;
}
if (cs->sel_id != s->cell_id) {
LOGP(DCS, LOGL_NOTICE, "Cell changes cell ID. "
"This is not good!\n");
if (ms->rrlayer.monitor)
vty_notify(ms, "MON: trigger cell re-selection: "
"cell changes cell ID\n");
goto trigger_resel;
}
return 0;
}
/* process system information during channel scanning */
static int gsm322_c_scan_sysinfo_bcch(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm48_sysinfo *s = cs->si;
struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
/* no sysinfo if we are not done with power scan */
if (cs->powerscan) {
LOGP(DCS, LOGL_INFO, "Ignoring sysinfo during power scan.\n");
return -EINVAL;
}
/* Store BA if we have full system info about cells and neighbor cells.
* Depending on the extended bit in the channel description,
* we require more or less system information about neighbor cells
*/
if (s->mcc
&& s->mnc
&& (gm->sysinfo == GSM48_MT_RR_SYSINFO_1
|| gm->sysinfo == GSM48_MT_RR_SYSINFO_2
|| gm->sysinfo == GSM48_MT_RR_SYSINFO_2bis
|| gm->sysinfo == GSM48_MT_RR_SYSINFO_2ter)
&& s->si1
&& s->si2
&& (!s->nb_ext_ind_si2 || s->si2bis)
&& (!s->si2ter_ind || s->si2ter))
gsm322_store_ba_list(cs, s);
/* all relevant system information received */
if (s->si1 && s->si2 && s->si3
&& (!s->nb_ext_ind_si2 || s->si2bis)
&& (!s->si2ter_ind || s->si2ter)) {
LOGP(DCS, LOGL_DEBUG, "Received relevant sysinfo.\n");
/* stop timer */
stop_cs_timer(cs);
//gsm48_sysinfo_dump(s, print_dcs, NULL);
/* store sysinfo and continue scan */
return gsm322_cs_store(ms);
}
/* wait for more sysinfo or timeout */
return 0;
}
static void gsm322_cs_timeout(void *arg)
{
struct gsm322_cellsel *cs = arg;
struct osmocom_ms *ms = cs->ms;
if (cs->neighbour) {
LOGP(DCS, LOGL_INFO, "Neighbour cell read failed.\n");
gsm322_nb_read(cs, 0);
return;
}
/* if we have no lock, we retry */
if (cs->ccch_state != GSM322_CCCH_ST_SYNC)
LOGP(DCS, LOGL_INFO, "Cell selection failed, sync timeout.\n");
else
LOGP(DCS, LOGL_INFO, "Cell selection failed, read timeout.\n");
/* remove system information */
cs->list[cs->arfci].flags &= ~GSM322_CS_FLAG_SYSINFO;
if (cs->list[cs->arfci].sysinfo) {
LOGP(DCS, LOGL_DEBUG, "free sysinfo arfcn=%s\n",
gsm_print_arfcn(cs->arfcn));
if (cs->si == cs->list[cs->arfci].sysinfo)
cs->si = NULL;
talloc_free(cs->list[cs->arfci].sysinfo);
cs->list[cs->arfci].sysinfo = NULL;
}
/* tune to next cell */
if (cs->state == GSM322_C4_NORMAL_CELL_RESEL
|| cs->state == GSM322_C8_ANY_CELL_RESEL)
gsm322_nb_scan(ms);
else
gsm322_cs_scan(ms);
return;
}
/*
* power scan process
*/
/* search for block of unscanned frequencies and start scanning */
static int gsm322_cs_powerscan(struct osmocom_ms *ms)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm_settings *set = &ms->settings;
int i, s = -1, e;
char s_text[ARFCN_TEXT_LEN], e_text[ARFCN_TEXT_LEN];
uint8_t mask, flags;
again:
mask = GSM322_CS_FLAG_SUPPORT | GSM322_CS_FLAG_POWER;
flags = GSM322_CS_FLAG_SUPPORT;
/* in case of sticking to a cell, we only select it */
if (set->stick) {
LOGP(DCS, LOGL_DEBUG, "Scanning power for sticked cell.\n");
i = arfcn2index(set->stick_arfcn);
if ((cs->list[i].flags & mask) == flags)
s = e = i;
} else {
/* search for first frequency to scan */
if (cs->state == GSM322_C2_STORED_CELL_SEL
|| cs->state == GSM322_C5_CHOOSE_CELL) {
LOGP(DCS, LOGL_DEBUG, "Scanning power for stored BA "
"list.\n");
mask |= GSM322_CS_FLAG_BA;
flags |= GSM322_CS_FLAG_BA;
} else
LOGP(DCS, LOGL_DEBUG, "Scanning power for all "
"frequencies.\n");
for (i = 0; i <= 1023+299; i++) {
if ((cs->list[i].flags & mask) == flags) {
s = e = i;
break;
}
}
}
/* if there is no more frequency, we can tune to that cell */
if (s < 0) {
int found = 0;
/* stop power level scanning */
cs->powerscan = 0;
/* check if no signal is found */
for (i = 0; i <= 1023+299; i++) {
if ((cs->list[i].flags & GSM322_CS_FLAG_SIGNAL))
found++;
}
if (!found) {
LOGP(DCS, LOGL_INFO, "Found no frequency.\n");
/* on normal cell selection, start over */
if (cs->state == GSM322_C1_NORMAL_CELL_SEL) {
for (i = 0; i <= 1023+299; i++) {
/* clear flag that this was scanned */
cs->list[i].flags &=
~(GSM322_CS_FLAG_POWER
| GSM322_CS_FLAG_SIGNAL
| GSM322_CS_FLAG_SYSINFO);
}
goto again;
}
/* freq. scan process done, process (negative) result */
return gsm322_search_end(ms);
}
LOGP(DCS, LOGL_INFO, "Found %d frequencies.\n", found);
cs->scan_state = 0xffffffff; /* higher than high */
/* clear counter of scanned frequencies of each range */
for (i = 0; gsm_sup_smax[i].max; i++)
gsm_sup_smax[i].temp = 0;
return gsm322_cs_scan(ms);
}
/* search last frequency to scan (en block) */
e = i;
if (!set->stick) {
for (i = s + 1; i <= 1023+299; i++) {
if (i == 1024)
break;
if ((cs->list[i].flags & mask) == flags)
e = i;
else
break;
}
}
osmo_strlcpy(s_text, gsm_print_arfcn(index2arfcn(s)), ARFCN_TEXT_LEN);
osmo_strlcpy(e_text, gsm_print_arfcn(index2arfcn(e)), ARFCN_TEXT_LEN);
LOGP(DCS, LOGL_DEBUG, "Scanning frequencies. (%s..%s)\n",
s_text,
e_text);
/* start scan on radio interface */
if (!cs->powerscan) {
l1ctl_tx_reset_req(ms, L1CTL_RES_T_FULL);
cs->powerscan = 1;
}
cs->sync_pending = 0;
return l1ctl_tx_pm_req_range(ms, index2arfcn(s), index2arfcn(e));
}
int gsm322_l1_signal(unsigned int subsys, unsigned int signal,
void *handler_data, void *signal_data)
{
struct osmocom_ms *ms;
struct gsm322_cellsel *cs;
struct osmobb_meas_res *mr;
struct osmobb_fbsb_res *fr;
struct osmobb_neigh_pm_ind *ni;
int i;
int8_t rxlev;
if (subsys != SS_L1CTL)
return 0;
switch (signal) {
case S_L1CTL_PM_RES:
mr = signal_data;
ms = mr->ms;
cs = &ms->cellsel;
if (!cs->powerscan)
return -EINVAL;
i = arfcn2index(mr->band_arfcn);
rxlev = mr->rx_lev;
if ((cs->list[i].flags & GSM322_CS_FLAG_POWER)) {
LOGP(DCS, LOGL_ERROR, "Getting PM for ARFCN %s "
"twice. Overwriting the first! Please fix "
"prim_pm.c\n", gsm_print_arfcn(index2arfcn(i)));
}
cs->list[i].rxlev = rxlev;
cs->list[i].flags |= GSM322_CS_FLAG_POWER;
cs->list[i].flags &= ~GSM322_CS_FLAG_SIGNAL;
/* if minimum level is reached or if we stick to a cell */
if (rxlev2dbm(rxlev) >= ms->settings.min_rxlev_dbm
|| ms->settings.stick) {
cs->list[i].flags |= GSM322_CS_FLAG_SIGNAL;
LOGP(DCS, LOGL_INFO, "Found signal (ARFCN %s "
"rxlev %s (%d))\n",
gsm_print_arfcn(index2arfcn(i)),
gsm_print_rxlev(rxlev), rxlev);
} else
/* no signal found, free sysinfo, if allocated */
if (cs->list[i].sysinfo) {
cs->list[i].flags &= ~GSM322_CS_FLAG_SYSINFO;
LOGP(DCS, LOGL_DEBUG, "free sysinfo ARFCN=%s\n",
gsm_print_arfcn(index2arfcn(i)));
if (cs->si == cs->list[i].sysinfo)
cs->si = NULL;
talloc_free(cs->list[i].sysinfo);
cs->list[i].sysinfo = NULL;
}
break;
case S_L1CTL_PM_DONE:
LOGP(DCS, LOGL_DEBUG, "Done with power scanning range.\n");
ms = signal_data;
cs = &ms->cellsel;
if (!cs->powerscan)
return -EINVAL;
gsm322_cs_powerscan(ms);
break;
case S_L1CTL_FBSB_RESP:
fr = signal_data;
ms = fr->ms;
cs = &ms->cellsel;
if (cs->powerscan)
return -EINVAL;
cs->sync_pending = 0;
if (cs->arfcn != fr->band_arfcn) {
LOGP(DCS, LOGL_NOTICE, "Channel synched on "
"wrong ARFCN=%d, syncing on right ARFCN again"
"...\n", fr->band_arfcn);
cs->sync_retries = SYNC_RETRIES;
gsm322_sync_to_cell(cs, cs->neighbour, 0);
break;
}
if (cs->ccch_state == GSM322_CCCH_ST_INIT) {
LOGP(DCS, LOGL_INFO, "Channel synched. (ARFCN=%s, "
"snr=%u, BSIC=%u)\n",
gsm_print_arfcn(cs->arfcn), fr->snr, fr->bsic);
cs->ccch_state = GSM322_CCCH_ST_SYNC;
if (cs->si)
cs->si->bsic = fr->bsic;
/* set timer for reading BCCH */
if (cs->state == GSM322_C2_STORED_CELL_SEL
|| cs->state == GSM322_C1_NORMAL_CELL_SEL
|| cs->state == GSM322_C6_ANY_CELL_SEL
|| cs->state == GSM322_C4_NORMAL_CELL_RESEL
|| cs->state == GSM322_C8_ANY_CELL_RESEL
|| cs->state == GSM322_C5_CHOOSE_CELL
|| cs->state == GSM322_C9_CHOOSE_ANY_CELL
|| cs->state == GSM322_ANY_SEARCH
|| cs->state == GSM322_PLMN_SEARCH
|| cs->state == GSM322_HPLMN_SEARCH)
start_cs_timer(cs, ms->support.scan_to, 0);
// TODO: timer depends on BCCH config
/* set downlink signalling failure criterion */
ms->meas.ds_fail = ms->meas.dsc = ms->settings.dsc_max;
LOGP(DRR, LOGL_INFO, "using DSC of %d\n", ms->meas.dsc);
/* start in case we are camping on serving/neighbour
* cell */
if (cs->state == GSM322_C3_CAMPED_NORMALLY
|| cs->state == GSM322_C7_CAMPED_ANY_CELL) {
if (cs->neighbour)
gsm322_nb_synced(cs, 1);
else
gsm322_nb_start(ms, 1);
}
}
break;
case S_L1CTL_FBSB_ERR:
fr = signal_data;
ms = fr->ms;
cs = &ms->cellsel;
if (cs->powerscan)
return -EINVAL;
cs->sync_pending = 0;
/* retry */
if (cs->sync_retries) {
LOGP(DCS, LOGL_INFO, "Channel sync error, try again\n");
cs->sync_retries--;
gsm322_sync_to_cell(cs, cs->neighbour, 0);
break;
}
if (cs->arfcn != fr->band_arfcn) {
LOGP(DCS, LOGL_NOTICE, "Channel synched failed on "
"wrong ARFCN=%d, syncing on right ARFCN again"
"...\n", fr->band_arfcn);
cs->sync_retries = SYNC_RETRIES;
gsm322_sync_to_cell(cs, cs->neighbour, 0);
break;
}
LOGP(DCS, LOGL_INFO, "Channel sync error.\n");
/* no sync, free sysinfo, if allocated */
if (cs->list[cs->arfci].sysinfo) {
cs->list[cs->arfci].flags &= ~GSM322_CS_FLAG_SYSINFO;
LOGP(DCS, LOGL_DEBUG, "free sysinfo ARFCN=%s\n",
gsm_print_arfcn(index2arfcn(cs->arfci)));
if (cs->si == cs->list[cs->arfci].sysinfo)
cs->si = NULL;
talloc_free(cs->list[cs->arfci].sysinfo);
cs->list[cs->arfci].sysinfo = NULL;
}
if (cs->selected && cs->sel_arfcn == cs->arfcn) {
LOGP(DCS, LOGL_INFO, "Unselect cell due to sync "
"error!\n");
/* unset selected cell */
gsm322_unselect_cell(cs);
}
stop_cs_timer(cs);
/* start in case we are camping on neighbour * cell */
if (cs->state == GSM322_C3_CAMPED_NORMALLY
|| cs->state == GSM322_C7_CAMPED_ANY_CELL) {
if (cs->neighbour) {
gsm322_nb_synced(cs, 0);
break;
}
}
gsm322_cs_loss(cs);
break;
case S_L1CTL_LOSS_IND:
ms = signal_data;
cs = &ms->cellsel;
LOGP(DCS, LOGL_INFO, "Loss of CCCH.\n");
if (cs->selected && cs->sel_arfcn == cs->arfcn) {
/* do not unselect cell */
LOGP(DCS, LOGL_INFO, "Keep cell selected after loss, "
"so we can use the Neighbour cell information "
"for cell re-selection.\n");
}
stop_cs_timer(cs);
gsm322_cs_loss(cs);
break;
case S_L1CTL_RESET:
ms = signal_data;
if (ms->mmlayer.power_off_idle) {
mobile_exit(ms, 1);
return 0;
}
break;
case S_L1CTL_NEIGH_PM_IND:
ni = signal_data;
ms = ni->ms;
#ifdef COMMING_LATE_R
/* in dedicated mode */
if (ms->rrlayer.dm_est)
gsm48_rr_meas_ind(ms, ni->band_arfcn, ni->rx_lev);
else
#endif
/* in camping mode */
if ((ms->cellsel.state == GSM322_C3_CAMPED_NORMALLY
|| ms->cellsel.state == GSM322_C7_CAMPED_ANY_CELL)
&& !ms->cellsel.neighbour)
gsm322_nb_meas_ind(ms, ni->band_arfcn, ni->rx_lev);
break;
}
return 0;
}
static void gsm322_cs_loss(void *arg)
{
struct gsm322_cellsel *cs = arg;
struct osmocom_ms *ms = cs->ms;
if ((cs->state == GSM322_C3_CAMPED_NORMALLY
|| cs->state == GSM322_C7_CAMPED_ANY_CELL)
&& !cs->neighbour) {
struct msgb *nmsg;
LOGP(DCS, LOGL_INFO, "Loss of CCCH, Trigger "
"re-selection.\n");
if (ms->rrlayer.monitor)
vty_notify(ms, "MON: trigger cell "
"re-selection: loss of signal\n");
nmsg = gsm322_msgb_alloc(GSM322_EVENT_CELL_RESEL);
if (!nmsg)
return;
gsm322_c_event(ms, nmsg);
msgb_free(nmsg);
return;
} else
if (cs->state == GSM322_CONNECTED_MODE_1
|| cs->state == GSM322_CONNECTED_MODE_2) {
LOGP(DCS, LOGL_INFO, "Loss of SACCH, Trigger RR "
"abort.\n");
/* keep cell info for re-selection */
gsm48_rr_los(ms);
/* be sure that nothing else is done after here
* because the function call above may cause
* to return from idle state and trigger cell re-sel.
*/
return;
}
gsm322_cs_timeout(cs);
return;
}
/*
* handler for cell selection process
*/
/* start any cell search */
static int gsm322_c_any_search(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
int i;
new_c_state(cs, GSM322_ANY_SEARCH);
/* mark all frequencies as scanned */
for (i = 0; i <= 1023+299; i++) {
cs->list[i].flags &= ~(GSM322_CS_FLAG_POWER
| GSM322_CS_FLAG_SIGNAL
| GSM322_CS_FLAG_SYSINFO);
}
/* start power scan */
return gsm322_cs_powerscan(ms);
}
/* start PLMN search */
static int gsm322_c_plmn_search(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
int i;
new_c_state(cs, GSM322_PLMN_SEARCH);
/* mark all frequencies as scanned */
for (i = 0; i <= 1023+299; i++) {
cs->list[i].flags &= ~(GSM322_CS_FLAG_POWER
| GSM322_CS_FLAG_SIGNAL
| GSM322_CS_FLAG_SYSINFO);
}
/* unset selected cell */
gsm322_unselect_cell(cs);
/* start power scan */
return gsm322_cs_powerscan(ms);
}
/* start HPLMN search */
static int gsm322_c_hplmn_search(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
int i, sel_i = arfcn2index(cs->sel_arfcn);
new_c_state(cs, GSM322_HPLMN_SEARCH);
/* mark all frequencies except our own BA as unscanned */
for (i = 0; i <= 1023+299; i++) {
if (i != sel_i
&& (cs->list[i].flags & GSM322_CS_FLAG_SYSINFO)
&& !(cs->list[i].flags & GSM322_CS_FLAG_BA)) {
cs->list[i].flags &= ~(GSM322_CS_FLAG_POWER
| GSM322_CS_FLAG_SIGNAL
| GSM322_CS_FLAG_SYSINFO);
}
}
/* start power scan */
return gsm322_cs_powerscan(ms);
}
/* start stored cell selection */
static int gsm322_c_stored_cell_sel(struct osmocom_ms *ms,
struct gsm322_ba_list *ba)
{
struct gsm322_cellsel *cs = &ms->cellsel;
int i;
/* we weed to rescan */
for (i = 0; i <= 1023+299; i++) {
cs->list[i].flags &= ~(GSM322_CS_FLAG_POWER
| GSM322_CS_FLAG_SIGNAL
| GSM322_CS_FLAG_SYSINFO);
}
new_c_state(cs, GSM322_C2_STORED_CELL_SEL);
/* flag all frequencies that are in current band allocation */
for (i = 0; i <= 1023+299; i++) {
if ((ba->freq[i >> 3] & (1 << (i & 7))))
cs->list[i].flags |= GSM322_CS_FLAG_BA;
else
cs->list[i].flags &= ~GSM322_CS_FLAG_BA;
}
/* unset selected cell */
gsm322_unselect_cell(cs);
/* start power scan */
return gsm322_cs_powerscan(ms);
}
/* start normal cell selection */
static int gsm322_c_normal_cell_sel(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
int i;
/* except for stored cell selection state, we weed to rescan */
if (cs->state != GSM322_C2_STORED_CELL_SEL) {
for (i = 0; i <= 1023+299; i++) {
cs->list[i].flags &= ~(GSM322_CS_FLAG_POWER
| GSM322_CS_FLAG_SIGNAL
| GSM322_CS_FLAG_SYSINFO);
}
}
new_c_state(cs, GSM322_C1_NORMAL_CELL_SEL);
/* unset selected cell */
gsm322_unselect_cell(cs);
/* start power scan */
return gsm322_cs_powerscan(ms);
}
/* start any cell selection */
static int gsm322_c_any_cell_sel(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
int msg_type = gm->msg_type;
/* in case we already tried any cell (re-)selection, power scan again */
if (cs->state == GSM322_C0_NULL
|| cs->state == GSM322_C6_ANY_CELL_SEL
|| cs->state == GSM322_C8_ANY_CELL_RESEL) {
int i;
for (i = 0; i <= 1023+299; i++) {
cs->list[i].flags &= ~(GSM322_CS_FLAG_POWER
| GSM322_CS_FLAG_SIGNAL
| GSM322_CS_FLAG_SYSINFO);
}
/* indicate to MM that we lost coverage.
* this is the only case where we really have no coverage.
* we tell MM, so it will enter the "No Cell Available" state. */
if (msg_type == GSM322_EVENT_NO_CELL_FOUND) {
struct msgb *nmsg;
/* tell that we have no cell found
* (not any cell at all) */
nmsg = gsm48_mmevent_msgb_alloc(
GSM48_MM_EVENT_NO_CELL_FOUND);
if (!nmsg)
return -ENOMEM;
gsm48_mmevent_msg(ms, nmsg);
}
}
new_c_state(cs, GSM322_C6_ANY_CELL_SEL);
cs->mcc = cs->mnc = 0;
/* unset selected cell */
gsm322_unselect_cell(cs);
/* start power scan */
return gsm322_cs_powerscan(ms);
}
static void gsm322_any_timeout(void *arg)
{
struct gsm322_cellsel *cs = arg;
struct osmocom_ms *ms = cs->ms;
/* the timer may still run when not camping, so we ignore it.
* it will be restarted whenever the 'camped on any cell' state
* is reached. */
if (cs->state != GSM322_C7_CAMPED_ANY_CELL)
return;
/* in case the time has been started before SIM was removed */
if (!ms->subscr.sim_valid)
return;
LOGP(DCS, LOGL_INFO, "'Any cell selection timer' timed out. "
"Starting special search to find allowed PLMNs.\n");
gsm322_c_any_search(ms, NULL);
}
/* sim is removed, proceed with any cell selection */
static int gsm322_c_sim_remove(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct llist_head *lh, *lh2;
/* flush list of forbidden LAs */
llist_for_each_safe(lh, lh2, &plmn->forbidden_la) {
llist_del(lh);
talloc_free(lh);
}
return gsm322_c_any_cell_sel(ms, msg);
}
/* start normal cell re-selection */
static int gsm322_c_normal_cell_resel(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct msgb *nmsg;
/* store last camped cell. this is required for next cell
* monitoring reselection criterion */
cs->last_serving_arfcn = cs->sel_arfcn;
cs->last_serving_valid = 1;
/* unset selected cell */
gsm322_unselect_cell(cs);
/* tell MM that we lost coverage */
nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_LOST_COVERAGE);
if (!nmsg)
return -ENOMEM;
gsm48_mmevent_msg(ms, nmsg);
new_c_state(cs, GSM322_C4_NORMAL_CELL_RESEL);
/* start scanning neighbour cells for reselection */
return gsm322_nb_scan(ms);
}
/* start any cell re-selection */
static int gsm322_c_any_cell_resel(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct msgb *nmsg;
/* store last camped cell. this is required for next cell
* monitoring reselection criterion */
cs->last_serving_arfcn = cs->sel_arfcn;
cs->last_serving_valid = 1;
/* unset selected cell */
gsm322_unselect_cell(cs);
/* tell MM that we lost coverage */
nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_LOST_COVERAGE);
if (!nmsg)
return -ENOMEM;
gsm48_mmevent_msg(ms, nmsg);
new_c_state(cs, GSM322_C8_ANY_CELL_RESEL);
/* start scanning neighbour cells for reselection */
return gsm322_nb_scan(ms);
}
/* a suitable cell was found, so we camp normally */
static int gsm322_c_camp_normally(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct msgb *nmsg;
LOGP(DSUM, LOGL_INFO, "Camping normally on cell (ARFCN=%s mcc=%s "
"mnc=%s %s, %s)\n", gsm_print_arfcn(cs->sel_arfcn),
gsm_print_mcc(cs->sel_mcc),
gsm_print_mnc(cs->sel_mnc), gsm_get_mcc(cs->sel_mcc),
gsm_get_mnc(cs->sel_mcc, cs->sel_mnc));
/* if we did cell reselection, we have a valid last serving cell */
if (cs->state != GSM322_C4_NORMAL_CELL_RESEL)
cs->last_serving_valid = 0;
/* tell that we have selected a (new) cell */
nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_CELL_SELECTED);
if (!nmsg)
return -ENOMEM;
gsm48_mmevent_msg(ms, nmsg);
new_c_state(cs, GSM322_C3_CAMPED_NORMALLY);
return 0;
}
/* any cell was found, so we camp on any cell */
static int gsm322_c_camp_any_cell(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct msgb *nmsg;
LOGP(DSUM, LOGL_INFO, "Camping on any cell (ARFCN=%s mcc=%s "
"mnc=%s %s, %s)\n", gsm_print_arfcn(cs->sel_arfcn),
gsm_print_mcc(cs->sel_mcc),
gsm_print_mnc(cs->sel_mnc), gsm_get_mcc(cs->sel_mcc),
gsm_get_mnc(cs->sel_mcc, cs->sel_mnc));
/* (re-)starting 'any cell selection' timer to look for coverage of
* allowed PLMNs.
* start timer, if not running.
* restart timer, if we just entered the 'camped any cell' state */
if (ms->subscr.sim_valid
&& (cs->state != GSM322_C8_ANY_CELL_RESEL
|| !osmo_timer_pending(&cs->any_timer))) {
struct gsm322_plmn *plmn = &ms->plmn;
stop_any_timer(cs);
if (ms->settings.plmn_mode == PLMN_MODE_MANUAL
&& (!plmn->mcc
|| gsm_subscr_is_forbidden_plmn(&ms->subscr, plmn->mcc,
plmn->mnc))) {
LOGP(DCS, LOGL_INFO, "Not starting 'any search' timer, "
"because no selected PLMN or forbidden\n");
} else
start_any_timer(cs, ms->subscr.any_timeout, 0);
}
/* if we did cell reselection, we have a valid last serving cell */
if (cs->state != GSM322_C8_ANY_CELL_RESEL)
cs->last_serving_valid = 0;
/* tell that we have selected a (new) cell.
* this cell iss not allowable, so the MM state will enter limited
* service */
if (cs->state != GSM322_C7_CAMPED_ANY_CELL) {
nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_CELL_SELECTED);
if (!nmsg)
return -ENOMEM;
gsm48_mmevent_msg(ms, nmsg);
}
new_c_state(cs, GSM322_C7_CAMPED_ANY_CELL);
return 0;
}
/* create temporary ba range with given frequency ranges */
struct gsm322_ba_list *gsm322_cs_ba_range(struct osmocom_ms *ms,
uint32_t *range, uint8_t ranges, uint8_t refer_pcs)
{
static struct gsm322_ba_list ba;
int lower, higher;
char lower_text[ARFCN_TEXT_LEN], higher_text[ARFCN_TEXT_LEN];
memset(&ba, 0, sizeof(ba));
while(ranges--) {
lower = *range & 1023;
higher = (*range >> 16) & 1023;
if (refer_pcs && lower >= 512 && lower <= 810) {
if (higher < 512 || higher > 810 || higher < lower) {
LOGP(DCS, LOGL_NOTICE, "Illegal PCS range: "
"%d..%d\n", lower, higher);
range++;
continue;
}
lower += 1024-512;
higher += 1024-512;
}
range++;
osmo_strlcpy(lower_text, gsm_print_arfcn(index2arfcn(lower)), ARFCN_TEXT_LEN);
osmo_strlcpy(higher_text, gsm_print_arfcn(index2arfcn(higher)), ARFCN_TEXT_LEN);
LOGP(DCS, LOGL_INFO, "Use BA range: %s..%s\n",
lower_text,
higher_text);
/* GSM 05.08 6.3 */
while (1) {
ba.freq[lower >> 3] |= 1 << (lower & 7);
if (lower == higher)
break;
lower++;
/* wrap around, only if not PCS */
if (lower == 1024)
lower = 0;
}
}
return &ba;
}
/* common part of gsm322_c_choose_cell and gsm322_c_choose_any_cell */
static int gsm322_cs_choose(struct osmocom_ms *ms)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm48_rrlayer *rr = &ms->rrlayer;
struct gsm322_ba_list *ba = NULL;
int i;
/* NOTE: The call to this function is synchron to RR layer, so
* we may access the BA range there.
*/
if (rr->ba_ranges)
ba = gsm322_cs_ba_range(ms, rr->ba_range, rr->ba_ranges,
gsm_refer_pcs(cs->sel_arfcn, &cs->sel_si));
else {
LOGP(DCS, LOGL_INFO, "No BA range(s), try sysinfo.\n");
/* get and update BA of last received sysinfo 5* */
ba = gsm322_cs_sysinfo_sacch(ms);
if (!ba) {
LOGP(DCS, LOGL_INFO, "No BA on sysinfo, try stored "
"BA list.\n");
ba = gsm322_find_ba_list(cs, cs->sel_si.mcc,
cs->sel_si.mnc);
}
}
if (!ba) {
struct msgb *nmsg;
LOGP(DCS, LOGL_INFO, "No BA list to use.\n");
/* tell CS to start over */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_NO_CELL_FOUND);
if (!nmsg)
return -ENOMEM;
gsm322_c_event(ms, nmsg);
msgb_free(nmsg);
return 0;
}
/* flag all frequencies that are in current band allocation */
for (i = 0; i <= 1023+299; i++) {
if (cs->state == GSM322_C5_CHOOSE_CELL) {
if ((ba->freq[i >> 3] & (1 << (i & 7)))) {
cs->list[i].flags |= GSM322_CS_FLAG_BA;
} else {
cs->list[i].flags &= ~GSM322_CS_FLAG_BA;
}
}
cs->list[i].flags &= ~(GSM322_CS_FLAG_POWER
| GSM322_CS_FLAG_SIGNAL
| GSM322_CS_FLAG_SYSINFO);
}
/* unset selected cell */
gsm322_unselect_cell(cs);
/* start power scan */
return gsm322_cs_powerscan(ms);
}
/* start 'Choose cell' after returning to idle mode */
static int gsm322_c_choose_cell(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
/* After location updating, we choose the last cell */
if (gm->same_cell) {
struct msgb *nmsg;
if (!cs->selected) {
LOGP(DCS, LOGL_INFO, "Cell not selected anymore, "
"choose cell!\n");
goto choose;
}
cs->arfcn = cs->sel_arfcn;
cs->arfci = arfcn2index(cs->arfcn);
/* be sure to go to current camping frequency on return */
LOGP(DCS, LOGL_INFO, "Selecting ARFCN %s. after LOC.UPD.\n",
gsm_print_arfcn(cs->arfcn));
cs->sync_retries = SYNC_RETRIES;
gsm322_sync_to_cell(cs, NULL, 0);
cs->si = cs->list[cs->arfci].sysinfo;
if (!cs->si) {
LOGP(DCS, LOGL_FATAL, "No SI when ret.idle, please fix!\n");
exit(0L);
}
new_c_state(cs, GSM322_C3_CAMPED_NORMALLY);
/* tell that we have selected the cell, so RR returns IDLE */
nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_CELL_SELECTED);
if (!nmsg)
return -ENOMEM;
gsm48_mmevent_msg(ms, nmsg);
return 0;
}
choose:
new_c_state(cs, GSM322_C5_CHOOSE_CELL);
return gsm322_cs_choose(ms);
}
/* start 'Choose any cell' after returning to idle mode */
static int gsm322_c_choose_any_cell(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
new_c_state(cs, GSM322_C9_CHOOSE_ANY_CELL);
return gsm322_cs_choose(ms);
}
/* a new PLMN is selected by PLMN search process */
static int gsm322_c_new_plmn(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm322_ba_list *ba;
cs->mcc = plmn->mcc;
cs->mnc = plmn->mnc;
if (gm->limited) {
LOGP(DCS, LOGL_INFO, "Selected PLMN with limited service.\n");
return gsm322_c_any_cell_sel(ms, msg);
}
LOGP(DSUM, LOGL_INFO, "Selecting PLMN (mcc=%s mnc=%s %s, %s)\n",
gsm_print_mcc(cs->mcc), gsm_print_mnc(cs->mnc),
gsm_get_mcc(cs->mcc), gsm_get_mnc(cs->mcc, cs->mnc));
/* search for BA list */
ba = gsm322_find_ba_list(cs, plmn->mcc, plmn->mnc);
if (ba) {
LOGP(DCS, LOGL_INFO, "Start stored cell selection.\n");
return gsm322_c_stored_cell_sel(ms, ba);
} else {
LOGP(DCS, LOGL_INFO, "Start normal cell selection.\n");
return gsm322_c_normal_cell_sel(ms, msg);
}
}
/* go connected mode */
static int gsm322_c_conn_mode_1(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
/* check for error */
if (!cs->selected) {
LOGP(DCS, LOGL_INFO, "No cell selected, please fix!\n");
exit(0L);
}
cs->arfcn = cs->sel_arfcn;
cs->arfci = arfcn2index(cs->arfcn);
/* maybe we are currently syncing to neighbours */
stop_cs_timer(cs);
new_c_state(cs, GSM322_CONNECTED_MODE_1);
/* be sure to go to current camping frequency on return */
LOGP(DCS, LOGL_INFO, "Going to camping (normal) ARFCN %s.\n",
gsm_print_arfcn(cs->arfcn));
cs->si = cs->list[cs->arfci].sysinfo;
if (!cs->si) {
LOGP(DCS, LOGL_FATAL, "No SI when leaving idle, please fix!\n");
exit(0L);
}
cs->sync_retries = SYNC_RETRIES;
gsm322_sync_to_cell(cs, NULL, 1);
return 0;
}
static int gsm322_c_conn_mode_2(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
/* check for error */
if (!cs->selected) {
LOGP(DCS, LOGL_INFO, "No cell selected, please fix!\n");
exit(0L);
}
cs->arfcn = cs->sel_arfcn;
cs->arfci = arfcn2index(cs->arfcn);
stop_cs_timer(cs);
new_c_state(cs, GSM322_CONNECTED_MODE_2);
/* be sure to go to current camping frequency on return */
LOGP(DCS, LOGL_INFO, "Going to camping (any cell) ARFCN %s.\n",
gsm_print_arfcn(cs->arfcn));
cs->si = cs->list[cs->arfci].sysinfo;
if (!cs->si) {
LOGP(DCS, LOGL_FATAL, "No SI when leaving idle, please fix!\n");
exit(0L);
}
cs->sync_retries = SYNC_RETRIES;
gsm322_sync_to_cell(cs, NULL, 1);
return 0;
}
/* switch on */
static int gsm322_c_switch_on(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm_subscriber *subscr = &ms->subscr;
/* if no SIM is is MS */
if (!subscr->sim_valid) {
LOGP(DCS, LOGL_INFO, "Switch on without SIM.\n");
return gsm322_c_any_cell_sel(ms, msg);
}
LOGP(DCS, LOGL_INFO, "Switch on with SIM inserted.\n");
/* stay in NULL state until PLMN is selected */
return 0;
}
/*
* state machines
*/
/* state machine for automatic PLMN selection events */
static struct plmnastatelist {
uint32_t states;
int type;
int (*rout) (struct osmocom_ms *ms, struct msgb *msg);
} plmnastatelist[] = {
{SBIT(GSM322_A0_NULL),
GSM322_EVENT_SWITCH_ON, gsm322_a_switch_on},
/* special case for full search */
{SBIT(GSM322_A0_NULL),
GSM322_EVENT_PLMN_SEARCH_END, gsm322_a_sel_first_plmn},
{ALL_STATES,
GSM322_EVENT_SWITCH_OFF, gsm322_a_switch_off},
{SBIT(GSM322_A0_NULL) | SBIT(GSM322_A6_NO_SIM),
GSM322_EVENT_SIM_INSERT, gsm322_a_switch_on},
{ALL_STATES,
GSM322_EVENT_SIM_INSERT, gsm322_a_sim_insert},
{ALL_STATES,
GSM322_EVENT_SIM_REMOVE, gsm322_a_sim_removed},
{ALL_STATES,
GSM322_EVENT_INVALID_SIM, gsm322_a_sim_removed},
{SBIT(GSM322_A1_TRYING_RPLMN),
GSM322_EVENT_REG_FAILED, gsm322_a_sel_first_plmn},
{SBIT(GSM322_A1_TRYING_RPLMN),
GSM322_EVENT_ROAMING_NA, gsm322_a_sel_first_plmn},
{SBIT(GSM322_A1_TRYING_RPLMN),
GSM322_EVENT_NO_CELL_FOUND, gsm322_a_sel_first_plmn},
{SBIT(GSM322_A1_TRYING_RPLMN) | SBIT(GSM322_A3_TRYING_PLMN),
GSM322_EVENT_REG_SUCCESS, gsm322_a_go_on_plmn},
{SBIT(GSM322_A2_ON_PLMN),
GSM322_EVENT_ROAMING_NA, gsm322_a_roaming_na},
{SBIT(GSM322_A2_ON_PLMN),
GSM322_EVENT_HPLMN_SEARCH, gsm322_a_hplmn_search_start},
{SBIT(GSM322_A2_ON_PLMN),
GSM322_EVENT_NO_CELL_FOUND, gsm322_a_loss_of_radio},
{SBIT(GSM322_A2_ON_PLMN),
GSM322_EVENT_USER_RESEL, gsm322_a_user_resel},
{SBIT(GSM322_A3_TRYING_PLMN),
GSM322_EVENT_REG_FAILED, gsm322_a_sel_next_plmn},
{SBIT(GSM322_A3_TRYING_PLMN),
GSM322_EVENT_ROAMING_NA, gsm322_a_sel_next_plmn},
{SBIT(GSM322_A3_TRYING_PLMN),
GSM322_EVENT_NO_CELL_FOUND, gsm322_a_sel_next_plmn},
{SBIT(GSM322_A5_HPLMN_SEARCH),
GSM322_EVENT_CELL_FOUND, gsm322_a_sel_first_plmn},
{SBIT(GSM322_A5_HPLMN_SEARCH),
GSM322_EVENT_NO_CELL_FOUND, gsm322_a_go_on_plmn},
{SBIT(GSM322_A4_WAIT_FOR_PLMN),
GSM322_EVENT_PLMN_AVAIL, gsm322_a_plmn_avail},
{ALL_STATES,
GSM322_EVENT_SEL_MANUAL, gsm322_a_sel_manual},
{ALL_STATES,
GSM322_EVENT_NO_CELL_FOUND, gsm322_am_no_cell_found},
};
#define PLMNASLLEN \
(sizeof(plmnastatelist) / sizeof(struct plmnastatelist))
static int gsm322_a_event(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
int msg_type = gm->msg_type;
int rc;
int i;
LOGP(DPLMN, LOGL_INFO, "(ms %s) Event '%s' for automatic PLMN "
"selection in state '%s'\n", ms->name, get_event_name(msg_type),
get_a_state_name(plmn->state));
/* find function for current state and message */
for (i = 0; i < PLMNASLLEN; i++)
if ((msg_type == plmnastatelist[i].type)
&& ((1 << plmn->state) & plmnastatelist[i].states))
break;
if (i == PLMNASLLEN) {
LOGP(DPLMN, LOGL_NOTICE, "Event unhandled at this state.\n");
return 0;
}
rc = plmnastatelist[i].rout(ms, msg);
return rc;
}
/* state machine for manual PLMN selection events */
static struct plmnmstatelist {
uint32_t states;
int type;
int (*rout) (struct osmocom_ms *ms, struct msgb *msg);
} plmnmstatelist[] = {
{SBIT(GSM322_M0_NULL),
GSM322_EVENT_SWITCH_ON, gsm322_m_switch_on},
{SBIT(GSM322_M0_NULL) | SBIT(GSM322_M3_NOT_ON_PLMN) |
SBIT(GSM322_M2_ON_PLMN),
GSM322_EVENT_PLMN_SEARCH_END, gsm322_m_display_plmns},
{ALL_STATES,
GSM322_EVENT_SWITCH_OFF, gsm322_m_switch_off},
{SBIT(GSM322_M0_NULL) | SBIT(GSM322_M5_NO_SIM),
GSM322_EVENT_SIM_INSERT, gsm322_m_switch_on},
{ALL_STATES,
GSM322_EVENT_SIM_INSERT, gsm322_m_sim_insert},
{ALL_STATES,
GSM322_EVENT_SIM_REMOVE, gsm322_m_sim_removed},
{SBIT(GSM322_M1_TRYING_RPLMN),
GSM322_EVENT_REG_FAILED, gsm322_m_display_plmns},
{SBIT(GSM322_M1_TRYING_RPLMN),
GSM322_EVENT_ROAMING_NA, gsm322_m_display_plmns},
{SBIT(GSM322_M1_TRYING_RPLMN),
GSM322_EVENT_NO_CELL_FOUND, gsm322_m_display_plmns},
{SBIT(GSM322_M1_TRYING_RPLMN),
GSM322_EVENT_REG_SUCCESS, gsm322_m_go_on_plmn},
{SBIT(GSM322_M2_ON_PLMN),
GSM322_EVENT_ROAMING_NA, gsm322_m_display_plmns},
/* undocumented case, where we loose coverage */
{SBIT(GSM322_M2_ON_PLMN),
GSM322_EVENT_NO_CELL_FOUND, gsm322_m_display_plmns},
{SBIT(GSM322_M1_TRYING_RPLMN) | SBIT(GSM322_M2_ON_PLMN) |
SBIT(GSM322_M4_TRYING_PLMN),
GSM322_EVENT_INVALID_SIM, gsm322_m_sim_removed},
{SBIT(GSM322_M3_NOT_ON_PLMN) | SBIT(GSM322_M2_ON_PLMN),
GSM322_EVENT_USER_RESEL, gsm322_m_user_resel},
{SBIT(GSM322_M3_NOT_ON_PLMN),
GSM322_EVENT_PLMN_AVAIL, gsm322_m_plmn_avail},
/* choose plmn is only specified when 'not on PLMN', but it makes
* sense to select cell from other states too. */
{SBIT(GSM322_M3_NOT_ON_PLMN) | SBIT(GSM322_M2_ON_PLMN) |
SBIT(GSM322_M1_TRYING_RPLMN) | SBIT(GSM322_M4_TRYING_PLMN),
GSM322_EVENT_CHOOSE_PLMN, gsm322_m_choose_plmn},
{SBIT(GSM322_M4_TRYING_PLMN),
GSM322_EVENT_REG_SUCCESS, gsm322_m_go_on_plmn},
/* we also display available PLMNs after trying to register.
* this is not standard. we need that so the user knows
* that registration failed, and the user can select a new network. */
{SBIT(GSM322_M4_TRYING_PLMN),
GSM322_EVENT_REG_FAILED, gsm322_m_display_plmns},
{SBIT(GSM322_M4_TRYING_PLMN),
GSM322_EVENT_ROAMING_NA, gsm322_m_display_plmns},
{SBIT(GSM322_M4_TRYING_PLMN),
GSM322_EVENT_NO_CELL_FOUND, gsm322_m_display_plmns},
{ALL_STATES,
GSM322_EVENT_SEL_AUTO, gsm322_m_sel_auto},
{ALL_STATES,
GSM322_EVENT_NO_CELL_FOUND, gsm322_am_no_cell_found},
};
#define PLMNMSLLEN \
(sizeof(plmnmstatelist) / sizeof(struct plmnmstatelist))
static int gsm322_m_event(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
int msg_type = gm->msg_type;
int rc;
int i;
LOGP(DPLMN, LOGL_INFO, "(ms %s) Event '%s' for manual PLMN selection "
"in state '%s'\n", ms->name, get_event_name(msg_type),
get_m_state_name(plmn->state));
/* find function for current state and message */
for (i = 0; i < PLMNMSLLEN; i++)
if ((msg_type == plmnmstatelist[i].type)
&& ((1 << plmn->state) & plmnmstatelist[i].states))
break;
if (i == PLMNMSLLEN) {
LOGP(DPLMN, LOGL_NOTICE, "Event unhandled at this state.\n");
return 0;
}
rc = plmnmstatelist[i].rout(ms, msg);
return rc;
}
/* dequeue GSM 03.22 PLMN events */
int gsm322_plmn_dequeue(struct osmocom_ms *ms)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct msgb *msg;
int work = 0;
while ((msg = msgb_dequeue(&plmn->event_queue))) {
/* send event to PLMN select process */
if (ms->settings.plmn_mode == PLMN_MODE_AUTO)
gsm322_a_event(ms, msg);
else
gsm322_m_event(ms, msg);
msgb_free(msg);
work = 1; /* work done */
}
return work;
}
/* state machine for channel selection events */
static struct cellselstatelist {
uint32_t states;
int type;
int (*rout) (struct osmocom_ms *ms, struct msgb *msg);
} cellselstatelist[] = {
{ALL_STATES,
GSM322_EVENT_SWITCH_ON, gsm322_c_switch_on},
{ALL_STATES,
GSM322_EVENT_SIM_REMOVE, gsm322_c_sim_remove},
{ALL_STATES,
GSM322_EVENT_NEW_PLMN, gsm322_c_new_plmn},
{ALL_STATES,
GSM322_EVENT_PLMN_SEARCH_START, gsm322_c_plmn_search},
{SBIT(GSM322_C1_NORMAL_CELL_SEL) | SBIT(GSM322_C2_STORED_CELL_SEL) |
SBIT(GSM322_C4_NORMAL_CELL_RESEL) | SBIT(GSM322_C5_CHOOSE_CELL),
GSM322_EVENT_CELL_FOUND, gsm322_c_camp_normally},
{SBIT(GSM322_C9_CHOOSE_ANY_CELL) | SBIT(GSM322_C6_ANY_CELL_SEL) |
SBIT(GSM322_C8_ANY_CELL_RESEL),
GSM322_EVENT_CELL_FOUND, gsm322_c_camp_any_cell},
{SBIT(GSM322_C1_NORMAL_CELL_SEL) | SBIT(GSM322_C6_ANY_CELL_SEL) |
SBIT(GSM322_C9_CHOOSE_ANY_CELL) | SBIT(GSM322_C8_ANY_CELL_RESEL) |
SBIT(GSM322_C0_NULL) /* after search */,
GSM322_EVENT_NO_CELL_FOUND, gsm322_c_any_cell_sel},
{SBIT(GSM322_C2_STORED_CELL_SEL) | SBIT(GSM322_C5_CHOOSE_CELL) |
SBIT(GSM322_C4_NORMAL_CELL_RESEL),
GSM322_EVENT_NO_CELL_FOUND, gsm322_c_normal_cell_sel},
{SBIT(GSM322_C3_CAMPED_NORMALLY),
GSM322_EVENT_LEAVE_IDLE, gsm322_c_conn_mode_1},
{SBIT(GSM322_C7_CAMPED_ANY_CELL),
GSM322_EVENT_LEAVE_IDLE, gsm322_c_conn_mode_2},
{SBIT(GSM322_CONNECTED_MODE_1),
GSM322_EVENT_RET_IDLE, gsm322_c_choose_cell},
{SBIT(GSM322_CONNECTED_MODE_2),
GSM322_EVENT_RET_IDLE, gsm322_c_choose_any_cell},
{SBIT(GSM322_C3_CAMPED_NORMALLY),
GSM322_EVENT_CELL_RESEL, gsm322_c_normal_cell_resel},
{SBIT(GSM322_C7_CAMPED_ANY_CELL),
GSM322_EVENT_CELL_RESEL, gsm322_c_any_cell_resel},
{SBIT(GSM322_C7_CAMPED_ANY_CELL),
GSM322_EVENT_CELL_FOUND, gsm322_c_normal_cell_sel},
{SBIT(GSM322_C1_NORMAL_CELL_SEL) | SBIT(GSM322_C2_STORED_CELL_SEL) |
SBIT(GSM322_C4_NORMAL_CELL_RESEL) | SBIT(GSM322_C5_CHOOSE_CELL) |
SBIT(GSM322_C9_CHOOSE_ANY_CELL) | SBIT(GSM322_C8_ANY_CELL_RESEL) |
SBIT(GSM322_C6_ANY_CELL_SEL) | SBIT(GSM322_ANY_SEARCH) |
SBIT(GSM322_PLMN_SEARCH) | SBIT(GSM322_HPLMN_SEARCH) ,
GSM322_EVENT_SYSINFO, gsm322_c_scan_sysinfo_bcch},
{SBIT(GSM322_C3_CAMPED_NORMALLY) | SBIT(GSM322_C7_CAMPED_ANY_CELL),
GSM322_EVENT_SYSINFO, gsm322_c_camp_sysinfo_bcch},
{SBIT(GSM322_C3_CAMPED_NORMALLY),
GSM322_EVENT_HPLMN_SEARCH, gsm322_c_hplmn_search},
};
#define CELLSELSLLEN \
(sizeof(cellselstatelist) / sizeof(struct cellselstatelist))
int gsm322_c_event(struct osmocom_ms *ms, struct msgb *msg)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm322_msg *gm = (struct gsm322_msg *) msg->data;
int msg_type = gm->msg_type;
int rc;
int i;
if (msg_type != GSM322_EVENT_SYSINFO)
LOGP(DCS, LOGL_INFO, "(ms %s) Event '%s' for Cell selection "
"in state '%s'\n", ms->name, get_event_name(msg_type),
get_cs_state_name(cs->state));
/* find function for current state and message */
for (i = 0; i < CELLSELSLLEN; i++)
if ((msg_type == cellselstatelist[i].type)
&& ((1 << cs->state) & cellselstatelist[i].states))
break;
if (i == CELLSELSLLEN) {
if (msg_type != GSM322_EVENT_SYSINFO)
LOGP(DCS, LOGL_NOTICE, "Event unhandled at this state."
"\n");
return 0;
}
rc = cellselstatelist[i].rout(ms, msg);
return rc;
}
/* dequeue GSM 03.22 cell selection events */
int gsm322_cs_dequeue(struct osmocom_ms *ms)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct msgb *msg;
int work = 0;
while ((msg = msgb_dequeue(&cs->event_queue))) {
/* send event to cell selection process */
gsm322_c_event(ms, msg);
msgb_free(msg);
work = 1; /* work done */
}
return work;
}
/*
* neighbour cell measurement process in idle mode
*/
static struct gsm322_neighbour *gsm322_nb_alloc(struct gsm322_cellsel *cs,
uint16_t arfcn)
{
struct gsm322_neighbour *nb;
time_t now;
time(&now);
nb = talloc_zero(cs->ms, struct gsm322_neighbour);
if (!nb)
return 0;
nb->cs = cs;
nb->arfcn = arfcn;
nb->rla_c_dbm = -128;
nb->created = now;
llist_add_tail(&nb->entry, &cs->nb_list);
return nb;
}
static void gsm322_nb_free(struct gsm322_neighbour *nb)
{
llist_del(&nb->entry);
talloc_free(nb);
}
/* check and calculate reselection criterion for all 6 neighbour cells and
* return, if cell reselection has to be triggered */
static int gsm322_nb_check(struct osmocom_ms *ms, int any)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm_subscriber *subscr = &ms->subscr;
struct gsm_settings *set = &ms->settings;
struct gsm48_sysinfo *s;
int i = 0, reselect = 0;
uint16_t acc_class;
int class;
enum gsm_band band;
struct gsm322_neighbour *nb;
time_t now;
char arfcn_text[10];
time(&now);
/* set out access class depending on the cell selection type */
if (any) {
acc_class = (subscr->acc_class | 0x0400); /* add emergency */
LOGP(DNB, LOGL_DEBUG, "Re-select using access class with "
"Emergency class.\n");
} else {
acc_class = subscr->acc_class;
LOGP(DNB, LOGL_DEBUG, "Re-select using access class.\n");
}
if (ms->rrlayer.monitor) {
vty_notify(ms, "MON: cell ARFCN LAC C1 C2 CRH RLA_C "
"bargraph\n");
snprintf(arfcn_text, 10, "%s ",
gsm_print_arfcn(cs->sel_arfcn));
arfcn_text[9] = '\0';
vty_notify(ms, "MON: serving %s 0x%04x %3d %3d %4d "
"%s\n", arfcn_text, cs->sel_lac, cs->c1, cs->c2,
cs->rla_c_dbm, bargraph(cs->rla_c_dbm / 2, -55, -24));
}
/* loop through all neighbour cells and select best cell */
llist_for_each_entry(nb, &cs->nb_list, entry) {
LOGP(DNB, LOGL_INFO, "Checking cell of ARFCN %s for cell "
"re-selection.\n", gsm_print_arfcn(nb->arfcn));
s = cs->list[arfcn2index(nb->arfcn)].sysinfo;
nb->checked_for_resel = 0;
nb->suitable_allowable = 0;
nb->c12_valid = 1;
nb->prio_low = 0;
if (nb->state == GSM322_NB_NOT_SUP) {
LOGP(DNB, LOGL_INFO, "Skip cell: ARFCN not supported."
"\n");
if (ms->rrlayer.monitor) {
snprintf(arfcn_text, 10, "%s ",
gsm_print_arfcn(nb->arfcn));
arfcn_text[9] = '\0';
vty_notify(ms, "MON: nb %2d %s ARFCN not "
"supported\n", i + 1, arfcn_text);
}
goto cont;
}
/* check if we have successfully read BCCH */
if (!s || nb->state != GSM322_NB_SYSINFO) {
LOGP(DNB, LOGL_INFO, "Skip cell: There are no system "
"information available.\n");
if (ms->rrlayer.monitor) {
snprintf(arfcn_text, 10, "%s ",
gsm_print_arfcn(nb->arfcn));
arfcn_text[9] = '\0';
vty_notify(ms, "MON: nb %2d %s "
" %4d %s\n",
i + 1, arfcn_text, nb->rla_c_dbm,
bargraph(nb->rla_c_dbm / 2, -55, -24));
}
goto cont;
}
/* get prio */
if (s->sp && s->sp_cbq)
nb->prio_low = 1;
/* get C1 & C2 */
gsm_arfcn2band_rc(nb->arfcn, &band);
class = class_of_band(ms, band);
nb->c1 = calculate_c1(DNB, nb->rla_c_dbm, s->rxlev_acc_min_db,
ms_pwr_dbm(band, s->ms_txpwr_max_cch),
ms_class_gmsk_dbm(band, class));
nb->c2 = calculate_c2(nb->c1, 0,
(cs->last_serving_valid
&& cs->last_serving_arfcn == nb->arfcn),
s->sp, s->sp_cro, now - nb->created, s->sp_pt,
s->sp_to);
nb->c12_valid = 1;
/* calculate CRH depending on LAI */
if (cs->sel_mcc == s->mcc && cs->sel_mnc == s->mnc
&& cs->sel_lac == s->lac) {
LOGP(DNB, LOGL_INFO, "-> Cell of is in the same LA, "
"so CRH = 0\n");
nb->crh = 0;
} else if (any) {
LOGP(DNB, LOGL_INFO, "-> Cell of is in a different LA, "
"but service is limited, so CRH = 0\n");
nb->crh = 0;
} else {
nb->crh = s->cell_resel_hyst_db;
LOGP(DNB, LOGL_INFO, "-> Cell of is in a different LA, "
"and service is normal, so CRH = %d\n",
nb->crh);
}
if (ms->rrlayer.monitor) {
snprintf(arfcn_text, 10, "%s ",
gsm_print_arfcn(nb->arfcn));
arfcn_text[9] = '\0';
vty_notify(ms, "MON: nb %2d %s 0x%04x %3d %3d %2d"
" %4d %s\n", i + 1, arfcn_text, s->lac,
nb->c1, nb->c2, nb->crh, nb->rla_c_dbm,
bargraph(nb->rla_c_dbm / 2, -55, -24));
}
/* if cell is barred and we don't override */
if (s->cell_barr && !(s->sp && s->sp_cbq)) {
LOGP(DNB, LOGL_INFO, "Skip cell: Cell is barred.\n");
goto cont;
}
/* if we have no access to the cell and we don't override */
if (!subscr->acc_barr
&& !(acc_class & (s->class_barr ^ 0xffff))) {
LOGP(DNB, LOGL_INFO, "Skip cell: Class is "
"barred for our access. (access=%04x "
"barred=%04x)\n", acc_class, s->class_barr);
goto cont;
}
/* check if LA is forbidden */
if (any && gsm322_is_forbidden_la(ms, s->mcc, s->mnc, s->lac)) {
LOGP(DNB, LOGL_INFO, "Skip cell: Cell has "
"forbidden LA.\n");
goto cont;
}
/* check if we have same PLMN */
if (!any && (cs->sel_mcc != s->mcc || cs->sel_mnc != s->mnc)) {
LOGP(DNB, LOGL_INFO, "Skip cell: PLMN of cell "
"does not match target PLMN. (cell: mcc=%s "
"mnc=%s)\n", gsm_print_mcc(s->mcc),
gsm_print_mnc(s->mnc));
goto cont;
}
/* check criterion C1 */
if (nb->c1 < 0) {
LOGP(DNB, LOGL_INFO, "Skip cell: C1 criterion "
" (>0) not met. (C1 = %d)\n", nb->c1);
goto cont;
}
/* we can use this cell, if it is better */
nb->suitable_allowable = 1;
/* check priority */
if (!cs->prio_low && nb->prio_low) {
LOGP(DNB, LOGL_INFO, "Skip cell: cell has low "
"priority, but serving cell has normal "
"prio.\n");
goto cont;
}
if (cs->prio_low && !nb->prio_low) {
LOGP(DNB, LOGL_INFO, "Found cell: cell has normal "
"priority, but serving cell has low prio.\n");
reselect = 1;
goto cont;
}
/* find better cell */
if (nb->c2 - nb->crh > cs->c2) {
LOGP(DNB, LOGL_INFO, "Found cell: cell is better "
"than serving cell.\n");
reselect = 1;
goto cont;
}
cont:
if (++i == GSM58_NB_NUMBER)
break;
}
if (!i) {
if (ms->rrlayer.monitor)
vty_notify(ms, "MON: no neighbour cells\n");
}
if (cs->resel_when + GSM58_RESEL_THRESHOLD >= now) {
LOGP(DNB, LOGL_INFO, "Found better neighbour cell, but "
"reselection threshold not reached.\n");
reselect = 0;
}
if (reselect && set->stick) {
LOGP(DNB, LOGL_INFO, "Don't trigger cell re-selection, because "
"we stick to serving cell.\n");
reselect = 0;
}
return reselect;
}
/* select a suitable and allowable cell */
static int gsm322_nb_scan(struct osmocom_ms *ms)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm_settings *set = &ms->settings;
int i = 0;
struct gsm322_neighbour *nb, *best_nb_low = NULL, *best_nb_normal = 0;
int16_t best_low = -32768, best_normal = -32768;
if (set->stick) {
LOGP(DCS, LOGL_DEBUG, "Do not re-select cell, because we stick "
" to a cell.\n");
goto no_cell_found;
}
if (!cs->c12_valid) {
LOGP(DCS, LOGL_DEBUG, "Do not re-select cell, because there "
" are no valid C1 and C2.\n");
goto no_cell_found;
}
/* loop through all neighbour cells and select best cell */
llist_for_each_entry(nb, &cs->nb_list, entry) {
LOGP(DCS, LOGL_INFO, "Checking cell with ARFCN %s for cell "
"re-selection. (C2 = %d)\n", gsm_print_arfcn(nb->arfcn),
nb->c2);
/* track which cells have been checked do far */
if (nb->checked_for_resel) {
LOGP(DCS, LOGL_INFO, "Skip cell: already tried to "
"select.\n");
goto cont;
}
/* check if we can use this cell */
if (!nb->suitable_allowable) {
LOGP(DCS, LOGL_INFO, "Skip cell: not suitable and/or "
"allowable.\n");
goto cont;
}
/* check if cell is "better" */
if (nb->prio_low) {
if (nb->c2 - nb->crh > best_low) {
best_low = nb->c2 - nb->crh;
best_nb_low = nb;
}
} else {
if (nb->c2 - nb->crh > best_normal) {
best_normal = nb->c2 - nb->crh;
best_nb_normal = nb;
}
}
cont:
if (++i == GSM58_NB_NUMBER)
break;
}
nb = NULL;
if (best_nb_normal) {
nb = best_nb_normal;
LOGP(DCS, LOGL_INFO, "Best neighbour cell with ARFCN %s "
"selected. (normal priority)\n",
gsm_print_arfcn(nb->arfcn));
}
if (best_nb_low) {
nb = best_nb_low;
LOGP(DCS, LOGL_INFO, "Best neighbour cell with ARFCN %s "
"selected. (low priority)\n",
gsm_print_arfcn(nb->arfcn));
}
if (!nb) {
struct msgb *nmsg;
LOGP(DCS, LOGL_INFO, "No (more) acceptable neighbour cell "
"available\n");
no_cell_found:
/* Tell cell selection process to handle "no cell found". */
nmsg = gsm322_msgb_alloc(GSM322_EVENT_NO_CELL_FOUND);
if (!nmsg)
return -ENOMEM;
gsm322_cs_sendmsg(ms, nmsg);
return 0;
}
nb->checked_for_resel = 1;
/* NOTE: We might already have system information from previous
* scan. But we need recent information, so we scan again!
*/
/* Tune to frequency for a while, to receive broadcasts. */
cs->arfcn = nb->arfcn;
cs->arfci = arfcn2index(cs->arfcn);
LOGP(DCS, LOGL_DEBUG, "Scanning ARFCN %s of neighbour "
"cell during cell reselection.\n", gsm_print_arfcn(cs->arfcn));
/* Allocate/clean system information. */
cs->list[cs->arfci].flags &= ~GSM322_CS_FLAG_SYSINFO;
if (cs->list[cs->arfci].sysinfo)
memset(cs->list[cs->arfci].sysinfo, 0,
sizeof(struct gsm48_sysinfo));
else
cs->list[cs->arfci].sysinfo = talloc_zero(ms,
struct gsm48_sysinfo);
if (!cs->list[cs->arfci].sysinfo)
exit(-ENOMEM);
cs->si = cs->list[cs->arfci].sysinfo;
cs->sync_retries = SYNC_RETRIES;
return gsm322_sync_to_cell(cs, NULL, 0);
}
/* start/modify measurement process with the current list of neighbour cells.
* only do that if: 1. we are camping 2. we are on serving cell */
static int gsm322_nb_start(struct osmocom_ms *ms, int synced)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm48_sysinfo *s = &cs->sel_si;
struct gsm322_neighbour *nb, *nb2;
int i, num;
uint8_t map[128];
uint16_t nc[32];
uint8_t changed = 0;
int refer_pcs, index;
uint16_t arfcn;
if (cs->ms->settings.no_neighbour)
return 0;
if (synced)
cs->nb_meas_set = 0;
refer_pcs = gsm_refer_pcs(cs->sel_arfcn, s);
/* remove all neighbours that are not in list anymore */
memset(map, 0, sizeof(map));
llist_for_each_entry_safe(nb, nb2, &cs->nb_list, entry) {
i = nb->arfcn & 1023;
map[i >> 3] |= (1 << (i & 7));
#ifndef TEST_INCLUDE_SERV
if (!(s->freq[i].mask & FREQ_TYPE_NCELL)) {
#else
if (!(s->freq[i].mask & (FREQ_TYPE_NCELL | FREQ_TYPE_SERV))) {
#endif
LOGP(DNB, LOGL_INFO, "Removing neighbour cell %s from "
"list.\n", gsm_print_arfcn(nb->arfcn));
gsm322_nb_free(nb);
changed = 1;
continue;
}
#ifndef TEST_INCLUDE_SERV
if (nb->arfcn == cs->sel_arfcn) {
LOGP(DNB, LOGL_INFO, "Removing serving cell %s (former "
"neighbour cell).\n",
gsm_print_arfcn(nb->arfcn));
gsm322_nb_free(nb);
changed = 1;
continue;
}
#endif
}
/* add missing entries to list */
for (i = 0; i <= 1023; i++) {
#ifndef TEST_INCLUDE_SERV
if ((s->freq[i].mask & FREQ_TYPE_NCELL) &&
!(map[i >> 3] & (1 << (i & 7)))) {
#else
if ((s->freq[i].mask & (FREQ_TYPE_NCELL | FREQ_TYPE_SERV)) &&
!(map[i >> 3] & (1 << (i & 7)))) {
#endif
index = i;
if (refer_pcs && i >= 512 && i <= 810)
index = i-512+1024;
arfcn = index2arfcn(index);
#ifndef TEST_INCLUDE_SERV
if (arfcn == cs->sel_arfcn) {
LOGP(DNB, LOGL_INFO, "Omitting serving cell %s."
"\n", gsm_print_arfcn(cs->arfcn));
continue;
}
#endif
nb = gsm322_nb_alloc(cs, arfcn);
LOGP(DNB, LOGL_INFO, "Adding neighbour cell %s to "
"list.\n", gsm_print_arfcn(nb->arfcn));
if (!(cs->list[index].flags & GSM322_CS_FLAG_SUPPORT))
nb->state = GSM322_NB_NOT_SUP;
changed = 1;
}
}
/* if nothing has changed, we are done */
if (!changed && cs->nb_meas_set)
return 0;
/* start neighbour cell measurement task */
num = 0;
llist_for_each_entry(nb, &cs->nb_list, entry) {
if (nb->state == GSM322_NB_NOT_SUP)
continue;
/* it should not happen that there are more than 32 nb-cells */
if (num == 32)
break;
nc[num] = nb->arfcn;
num++;
}
LOGP(DNB, LOGL_INFO, "Sending list of neighbour cells to layer1.\n");
l1ctl_tx_neigh_pm_req(ms, num, nc);
cs->nb_meas_set = 1;
return 1;
}
/* a complete set of measurements are received, calculate the RLA_C, sort */
static int gsm322_nb_trigger_event(struct gsm322_cellsel *cs)
{
struct osmocom_ms *ms = cs->ms;
struct gsm322_neighbour *nb, *nb_sync = NULL, *nb_again = NULL;
int i = 0;
time_t now;
time(&now);
/* check the list for reading neighbour cell's BCCH */
llist_for_each_entry(nb, &cs->nb_list, entry) {
if (nb->rla_c_dbm >= cs->ms->settings.min_rxlev_dbm) {
/* select the strongest unsynced cell */
if (nb->state == GSM322_NB_RLA_C) {
nb_sync = nb;
break;
}
#if 0
if (nb->state == GSM322_NB_SYSINFO) {
printf("%d time to sync again: %u\n", nb->arfcn, now + GSM58_READ_AGAIN - nb->when);
}
#endif
/* select the strongest cell to be read/try again */
if (!nb_again) {
if ((nb->state == GSM322_NB_NO_SYNC
|| nb->state == GSM322_NB_NO_BCCH)
&& nb->when + GSM58_TRY_AGAIN <= now)
nb_again = nb;
else
if (nb->state == GSM322_NB_SYSINFO
&& nb->when + GSM58_READ_AGAIN <= now)
nb_again = nb;
}
}
if (++i == GSM58_NB_NUMBER)
break;
}
/* trigger sync to neighbour cell, priorize the untested cell */
if (nb_sync || nb_again) {
if (nb_sync) {
nb = nb_sync;
cs->arfcn = nb->arfcn;
cs->arfci = arfcn2index(cs->arfcn);
LOGP(DNB, LOGL_INFO, "Syncing to new neighbour cell "
"%s.\n", gsm_print_arfcn(cs->arfcn));
} else {
nb = nb_again;
cs->arfcn = nb->arfcn;
cs->arfci = arfcn2index(cs->arfcn);
LOGP(DNB, LOGL_INFO, "Syncing again to neighbour cell "
"%s after timerout.\n",
gsm_print_arfcn(cs->arfcn));
}
/* Allocate/clean system information. */
cs->list[cs->arfci].flags &= ~GSM322_CS_FLAG_SYSINFO;
if (cs->list[cs->arfci].sysinfo)
memset(cs->list[cs->arfci].sysinfo, 0,
sizeof(struct gsm48_sysinfo));
else
cs->list[cs->arfci].sysinfo = talloc_zero(ms,
struct gsm48_sysinfo);
if (!cs->list[cs->arfci].sysinfo)
exit(-ENOMEM);
cs->si = cs->list[cs->arfci].sysinfo;
cs->sync_retries = SYNC_RETRIES;
return gsm322_sync_to_cell(cs, nb, 0);
}
if (gsm322_nb_check(ms, (cs->state == GSM322_C7_CAMPED_ANY_CELL)) > 0) {
struct msgb *nmsg;
LOGP(DNB, LOGL_INFO, "Better neighbour cell triggers cell "
"reselection.\n");
if (ms->rrlayer.monitor)
vty_notify(ms, "MON: trigger cell re-selection: "
"better cell\n");
cs->resel_when = now;
/* unset selected cell */
gsm322_unselect_cell(cs);
nmsg = gsm322_msgb_alloc(GSM322_EVENT_CELL_RESEL);
if (!nmsg)
return -ENOMEM;
gsm322_c_event(ms, nmsg);
msgb_free(nmsg);
return 0;
}
if (cs->neighbour) {
cs->arfcn = cs->sel_arfcn;
cs->arfci = arfcn2index(cs->arfcn);
cs->si = cs->list[cs->arfci].sysinfo;
if (!cs->si) {
LOGP(DNB, LOGL_FATAL, "No SI after neighbour scan, please fix!\n");
exit(0L);
}
LOGP(DNB, LOGL_INFO, "Syncing back to serving cell\n");
cs->sync_retries = SYNC_RETRIES_SERVING;
return gsm322_sync_to_cell(cs, NULL, 0);
}
/* do nothing */
return 0;
}
/* we (successfully) synced to a neighbour */
static int gsm322_nb_synced(struct gsm322_cellsel *cs, int yes)
{
time_t now;
LOGP(DNB, LOGL_INFO, "%s to neighbour cell %d.\n",
(yes) ? "Synced" : "Failed to sync", cs->arfcn);
if (yes) {
start_cs_timer(cs, GSM322_NB_TIMEOUT, 0);
return 0;
}
cs->neighbour->state = GSM322_NB_NO_SYNC;
time(&now);
cs->neighbour->when = now;
return gsm322_nb_trigger_event(cs);
}
/* we (successfully) read the neighbour */
static int gsm322_nb_read(struct gsm322_cellsel *cs, int yes)
{
time_t now;
LOGP(DNB, LOGL_INFO, "%s from neighbour cell %d (rxlev %s).\n",
(yes) ? "Read" : "Failed to read",
cs->arfcn, gsm_print_rxlev(cs->list[cs->arfci].rxlev));
cs->neighbour->state = (yes) ? GSM322_NB_SYSINFO : GSM322_NB_NO_BCCH;
time(&now);
cs->neighbour->when = now;
return gsm322_nb_trigger_event(cs);
}
/* a complete set of measurements are received, calculate the RLA_C, sort */
static int gsm322_nb_new_rxlev(struct gsm322_cellsel *cs)
{
struct gsm322_neighbour *nb, *strongest_nb;
int i = 0;
int8_t strongest;
struct llist_head sorted;
struct llist_head *lh, *lh2;
struct gsm48_sysinfo *s = &cs->sel_si;
enum gsm_band band;
int class;
gsm_arfcn2band_rc(cs->arfcn, &band);
class = class_of_band(cs->ms, band);
/* calculate the RAL_C of serving cell */
if (cs->rxlev_count) {
cs->rla_c_dbm = (cs->rxlev_sum_dbm + (cs->rxlev_count / 2))
/ cs->rxlev_count;
cs->rxlev_sum_dbm = 0;
cs->rxlev_count = 0;
}
LOGP(DNB, LOGL_INFO, "RLA_C of serving cell: %d\n", cs->rla_c_dbm);
/* calculate C1 criterion, SI 3 carries complete neighbour cell info */
cs->prio_low = 0;
if (s && (s->si3 || s->si4)) {
cs->c1 = calculate_c1(DNB, cs->rla_c_dbm, s->rxlev_acc_min_db,
ms_pwr_dbm(band, s->ms_txpwr_max_cch),
ms_class_gmsk_dbm(band, class));
cs->c2 = calculate_c2(cs->c1, 1, 0, s->sp, s->sp_cro, 0, s->sp_pt, s->sp_to);
cs->c12_valid = 1;
if (s->sp && s->sp_cbq)
cs->prio_low = 1;
}
/* calculate the RAL_C of neighbours */
llist_for_each_entry(nb, &cs->nb_list, entry) {
if (nb->state == GSM322_NB_NOT_SUP)
continue;
/* if sysinfo is gone due to scanning, mark neighbour as
* unscanned. */
if (nb->state == GSM322_NB_SYSINFO) {
if (!cs->list[arfcn2index(nb->arfcn)].sysinfo) {
nb->state = GSM322_NB_NO_BCCH;
nb->when = 0;
}
}
nb->rla_c_dbm =
(nb->rxlev_sum_dbm + (nb->rxlev_count / 2))
/ nb->rxlev_count;
nb->rxlev_count = 0;
nb->rxlev_sum_dbm = 0;
if (nb->state == GSM322_NB_NEW)
nb->state = GSM322_NB_RLA_C;
}
/* sort the 6 strongest */
INIT_LLIST_HEAD(&sorted);
/* detach up to 6 of the strongest neighbour cells from list and put
* them in the "sorted" list */
while (!llist_empty(&cs->nb_list)) {
strongest = -128;
strongest_nb = NULL;
llist_for_each_entry(nb, &cs->nb_list, entry) {
if (nb->state == GSM322_NB_NOT_SUP)
continue;
if (nb->rla_c_dbm > strongest) {
strongest = nb->rla_c_dbm;
strongest_nb = nb;
}
}
if (strongest_nb == NULL) /* this should not happen */
break;
LOGP(DNB, LOGL_INFO, "#%d ARFCN=%d RLA_C=%d\n",
i+1, strongest_nb->arfcn, strongest_nb->rla_c_dbm);
llist_del(&strongest_nb->entry);
llist_add(&strongest_nb->entry, &sorted);
if (++i == GSM58_NB_NUMBER)
break;
}
/* take the sorted list and attat it to the head of the neighbour cell
* list */
llist_for_each_safe(lh, lh2, &sorted) {
llist_del(lh);
llist_add(lh, &cs->nb_list);
}
return gsm322_nb_trigger_event(cs);
}
/* accumulate the measurement results and check if there is a complete set for
* all neighbour cells received. */
static int gsm322_nb_meas_ind(struct osmocom_ms *ms, uint16_t arfcn,
uint8_t rx_lev)
{
struct gsm322_cellsel *cs = &ms->cellsel;
struct gsm322_neighbour *nb;
int enough_results = 1, result = 0;
llist_for_each_entry(nb, &cs->nb_list, entry) {
if (nb->state == GSM322_NB_NOT_SUP)
continue;
if (arfcn != nb->arfcn) {
if (nb->rxlev_count < RLA_C_NUM)
enough_results = 0;
continue;
}
nb->rxlev_sum_dbm += rx_lev - 110;
nb->rxlev_count++;
LOGP(DNB, LOGL_INFO, "Measurement result for ARFCN %s: %d\n",
gsm_print_arfcn(arfcn), rx_lev - 110);
if (nb->rxlev_count < RLA_C_NUM)
enough_results = 0;
result = 1;
}
if (!result)
LOGP(DNB, LOGL_INFO, "Measurement result for ARFCN %s not "
"requested. (not a bug)\n", gsm_print_arfcn(arfcn));
if (enough_results)
return gsm322_nb_new_rxlev(cs);
return 0;
}
int gsm322_meas(struct osmocom_ms *ms, uint8_t rx_lev)
{
struct gsm322_cellsel *cs = &ms->cellsel;
if (cs->neighbour)
return -EINVAL;
cs->rxlev_sum_dbm += rx_lev - 110;
cs->rxlev_count++;
return 0;
}
/*
* dump lists
*/
int gsm322_dump_sorted_plmn(struct osmocom_ms *ms)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm322_plmn_list *temp;
LOGP(DPLMN, LOGL_INFO, "MCC |MNC |allowed|rx-lev\n");
LOGP(DPLMN, LOGL_INFO, "-------+-------+-------+-------\n");
llist_for_each_entry(temp, &plmn->sorted_plmn, entry) {
LOGP(DPLMN, LOGL_INFO, "%s |%s%s |%s |%s\n",
gsm_print_mcc(temp->mcc), gsm_print_mnc(temp->mnc),
((temp->mnc & 0x00f) == 0x00f) ? " ":"",
(temp->cause) ? "no ":"yes",
gsm_print_rxlev(temp->rxlev));
}
return 0;
}
int gsm322_dump_cs_list(struct gsm322_cellsel *cs, uint8_t flags,
void (*print)(void *, const char *, ...), void *priv)
{
int i;
struct gsm48_sysinfo *s;
print(priv, "ARFCN |MCC |MNC |LAC |cell ID|forb.LA|prio |"
"min-db |max-pwr|rx-lev\n");
print(priv, "-------+-------+-------+-------+-------+-------+-------+"
"-------+-------+-------\n");
for (i = 0; i <= 1023+299; i++) {
s = cs->list[i].sysinfo;
if (!s || !(cs->list[i].flags & flags))
continue;
if (i >= 1024)
print(priv, "%4dPCS|", i-1024+512);
else if (i >= 512 && i <= 885)
print(priv, "%4dDCS|", i);
else
print(priv, "%4d |", i);
if (s->mcc) {
print(priv, "%s |%s%s |", gsm_print_mcc(s->mcc),
gsm_print_mnc(s->mnc),
((s->mnc & 0x00f) == 0x00f) ? " ":"");
print(priv, "0x%04x |0x%04x |", s->lac, s->cell_id);
} else
print(priv, "n/a |n/a |n/a |n/a |");
if ((cs->list[i].flags & GSM322_CS_FLAG_SYSINFO)) {
if ((cs->list[i].flags & GSM322_CS_FLAG_FORBIDD))
print(priv, "yes |");
else
print(priv, "no |");
if ((cs->list[i].flags & GSM322_CS_FLAG_BARRED))
print(priv, "barred |");
else {
if (cs->list[i].sysinfo->cell_barr)
print(priv, "low |");
else
print(priv, "normal |");
}
} else
print(priv, "n/a |n/a |");
if (s->si3 || s->si4)
print(priv, "%4d |%4d |%s\n", s->rxlev_acc_min_db,
s->ms_txpwr_max_cch,
gsm_print_rxlev(cs->list[i].rxlev));
else
print(priv, "n/a |n/a |n/a\n");
}
print(priv, "\n");
return 0;
}
int gsm322_dump_forbidden_la(struct osmocom_ms *ms,
void (*print)(void *, const char *, ...), void *priv)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm322_la_list *temp;
print(priv, "MCC |MNC |LAC |cause\n");
print(priv, "-------+-------+-------+-------\n");
llist_for_each_entry(temp, &plmn->forbidden_la, entry)
print(priv, "%s |%s%s |0x%04x |#%d\n",
gsm_print_mcc(temp->mcc), gsm_print_mnc(temp->mnc),
((temp->mnc & 0x00f) == 0x00f) ? " ":"",
temp->lac, temp->cause);
return 0;
}
int gsm322_dump_ba_list(struct gsm322_cellsel *cs, uint16_t mcc, uint16_t mnc,
void (*print)(void *, const char *, ...), void *priv)
{
struct gsm322_ba_list *ba;
int i;
llist_for_each_entry(ba, &cs->ba_list, entry) {
if (mcc && mnc && (mcc != ba->mcc || mnc != ba->mnc))
continue;
print(priv, "Band Allocation of network: MCC %s MNC %s "
"(%s, %s)\n", gsm_print_mcc(ba->mcc),
gsm_print_mnc(ba->mnc), gsm_get_mcc(ba->mcc),
gsm_get_mnc(ba->mcc, ba->mnc));
for (i = 0; i <= 1023+299; i++) {
if ((ba->freq[i >> 3] & (1 << (i & 7))))
print(priv, " %s",
gsm_print_arfcn(index2arfcn(i)));
}
print(priv, "\n");
}
return 0;
}
int gsm322_dump_nb_list(struct gsm322_cellsel *cs,
void (*print)(void *, const char *, ...), void *priv)
{
struct gsm48_sysinfo *s;
struct gsm322_neighbour *nb;
int i = 0;
if (!cs->selected) {
print(priv, "No serving cell selected (yet).\n");
return 0;
}
print(priv, "Serving cell:\n\n");
print(priv, "ARFCN=%s ", gsm_print_arfcn(cs->sel_arfcn));
print(priv, "RLA_C=%s ", gsm_print_rxlev(cs->rla_c_dbm + 110));
if (cs->c12_valid)
print(priv, "C1=%d C2=%d ", cs->c1, cs->c1);
else
print(priv, "C1 - C2 - ");
print(priv, "LAC=0x%04x\n\n", (cs->selected) ? cs->sel_si.lac : 0);
print(priv, "Neighbour cells:\n\n");
llist_for_each_entry(nb, &cs->nb_list, entry) {
if (i == 0) {
print(priv, "# |ARFCN |RLA_C |C1 |C2 |"
"CRH |prio |LAC |cell ID|usable |"
"state\n");
print(priv, "----------------------------------------"
"----------------------------------------"
"-------\n");
} else
if (i == GSM58_NB_NUMBER)
print(priv, "--- unmonitored cells: ---\n");
i++;
if (cs->last_serving_valid
&& cs->last_serving_arfcn == nb->arfcn)
print(priv, "%2d last|", i);
else
print(priv, "%2d |", i);
if ((nb->arfcn & ARFCN_PCS))
print(priv, "%4dPCS|", nb->arfcn & 1023);
else if (i >= 512 && i <= 885)
print(priv, "%4dDCS|", nb->arfcn & 1023);
else
print(priv, "%4d |", nb->arfcn);
if (nb->state == GSM322_NB_NOT_SUP) {
print(priv, " ARFCN not supported\n");
continue;
}
if (nb->rla_c_dbm > -128)
print(priv, "%6s |",
gsm_print_rxlev(nb->rla_c_dbm + 110));
else
print(priv, "- |");
if (nb->state == GSM322_NB_SYSINFO && nb->c12_valid)
print(priv, "%4d |%4d |%4d |", nb->c1, nb->c1,
nb->crh);
else
print(priv, "- |- |- |");
s = cs->list[arfcn2index(nb->arfcn)].sysinfo;
if (nb->state == GSM322_NB_SYSINFO && s) {
print(priv, "%s |0x%04x |0x%04x |",
(nb->prio_low) ? "low ":"normal", s->lac,
s->cell_id);
} else
print(priv, "- |- |- |");
print(priv, "%s |",
(nb->suitable_allowable) ? "yes" : "no ");
print(priv, "%s\n", get_nb_state_name(nb->state));
}
if (i == 0)
print(priv, "No neighbour cells available (yet).\n");
return 0;
}
/*
* initialization
*/
int gsm322_init(struct osmocom_ms *ms)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm322_cellsel *cs = &ms->cellsel;
FILE *fp;
char *ba_filename;
int i;
struct gsm322_ba_list *ba;
uint8_t buf[4];
char version[32];
LOGP(DPLMN, LOGL_INFO, "init PLMN process\n");
LOGP(DCS, LOGL_INFO, "init Cell Selection process\n");
memset(plmn, 0, sizeof(*plmn));
memset(cs, 0, sizeof(*cs));
plmn->ms = ms;
cs->ms = ms;
/* set initial state */
plmn->state = 0;
cs->state = 0;
/* init lists */
INIT_LLIST_HEAD(&plmn->event_queue);
INIT_LLIST_HEAD(&cs->event_queue);
INIT_LLIST_HEAD(&plmn->sorted_plmn);
INIT_LLIST_HEAD(&plmn->forbidden_la);
INIT_LLIST_HEAD(&cs->ba_list);
INIT_LLIST_HEAD(&cs->nb_list);
/* set supported frequencies in cell selection list */
for (i = 0; i <= 1023+299; i++)
if ((ms->settings.freq_map[i >> 3] & (1 << (i & 7))))
cs->list[i].flags |= GSM322_CS_FLAG_SUPPORT;
/* read BA list */
ba_filename = talloc_asprintf(ms, "%s/%s.ba", config_dir, ms->name);
fp = fopen(ba_filename, "r");
talloc_free(ba_filename);
if (fp) {
int rc;
char *s_rc;
s_rc = fgets(version, sizeof(version), fp);
version[sizeof(version) - 1] = '\0';
if (!s_rc || !!strcmp(ba_version, version)) {
LOGP(DCS, LOGL_NOTICE, "BA version mismatch, "
"stored BA list becomes obsolete.\n");
} else
while(!feof(fp)) {
ba = talloc_zero(ms, struct gsm322_ba_list);
if (!ba) {
fclose(fp);
return -ENOMEM;
}
rc = fread(buf, 4, 1, fp);
if (!rc) {
talloc_free(ba);
break;
}
ba->mcc = (buf[0] << 8) | buf[1];
ba->mnc = (buf[2] << 8) | buf[3];
rc = fread(ba->freq, sizeof(ba->freq), 1, fp);
if (!rc) {
talloc_free(ba);
break;
}
llist_add_tail(&ba->entry, &cs->ba_list);
LOGP(DCS, LOGL_INFO, "Read stored BA list (mcc=%s "
"mnc=%s %s, %s)\n", gsm_print_mcc(ba->mcc),
gsm_print_mnc(ba->mnc), gsm_get_mcc(ba->mcc),
gsm_get_mnc(ba->mcc, ba->mnc));
}
fclose(fp);
} else
LOGP(DCS, LOGL_INFO, "No stored BA list\n");
return 0;
}
int gsm322_exit(struct osmocom_ms *ms)
{
struct gsm322_plmn *plmn = &ms->plmn;
struct gsm322_cellsel *cs = &ms->cellsel;
struct llist_head *lh, *lh2;
struct msgb *msg;
FILE *fp;
char *ba_filename;
struct gsm322_ba_list *ba;
uint8_t buf[4];
int rc = 0;
int i;
LOGP(DPLMN, LOGL_INFO, "exit PLMN process\n");
LOGP(DCS, LOGL_INFO, "exit Cell Selection process\n");
/* stop cell selection process (if any) */
new_c_state(cs, GSM322_C0_NULL);
/* stop timers */
stop_cs_timer(cs);
stop_any_timer(cs);
stop_plmn_timer(plmn);
/* flush sysinfo */
for (i = 0; i <= 1023+299; i++) {
if (cs->list[i].sysinfo) {
LOGP(DCS, LOGL_DEBUG, "free sysinfo ARFCN=%s\n",
gsm_print_arfcn(index2arfcn(i)));
talloc_free(cs->list[i].sysinfo);
cs->list[i].sysinfo = NULL;
cs->si = NULL;
}
cs->list[i].flags = 0;
}
cs->si = NULL;
/* store BA list */
ba_filename = talloc_asprintf(ms, "%s/%s.ba", config_dir, ms->name);
if (ba_filename) {
fp = fopen(ba_filename, "w");
talloc_free(ba_filename);
if (fp) {
fputs(ba_version, fp);
llist_for_each_entry(ba, &cs->ba_list, entry) {
buf[0] = ba->mcc >> 8;
buf[1] = ba->mcc & 0xff;
buf[2] = ba->mnc >> 8;
buf[3] = ba->mnc & 0xff;
LOGP(DCS, LOGL_INFO, "Write stored BA list (mcc=%s "
"mnc=%s %s, %s)\n", gsm_print_mcc(ba->mcc),
gsm_print_mnc(ba->mnc), gsm_get_mcc(ba->mcc),
gsm_get_mnc(ba->mcc, ba->mnc));
rc += fwrite(buf, 4, 1, fp);
rc += fwrite(ba->freq, sizeof(ba->freq), 1, fp);
}
fclose(fp);
}
}
if (rc != 2)
LOGP(DCS, LOGL_ERROR, "Failed to write BA list\n");
/* free lists */
while ((msg = msgb_dequeue(&plmn->event_queue)))
msgb_free(msg);
while ((msg = msgb_dequeue(&cs->event_queue)))
msgb_free(msg);
llist_for_each_safe(lh, lh2, &plmn->sorted_plmn) {
llist_del(lh);
talloc_free(lh);
}
llist_for_each_safe(lh, lh2, &plmn->forbidden_la) {
llist_del(lh);
talloc_free(lh);
}
llist_for_each_safe(lh, lh2, &cs->ba_list) {
llist_del(lh);
talloc_free(lh);
}
llist_for_each_safe(lh, lh2, &cs->nb_list)
gsm322_nb_free(container_of(lh, struct gsm322_neighbour,
entry));
return 0;
}