/*
 * (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/ms.h>
#include <osmocom/bb/common/networks.h>
#include <osmocom/bb/common/utils.h>
#include <osmocom/bb/common/settings.h>
#include <osmocom/bb/mobile/vty.h>
#include <osmocom/bb/mobile/app_mobile.h>
#include <osmocom/bb/mobile/gsm322.h>
#include <osmocom/bb/mobile/gsm48_mm.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, const struct osmo_plmn_id *plmn, 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));
	memset(&cs->sel_cgi, 0, sizeof(cs->sel_cgi));
}

/* 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, const struct osmo_location_area_id *lai)
{
	struct gsm322_plmn *plmn = &ms->plmn;
	struct gsm322_la_list *la;

	llist_for_each_entry(la, &plmn->forbidden_la, entry) {
		if (osmo_lai_cmp(&la->lai, lai) == 0) {
			LOGP(DPLMN, LOGL_INFO, "Delete from list of forbidden LAs (LAI=%s)\n",
			     osmo_lai_name(lai));
			llist_del(&la->entry);
			talloc_free(la);
			return 0;
		}
	}

	return -EINVAL;
}

/* add forbidden LA */
int gsm322_add_forbidden_la(struct osmocom_ms *ms, const struct osmo_location_area_id *lai, uint8_t cause)
{
	struct gsm322_plmn *plmn = &ms->plmn;
	struct gsm322_la_list *la;

	LOGP(DPLMN, LOGL_INFO, "Add to list of forbidden LAs (LAI=%s)\n", osmo_lai_name(lai));
	la = talloc_zero(ms, struct gsm322_la_list);
	if (!la)
		return -ENOMEM;
	la->lai = *lai;
	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, const struct osmo_location_area_id *lai)
{
	struct gsm322_plmn *plmn = &ms->plmn;
	struct gsm322_la_list *la;

	llist_for_each_entry(la, &plmn->forbidden_la, entry) {
		if (osmo_lai_cmp(&la->lai, lai) == 0)
			return 1;
	}

	return 0;
}

/* search for PLMN in all BA lists */
static struct gsm322_ba_list *gsm322_find_ba_list(struct gsm322_cellsel *cs, const struct osmo_plmn_id *plmn)
{
	struct gsm322_ba_list *ba, *ba_found = NULL;

	/* search for BA list */
	llist_for_each_entry(ba, &cs->ba_list, entry) {
		if (osmo_plmn_cmp(&ba->plmn, plmn) == 0) {
			ba_found = ba;
			break;
		}
	}

	return ba_found;
}

/* search available PLMN */
int gsm322_is_plmn_avail_and_allow(struct gsm322_cellsel *cs, const struct osmo_plmn_id *plmn)
{
	int i;

	for (i = 0; i <= 1023+299; i++) {
		if ((cs->list[i].flags & GSM322_CS_FLAG_TEMP_AA)
		 && cs->list[i].sysinfo
		 && (osmo_plmn_cmp(&cs->list[i].sysinfo->lai.plmn, plmn) == 0))
			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->lai.plmn.mcc,
				  cs->list[i].sysinfo->lai.plmn.mnc,
				  cs->list[i].sysinfo->lai.plmn.mnc_3_digits,
				  imsi))
			return 1;
		/* TODO: take into account mnc_3_digits, probably use osmo_mnc_cmp()*/
	}

	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 (osmo_plmn_cmp(&temp->plmn, &cs->list[i].sysinfo->lai.plmn) == 0) {
				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;
			memcpy(&temp->plmn, &cs->list[i].sysinfo->lai.plmn, sizeof(temp->plmn));
			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->plmn.mcc, temp->plmn.mnc, temp->plmn.mnc_3_digits, 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 (osmo_plmn_cmp(&temp->plmn, &sim_entry->plmn) == 0) {
				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 (osmo_plmn_cmp(&temp->plmn, &na_entry->plmn) == 0) {
				temp->cause = na_entry->cause;
				break;
			}
		}
		LOGP(DPLMN, LOGL_INFO, "Creating Sorted PLMN list. "
			"(%02d: mcc-mnc %s allowed %s rx-lev %s)\n",
			i, osmo_plmn_name(&temp->plmn),
			(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->plmn.mcc, plmn->plmn.mnc, plmn->plmn.mnc_3_digits, subscr->imsi)
	 && (subscr->always_search_hplmn
	  || gsm_match_mcc(plmn->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, NULL, 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");
			memcpy(&plmn->plmn, &subscr->plmn, sizeof(struct osmo_plmn_id));
		} else
		if (subscr->sim_valid) {
			LOGP(DPLMN, LOGL_INFO, "Not any PLMN allowable. "
				"Do limited search with HPLMN.\n");
			memcpy(&plmn->plmn, &subscr->plmn, sizeof(struct osmo_plmn_id));
		} else {
			LOGP(DPLMN, LOGL_INFO, "Not any PLMN allowable. "
				"Do limited search with no PLMN.\n");
			memset(&plmn->plmn, 0, sizeof(struct osmo_plmn_id));
		}

		return gsm322_a_go_wait_for_plmns(ms, msg);
	}

	/* select first PLMN in list */
	memcpy(&plmn->plmn, &cs->list[found].sysinfo->lai.plmn, sizeof(struct osmo_plmn_id));

	LOGP(DPLMN, LOGL_INFO, "PLMN available after searching PLMN list "
		"(mcc=-mnc=%s  %s, %s)\n",
		osmo_plmn_name(&plmn->plmn),
		gsm_get_mcc(plmn->plmn.mcc), gsm_get_mnc(&plmn->plmn));

	/* 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->plmn.mcc, plmn_entry->plmn.mnc,
				  plmn_entry->plmn.mnc_3_digits, subscr->imsi)
		    && (osmo_plmn_cmp(&plmn_entry->plmn, &plmn->plmn) == 0)) {
			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-mnc=%s), "
			"because it is not allowed (cause %d).\n", i,
			osmo_plmn_name(&plmn_entry->plmn),
			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-mnc=%s  %s, %s)\n",
		plmn->plmn_curr,
		osmo_plmn_name(&plmn_first->plmn),
		gsm_get_mcc(plmn_first->plmn.mcc),
		gsm_get_mnc(&plmn_first->plmn));

	/* set current network */
	memcpy(&plmn->plmn, &plmn_first->plmn, sizeof(struct osmo_plmn_id));

	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-mnc=%s), "
			"because it is not allowed (cause %d).\n", i,
			osmo_plmn_name(&plmn_entry->plmn),
			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 */
	memcpy(&plmn->plmn, &plmn_next->plmn, sizeof(struct osmo_plmn_id));

	LOGP(DPLMN, LOGL_INFO, "Selecting PLMN from list. (%02d: mcc-mnc=%s  %s, %s)\n",
		plmn->plmn_curr,
		osmo_plmn_name(&plmn->plmn),
		gsm_get_mcc(plmn->plmn.mcc),
		gsm_get_mnc(&plmn->plmn));

	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 (osmo_plmn_cmp(&plmn_entry->plmn, &plmn->plmn) == 0) {
			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 &&
	    (osmo_plmn_cmp(&plmn->plmn, &gm->plmn) == 0)) {
		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, NULL, 0);

	/* if PLMN in list */
	if (found >= 0) {
		LOGP(DPLMN, LOGL_INFO, "PLMN available (mcc-mnc=%s  %s, %s)\n",
			osmo_plmn_name(&cs->list[found].sysinfo->lai.plmn),
			gsm_get_mcc(cs->list[found].sysinfo->lai.plmn.mcc),
			gsm_get_mnc(&cs->list[found].sysinfo->lai.plmn));
		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 */
		memcpy(&plmn->plmn, &subscr->plmn, sizeof(struct osmo_plmn_id));

		LOGP(DSUM, LOGL_INFO, "Start search of last registered PLMN "
			"(mcc-%s  %s, %s)\n",
			osmo_plmn_name(&plmn->plmn),
			gsm_get_mcc(plmn->plmn.mcc),
			gsm_get_mnc(&plmn->plmn));
		LOGP(DPLMN, LOGL_INFO, "Use RPLMN (mcc-mnc=%s  %s, %s)\n",
			osmo_plmn_name(&plmn->plmn),
			gsm_get_mcc(plmn->plmn.mcc),
			gsm_get_mnc(&plmn->plmn));

		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;
	}

	memset(&plmn->plmn, 0, sizeof(plmn->plmn));

	/* 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, NULL);

	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);

	l23_vty_ms_notify(ms, NULL);
	switch (msg_type) {
	case GSM322_EVENT_REG_FAILED:
		l23_vty_ms_notify(ms, "Failed to register to network %s (%s, %s)\n",
			osmo_plmn_name(&plmn->plmn),
			gsm_get_mcc(plmn->plmn.mcc),
			gsm_get_mnc(&plmn->plmn));
		break;
	case GSM322_EVENT_NO_CELL_FOUND:
		l23_vty_ms_notify(ms, "No cell found for network %s (%s, %s)\n",
			osmo_plmn_name(&plmn->plmn),
			gsm_get_mcc(plmn->plmn.mcc),
			gsm_get_mnc(&plmn->plmn));
		break;
	case GSM322_EVENT_ROAMING_NA:
		l23_vty_ms_notify(ms, "Roaming not allowed to network %s (%s, %s)\n",
			osmo_plmn_name(&plmn->plmn),
			gsm_get_mcc(plmn->plmn.mcc),
			gsm_get_mnc(&plmn->plmn));
		break;
	}

	if (llist_empty(&plmn->sorted_plmn))
		l23_vty_ms_notify(ms, "Search network!\n");
	else {
		l23_vty_ms_notify(ms, "Search or select from network:\n");
		llist_for_each_entry(temp, &plmn->sorted_plmn, entry)
			l23_vty_ms_notify(ms, " Network mcc-mnc=%s (%s, %s)\n",
				osmo_plmn_name(&temp->plmn),
				gsm_get_mcc(temp->plmn.mcc),
				gsm_get_mnc(&temp->plmn));
	}

	/* 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.
	 */
	memset(&plmn->plmn, 0, sizeof(plmn->plmn));

	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 */
		memcpy(&plmn->plmn, &subscr->plmn, sizeof(struct osmo_plmn_id));

		LOGP(DSUM, LOGL_INFO, "Start search of last registered PLMN (mcc-mnc=%s  %s, %s)\n",
			osmo_plmn_name(&plmn->plmn),
			gsm_get_mcc(plmn->plmn.mcc),
			gsm_get_mnc(&plmn->plmn));
		LOGP(DPLMN, LOGL_INFO, "Use RPLMN (mcc-mnc=%s  %s, %s)\n",
			osmo_plmn_name(&plmn->plmn),
			gsm_get_mcc(plmn->plmn.mcc),
			gsm_get_mnc(&plmn->plmn));

		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;
	}

	memset(&plmn->plmn, 0, sizeof(plmn->plmn));

	/* 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, NULL);

	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 = true;
	memcpy(&subscr->plmn, &plmn->plmn, sizeof(struct osmo_plmn_id));

	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 */
	memcpy(&plmn->plmn, &gm->plmn, sizeof(struct osmo_plmn_id));

	LOGP(DPLMN, LOGL_INFO, "User selects PLMN. (mcc-mnc=%s  %s, %s)\n",
		osmo_plmn_name(&plmn->plmn),
		gsm_get_mcc(plmn->plmn.mcc),
		gsm_get_mnc(&plmn->plmn));

	/* if selected PLMN is in list of forbidden PLMNs */
	gsm_subscr_del_forbidden_plmn(subscr, &plmn->plmn);

	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, const struct osmo_plmn_id *plmn, 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. (lai=%s)\n",
					gsm_print_arfcn(index2arfcn(i)),
					osmo_lai_name(&s->lai));
				continue;
			}
			LOGP(DCS, LOGL_INFO, "Accept ARFCN %s: Cell is in "
				"list of forbidden LAs, but we search for any "
				"cell. (lai=%s)\n",
				gsm_print_arfcn(index2arfcn(i)),
				osmo_lai_name(&s->lai));
			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->lai.plmn)) {
			if (!any) {
				LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is "
					"in list of forbidden PLMNs. (mcc-mnc=%s)\n",
					gsm_print_arfcn(index2arfcn(i)),
					osmo_plmn_name(&s->lai.plmn));
				continue;
			}
			LOGP(DCS, LOGL_INFO, "Accept ARFCN %s: Cell is in list "
				"of forbidden PLMNs, but we search for any "
				"cell. (mcc-mnc=%s)\n",
				gsm_print_arfcn(index2arfcn(i)),
				osmo_plmn_name(&s->lai.plmn));
			cs->list[i].flags &= ~GSM322_CS_FLAG_TEMP_AA;
		}

		/* if we search a specific PLMN, but it does not match */
		if (!any && plmn && (osmo_plmn_cmp(&s->lai.plmn, plmn) != 0)) {
			LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: PLMN of cell "
				"does not match target PLMN. (mcc-mnc=%s)\n",
				gsm_print_arfcn(index2arfcn(i)),
				osmo_plmn_name(&s->lai.plmn));
			continue;
		}

		LOGP(DCS, LOGL_INFO, "Cell ARFCN %s: Cell found, (rxlev=%s "
			"lai=%s  %s, %s)\n",
			gsm_print_arfcn(index2arfcn(i)),
			gsm_print_rxlev(cs->list[i].rxlev),
			osmo_lai_name(&s->lai),
			gsm_get_mcc(s->lai.plmn.mcc), gsm_get_mnc(&s->lai.plmn));

		/* 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, const struct osmo_plmn_id *plmn, 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. (lai=%s)\n",
			gsm_print_arfcn(index2arfcn(i)),
			osmo_lai_name(&s->lai));
		return -1;
	}

	/* if cell is in list of forbidden PLMNs */
	if (!any && gsm_subscr_is_forbidden_plmn(subscr, &s->lai.plmn)) {
		LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is in "
			"list of forbidden PLMNs. (mcc-mnc=%s)\n",
			gsm_print_arfcn(index2arfcn(i)),
			osmo_plmn_name(&s->lai.plmn));
		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 && plmn && (osmo_plmn_cmp(plmn, &s->lai.plmn) != 0)) {
		LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: PLMN of cell "
			"does not match target PLMN. (mcc-mnc=%s)\n",
			gsm_print_arfcn(index2arfcn(i)),
			osmo_plmn_name(&s->lai.plmn));
		return -1;
	}

	LOGP(DCS, LOGL_INFO, "Cell ARFCN %s: Neighbour cell accepted, "
		"(rxlev=%s lai=%s  %s, %s)\n",
		gsm_print_arfcn(index2arfcn(i)),
		gsm_print_rxlev(cs->list[i].rxlev),
		osmo_lai_name(&s->lai),
		gsm_get_mcc(s->lai.plmn.mcc),
		gsm_get_mnc(&s->lai.plmn));

	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 *plmn322 = &ms->plmn;
	struct msgb *nmsg;
	struct gsm322_msg *ngm;
	int msg_type = -1; /* no message to be sent */
	int tune_back = 0;
	int found;
	struct osmo_plmn_id plmn = {};

	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, NULL, 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
		 || plmn322->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, &plmn322->plmn)) {
			/* set what PLMN becomes available */
			memcpy(&plmn, &plmn322->plmn, sizeof(struct osmo_plmn_id));
		}

		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, NULL, 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;
		memcpy(&ngm->plmn, &plmn, sizeof(struct osmo_plmn_id));
		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_cgi.lai = cs->si->lai;
		cs->sel_cgi.cell_identity = 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->lai.plmn.mcc) {
		if (gsm322_is_forbidden_la(ms, &s->lai))
			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 lai %s)\n",
		gsm_print_arfcn(cs->arfcn),
		gsm_print_rxlev(cs->list[cs->arfci].rxlev),
		osmo_lai_name(&s->lai));

	/* 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
	 && (osmo_plmn_cmp(&s->lai.plmn, &plmn->plmn) == 0)) {
		LOGP(DCS, LOGL_INFO, "Candidate network to become available "
			"again\n");
		found = gsm322_cs_select(ms, cs->arfci, &s->lai.plmn, 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;
			memcpy(&ngm->plmn, &plmn->plmn, sizeof(struct osmo_plmn_id));
			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
	 && (osmo_plmn_cmp(&s->lai.plmn, &plmn->plmn) == 0)) {
		LOGP(DCS, LOGL_INFO, "Candidate network to become available "
			"again\n");
		found = gsm322_cs_select(ms, cs->arfci, &s->lai.plmn, 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->plmn, any);
	else
		found = gsm322_cs_select(ms, -1, &cs->plmn, 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_cgi.lai = cs->si->lai;
	cs->sel_cgi.cell_identity = cs->si->cell_id;
	if (ms->rrlayer.monitor) {
		l23_vty_ms_notify(ms, "MON: %scell selected ARFCN=%s CGI=%s (%s %s)\n",
			(any) ? "any " : "", gsm_print_arfcn(cs->sel_arfcn),
			osmo_cgi_name(&cs->sel_cgi),
			gsm_get_mcc(cs->sel_cgi.lai.plmn.mcc),
			gsm_get_mnc(&cs->sel_cgi.lai.plmn));
	}

	/* 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;
	bool 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->lai.plmn);
		if (!ba) {
			ba = talloc_zero(ms, struct gsm322_ba_list);
			if (!ba)
				return NULL;
			memcpy(&ba->plmn, &s->lai.plmn, sizeof(struct osmo_plmn_id));
			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-mnc=%s  %s, %s).\n",
				osmo_plmn_name(&ba->plmn),
				gsm_get_mcc(ba->plmn.mcc),
				gsm_get_mnc(&ba->plmn));
			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;
	bool refer_pcs;
	uint8_t freq[128+38];

	/* find or create ba list */
	ba = gsm322_find_ba_list(cs, &s->lai.plmn);
	if (!ba) {
		ba = talloc_zero(cs->ms, struct gsm322_ba_list);
		if (!ba)
			return -ENOMEM;
		memcpy(&ba->plmn, &s->lai.plmn, sizeof(struct osmo_plmn_id));
		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-mnc=%s  %s, %s).\n",
			osmo_plmn_name(&ba->plmn),
			gsm_get_mcc(ba->plmn.mcc),
			gsm_get_mnc(&ba->plmn));
		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->lai.plmn.mcc
	 && s->lai.plmn.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)
				l23_vty_ms_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)
				l23_vty_ms_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 (osmo_lai_cmp(&cs->sel_cgi.lai, &s->lai) != 0) {
		LOGP(DCS, LOGL_NOTICE, "Cell changes location area. "
			"This is not good!\n");
		if (ms->rrlayer.monitor)
			l23_vty_ms_notify(ms, "MON: trigger cell re-selection: "
				"cell changes LAI\n");
		goto trigger_resel;
	}
	if (cs->sel_cgi.cell_identity != s->cell_id) {
		LOGP(DCS, LOGL_NOTICE, "Cell changes cell ID. "
			"This is not good!\n");
		if (ms->rrlayer.monitor)
			l23_vty_ms_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->lai.plmn.mcc
	 && s->lai.plmn.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)
				l23_vty_ms_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);

	memset(&cs->plmn, 0, sizeof(cs->plmn));

	/* 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-mnc=%s  %s, %s)\n", gsm_print_arfcn(cs->sel_arfcn),
		osmo_plmn_name(&cs->sel_cgi.lai.plmn),
		gsm_get_mcc(cs->sel_cgi.lai.plmn.mcc),
		gsm_get_mnc(&cs->sel_cgi.lai.plmn));

	/* 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-mnc=%s  %s, %s)\n",
		gsm_print_arfcn(cs->sel_arfcn),
		osmo_plmn_name(&cs->sel_cgi.lai.plmn),
		gsm_get_mcc(cs->sel_cgi.lai.plmn.mcc),
		gsm_get_mnc(&cs->sel_cgi.lai.plmn));

	/* (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 *plmn322 = &ms->plmn;

		stop_any_timer(cs);
		if (ms->settings.plmn_mode == PLMN_MODE_MANUAL
		 && (!plmn322->plmn.mcc
		  || gsm_subscr_is_forbidden_plmn(&ms->subscr, &plmn322->plmn))) {
			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 */
static struct gsm322_ba_list *gsm322_cs_ba_range(struct osmocom_ms *ms, uint32_t *range, uint8_t ranges, bool 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.lai.plmn);
		}
	}

	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 *plmn322 = &ms->plmn;
	struct gsm322_ba_list *ba;

	memcpy(&cs->plmn, &plmn322->plmn, sizeof(struct osmo_plmn_id));

	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-mnc=%s  %s, %s)\n",
		osmo_plmn_name(&cs->plmn),
		gsm_get_mcc(cs->plmn.mcc), gsm_get_mnc(&cs->plmn));

	/* search for BA list */
	ba = gsm322_find_ba_list(cs, &plmn322->plmn);

	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 %s unhandled in state %s.\n",
		     get_event_name(msg_type), get_a_state_name(plmn->state));
		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) {
		l23_vty_ms_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';
		l23_vty_ms_notify(ms, "MON: serving %s 0x%04x %3d %3d     %4d  "
			"%s\n", arfcn_text, cs->sel_cgi.lai.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';
				l23_vty_ms_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';
				l23_vty_ms_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 */
		if (gsm_arfcn2band_rc(nb->arfcn, &band) != 0) {
			LOGP(DNB, LOGL_ERROR, "gsm_arfcn2band_rc() failed\n");
			goto cont;
		}
		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 (osmo_lai_cmp(&cs->sel_cgi.lai, &s->lai) == 0) {
			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';
			l23_vty_ms_notify(ms, "MON: nb %2d   %s 0x%04x %3d %3d %2d"
				"  %4d  %s\n", i + 1, arfcn_text, s->lai.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->lai)) {
			LOGP(DNB, LOGL_INFO, "Skip cell: Cell has "
				"forbidden LA.\n");
			goto cont;
		}

		/* check if we have same PLMN */
		if (!any && (osmo_plmn_cmp(&cs->sel_cgi.lai.plmn, &s->lai.plmn) != 0)) {
			LOGP(DNB, LOGL_INFO, "Skip cell: PLMN of cell "
				"does not match target PLMN. (cell: mcc-mnc=%s)\n",
				osmo_plmn_name(&s->lai.plmn));
			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)
			l23_vty_ms_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;
	bool refer_pcs;
	int 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)
			l23_vty_ms_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;

	if (gsm_arfcn2band_rc(cs->arfcn, &band) != 0) {
		LOGP(DNB, LOGL_ERROR, "gsm_arfcn2band_rc() failed\n");
		return -EINVAL;
	}

	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    |%-3s    |%s    |%s\n",
			osmo_mcc_name(temp->plmn.mcc),
			osmo_mnc_name(temp->plmn.mnc, temp->plmn.mnc_3_digits),
			(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->lai.plmn.mcc) {
			print(priv, "%s    |%-3s    |",
			      osmo_mcc_name(s->lai.plmn.mcc),
			      osmo_mnc_name(s->lai.plmn.mnc, s->lai.plmn.mnc_3_digits));
			print(priv, "0x%04x |0x%04x |", s->lai.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    |%-3s    |0x%04x |#%d\n",
			osmo_mcc_name(temp->lai.plmn.mcc),
			osmo_mnc_name(temp->lai.plmn.mnc, temp->lai.plmn.mnc_3_digits),
			temp->lai.lac, temp->cause);

	return 0;
}

int gsm322_dump_ba_list(struct gsm322_cellsel *cs, const struct osmo_plmn_id *plmn,
			void (*print)(void *, const char *, ...), void *priv)
{
	struct gsm322_ba_list *ba;
	int i;

	llist_for_each_entry(ba, &cs->ba_list, entry) {
		if (plmn && (osmo_plmn_cmp(&ba->plmn, plmn) != 0))
			continue;
		print(priv, "Band Allocation of network: MCC-MNC %s (%s, %s)\n",
			osmo_plmn_name(&ba->plmn),
			gsm_get_mcc(ba->plmn.mcc),
			gsm_get_mnc(&ba->plmn));
		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.lai.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->lai.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)) {
			uint16_t mcc_hex, mnc_hex;
			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;
			}
			mcc_hex = (buf[0] << 8) | buf[1];
			mnc_hex = (buf[2] << 8) | buf[3];
			ba->plmn.mcc = (((mcc_hex & 0x0f00) >> 8) * 100) +
					(((mcc_hex & 0x00f0) >> 4) * 10) +
					(mcc_hex & 0x000f);
			ba->plmn.mnc_3_digits = ((mnc_hex & 0x00f) != 0x00f);
			if (ba->plmn.mnc_3_digits)
				ba->plmn.mnc = (((mnc_hex & 0x0f00) >> 8) * 100) +
					       (((mnc_hex & 0x00f0) >> 4) * 10) +
					       (mnc_hex & 0x000f);
			else
				ba->plmn.mnc = (((mnc_hex & 0x0f00) >> 8) * 10) +
					       (((mnc_hex & 0x00f0) >> 4));

			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-mnc=%s  %s, %s)\n",
				osmo_plmn_name(&ba->plmn),
				gsm_get_mcc(ba->plmn.mcc),
				gsm_get_mnc(&ba->plmn));
		}
		fclose(fp);
	} else
		LOGP(DCS, LOGL_INFO, "No stored BA list\n");

	return 0;
}

static void gsm322_write_ba(struct osmocom_ms *ms)
{
	const struct gsm322_ba_list *ba;
	char *ba_filename;
	FILE *fp;

	ba_filename = talloc_asprintf(ms, "%s/%s.ba", config_dir, ms->name);
	OSMO_ASSERT(ba_filename != NULL);

	LOGP(DCS, LOGL_INFO, "Writing stored BA list to '%s'\n", ba_filename);

	fp = fopen(ba_filename, "w");
	talloc_free(ba_filename);
	if (fp == NULL) {
		LOGP(DCS, LOGL_ERROR,
		     "Failed to open '%s' for writing: %s\n",
		     ba_filename, strerror(errno));
		return;
	}

	fputs(ba_version, fp);

	llist_for_each_entry(ba, &ms->cellsel.ba_list, entry) {
		size_t rc = 0;
		uint16_t mcc_hex = gsm_mcc_to_hex(ba->plmn.mcc);
		uint16_t mnc_hex = gsm_mnc_to_hex(ba->plmn.mnc, ba->plmn.mnc_3_digits);
		uint8_t buf[] = {
			mcc_hex >> 8, mcc_hex & 0xff,
			mnc_hex >> 8, mnc_hex & 0xff,
		};

		LOGP(DCS, LOGL_INFO,
		     "Writing stored BA list entry (mcc-mnc=%s %s, %s)\n",
		     osmo_plmn_name(&ba->plmn),
		     gsm_get_mcc(ba->plmn.mcc),
		     gsm_get_mnc(&ba->plmn));

		rc += fwrite(buf, sizeof(buf), 1, fp);
		rc += fwrite(ba->freq, sizeof(ba->freq), 1, fp);

		/* fwrite() returns count of written items, should be 2 */
		if (rc != 2) {
			LOGP(DCS, LOGL_ERROR,
			     "Writing stored BA list: fwrite() failed (rc=%zu)\n", rc);
			break;
		}
	}

	fclose(fp);
}

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;
	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 */
	gsm322_write_ba(ms);

	/* 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;
}
