diff options
Diffstat (limited to 'src/host/layer23/src')
38 files changed, 35015 insertions, 0 deletions
diff --git a/src/host/layer23/src/Makefile.am b/src/host/layer23/src/Makefile.am new file mode 100644 index 00000000..58a5f7fb --- /dev/null +++ b/src/host/layer23/src/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = common misc mobile diff --git a/src/host/layer23/src/common/Makefile.am b/src/host/layer23/src/common/Makefile.am new file mode 100644 index 00000000..8d96ed2c --- /dev/null +++ b/src/host/layer23/src/common/Makefile.am @@ -0,0 +1,6 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBGPS_CFLAGS) + +noinst_LIBRARIES = liblayer23.a +liblayer23_a_SOURCES = l1ctl.c l1l2_interface.c sap_interface.c \ + logging.c networks.c sim.c sysinfo.c gps.c l1ctl_lapdm_glue.c diff --git a/src/host/layer23/src/common/gps.c b/src/host/layer23/src/common/gps.c new file mode 100644 index 00000000..e9aaa97c --- /dev/null +++ b/src/host/layer23/src/common/gps.c @@ -0,0 +1,402 @@ +/* + * (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdio.h> +#include <sys/file.h> +#include <termios.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <time.h> +#include <stdbool.h> + +#ifdef _HAVE_GPSD +#include <gps.h> +#endif + +#include <osmocom/core/utils.h> + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/gps.h> + +struct osmo_gps g = { + 0, + GPS_TYPE_UNDEF, +#ifdef _HAVE_GPSD + "localhost", + "2947", +#endif + "/dev/ttyACM0", + 0, + 0, + 0, + 0,0 +}; + +static struct osmo_fd gps_bfd; + +#ifdef _HAVE_GPSD + +static struct gps_data_t* gdata = NULL; + +#if GPSD_API_MAJOR_VERSION >= 5 +static struct gps_data_t _gdata; +#define gps_poll gps_read +#endif + +int osmo_gpsd_cb(struct osmo_fd *bfd, unsigned int what) +{ + struct tm *tm; + unsigned diff = 0; + + g.valid = 0; + + /* gps is offline */ + if (gdata->online) + goto gps_not_ready; + +#if GPSD_API_MAJOR_VERSION >= 5 + /* gps has no data */ + if (gps_waiting(gdata, 500)) + goto gps_not_ready; +#else + /* gps has no data */ + if (gps_waiting(gdata)) + goto gps_not_ready; +#endif + + /* polling returned an error */ + if (gps_poll(gdata)) + goto gps_not_ready; + + /* data are valid */ + if (gdata->set & LATLON_SET) { + g.valid = 1; + g.gmt = gdata->fix.time; + tm = localtime(&g.gmt); + diff = time(NULL) - g.gmt; + g.latitude = gdata->fix.latitude; + g.longitude = gdata->fix.longitude; + + LOGP(DGPS, LOGL_INFO, " time=%02d:%02d:%02d %04d-%02d-%02d, " + "diff-to-host=%d, latitude=%do%.4f, longitude=%do%.4f\n", + tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_year + 1900, + tm->tm_mday, tm->tm_mon + 1, diff, + (int)g.latitude, + (g.latitude - ((int)g.latitude)) * 60.0, + (int)g.longitude, + (g.longitude - ((int)g.longitude)) * 60.0); + } + + return 0; + +gps_not_ready: + LOGP(DGPS, LOGL_DEBUG, "gps is offline"); + return -1; +} + +int osmo_gpsd_open(void) +{ + LOGP(DGPS, LOGL_INFO, "Connecting to gpsd at '%s:%s'\n", g.gpsd_host, g.gpsd_port); + + gps_bfd.data = NULL; + gps_bfd.when = BSC_FD_READ; + gps_bfd.cb = osmo_gpsd_cb; + +#if GPSD_API_MAJOR_VERSION >= 5 + if (gps_open(g.gpsd_host, g.gpsd_port, &_gdata) == -1) + gdata = NULL; + else + gdata = &_gdata; +#else + gdata = gps_open(g.gpsd_host, g.gpsd_port); +#endif + if (gdata == NULL) { + LOGP(DGPS, LOGL_ERROR, "Can't connect to gpsd\n"); + return -1; + } + gps_bfd.fd = gdata->gps_fd; + if (gps_bfd.fd < 0) + return gps_bfd.fd; + + if (gps_stream(gdata, WATCH_ENABLE, NULL) == -1) { + LOGP(DGPS, LOGL_ERROR, "Error in gps_stream()\n"); + return -1; + } + + osmo_fd_register(&gps_bfd); + + return 0; +} + +void osmo_gpsd_close(void) +{ + if (gps_bfd.fd <= 0) + return; + + LOGP(DGPS, LOGL_INFO, "Disconnecting from gpsd\n"); + + osmo_fd_unregister(&gps_bfd); + +#if GPSD_API_MAJOR_VERSION >= 5 + gps_stream(gdata, WATCH_DISABLE, NULL); +#endif + gps_close(gdata); + gps_bfd.fd = -1; /* -1 or 0 indicates: 'close' */ +} + +#endif + +static struct termios gps_termios, gps_old_termios; + +static int osmo_serialgps_line(char *line) +{ + time_t gps_now, host_now; + struct tm *tm; + int32_t diff; + double latitude, longitude; + + if (!!strncmp(line, "$GPGLL", 6)) + return 0; + line += 7; + if (strlen(line) < 37) + return 0; + line[37] = '\0'; + /* ddmm.mmmm,N,dddmm.mmmm,E,hhmmss.mmm,A */ + + /* valid position */ + if (line[36] != 'A') { + LOGP(DGPS, LOGL_INFO, "%s (invalid)\n", line); + g.valid = 0; + return 0; + } + g.valid = 1; + + /* time stamp */ + gps_now = line[30] - '0'; + gps_now += (line[29] - '0') * 10; + gps_now += (line[28] - '0') * 60; + gps_now += (line[27] - '0') * 600; + gps_now += (line[26] - '0') * 3600; + gps_now += (line[25] - '0') * 36000; + time(&host_now); + /* calculate the number of seconds the host differs from GPS */ + diff = host_now % 86400 - gps_now; + if (diff < 0) + diff += 86400; + if (diff >= 43200) + diff -= 86400; + /* apply the "date" part to the GPS time */ + gps_now = host_now - diff; + g.gmt = gps_now; + tm = localtime(&gps_now); + + /* position */ + latitude = (double)(line[0] - '0') * 10.0; + latitude += (double)(line[1] - '0'); + latitude += (double)(line[2] - '0') / 6.0; + latitude += (double)(line[3] - '0') / 60.0; + latitude += (double)(line[5] - '0') / 600.0; + latitude += (double)(line[6] - '0') / 6000.0; + latitude += (double)(line[7] - '0') / 60000.0; + latitude += (double)(line[8] - '0') / 600000.0; + if (line[10] == 'S') + latitude = 0.0 - latitude; + g.latitude = latitude; + longitude = (double)(line[12] - '0') * 100.0; + longitude += (double)(line[13] - '0') * 10.0; + longitude += (double)(line[14] - '0'); + longitude += (double)(line[15] - '0') / 6.0; + longitude += (double)(line[16] - '0') / 60.0; + longitude += (double)(line[18] - '0') / 600.0; + longitude += (double)(line[19] - '0') / 6000.0; + longitude += (double)(line[20] - '0') / 60000.0; + longitude += (double)(line[21] - '0') / 600000.0; + if (line[23] == 'W') + longitude = 360.0 - longitude; + g.longitude = longitude; + + LOGP(DGPS, LOGL_DEBUG, "%s\n", line); + LOGP(DGPS, LOGL_INFO, " time=%02d:%02d:%02d %04d-%02d-%02d, " + "diff-to-host=%d, latitude=%do%.4f, longitude=%do%.4f\n", + tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_year + 1900, + tm->tm_mday, tm->tm_mon + 1, diff, + (int)g.latitude, + (g.latitude - ((int)g.latitude)) * 60.0, + (int)g.longitude, + (g.longitude - ((int)g.longitude)) * 60.0); + return 0; +} + +static int nmea_checksum(char *line) +{ + uint8_t checksum = 0; + + while (*line) { + if (*line == '$') { + line++; + continue; + } + if (*line == '*') + break; + checksum ^= *line++; + } + return (strtoul(line+1, NULL, 16) == checksum); +} + +int osmo_serialgps_cb(struct osmo_fd *bfd, unsigned int what) +{ + char buff[128]; + static char line[128]; + static int lpos = 0; + int i = 0, len; + + len = read(bfd->fd, buff, sizeof(buff)); + if (len <= 0) { + fprintf(stderr, "error reading GPS device (errno=%d)\n", errno); + return len; + } + while(i < len) { + if (buff[i] == 13) { + i++; + continue; + } + if (buff[i] == 10) { + line[lpos] = '\0'; + lpos = 0; + i++; + if (!nmea_checksum(line)) + fprintf(stderr, "NMEA checksum error\n"); + else + osmo_serialgps_line(line); + continue; + } + line[lpos++] = buff[i++]; + if (lpos == sizeof(line)) + lpos--; + } + + return 0; +} + +int osmo_serialgps_open(void) +{ + int baud = 0; + + if (gps_bfd.fd > 0) + return 0; + + LOGP(DGPS, LOGL_INFO, "Open GPS device '%s'\n", g.device); + + gps_bfd.data = NULL; + gps_bfd.when = BSC_FD_READ; + gps_bfd.cb = osmo_serialgps_cb; + gps_bfd.fd = open(g.device, O_RDONLY); + if (gps_bfd.fd < 0) + return gps_bfd.fd; + + switch (g.baud) { + case 4800: + baud = B4800; break; + case 9600: + baud = B9600; break; + case 19200: + baud = B19200; break; + case 38400: + baud = B38400; break; + case 57600: + baud = B57600; break; + case 115200: + baud = B115200; break; + } + + if (isatty(gps_bfd.fd)) + { + /* get termios */ + tcgetattr(gps_bfd.fd, &gps_old_termios); + tcgetattr(gps_bfd.fd, &gps_termios); + /* set baud */ + if (baud) { + gps_termios.c_cflag |= baud; + cfsetispeed(&gps_termios, baud); + cfsetospeed(&gps_termios, baud); + } + if (tcsetattr(gps_bfd.fd, TCSANOW, &gps_termios)) + printf("Failed to set termios for GPS\n"); + } + + osmo_fd_register(&gps_bfd); + + return 0; +} + +void osmo_serialgps_close(void) +{ + if (gps_bfd.fd <= 0) + return; + + LOGP(DGPS, LOGL_INFO, "Close GPS device\n"); + + osmo_fd_unregister(&gps_bfd); + + if (isatty(gps_bfd.fd)) + tcsetattr(gps_bfd.fd, TCSANOW, &gps_old_termios); + + close(gps_bfd.fd); + gps_bfd.fd = -1; /* -1 or 0 indicates: 'close' */ +} + +void osmo_gps_init(void) +{ + memset(&gps_bfd, 0, sizeof(gps_bfd)); +} + +int osmo_gps_open(void) +{ + switch (g.gps_type) { +#ifdef _HAVE_GPSD + case GPS_TYPE_GPSD: + return osmo_gpsd_open(); +#endif + case GPS_TYPE_SERIAL: + return osmo_serialgps_open(); + + default: + return 0; + } +} + +void osmo_gps_close(void) +{ + switch (g.gps_type) { +#ifdef _HAVE_GPSD + case GPS_TYPE_GPSD: + return osmo_gpsd_close(); +#endif + case GPS_TYPE_SERIAL: + return osmo_serialgps_close(); + + default: + return; + } +} + diff --git a/src/host/layer23/src/common/l1ctl.c b/src/host/layer23/src/common/l1ctl.c new file mode 100644 index 00000000..521949c1 --- /dev/null +++ b/src/host/layer23/src/common/l1ctl.c @@ -0,0 +1,967 @@ +/* Layer1 control code, talking L1CTL protocol with L1 on the phone */ + +/* (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> + +#include <arpa/inet.h> + +#include <l1ctl_proto.h> + +#include <osmocom/core/signal.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/gsm/rsl.h> + +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/l1l2_interface.h> +#include <osmocom/gsm/lapdm.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/codec/codec.h> + +extern struct gsmtap_inst *gsmtap_inst; + +static struct msgb *osmo_l1_alloc(uint8_t msg_type) +{ + struct l1ctl_hdr *l1h; + struct msgb *msg = msgb_alloc_headroom(256, 4, "osmo_l1"); + + if (!msg) { + LOGP(DL1C, LOGL_ERROR, "Failed to allocate memory.\n"); + return NULL; + } + + msg->l1h = msgb_put(msg, sizeof(*l1h)); + l1h = (struct l1ctl_hdr *) msg->l1h; + l1h->msg_type = msg_type; + + return msg; +} + + +static inline int msb_get_bit(uint8_t *buf, int bn) +{ + int pos_byte = bn >> 3; + int pos_bit = 7 - (bn & 7); + + return (buf[pos_byte] >> pos_bit) & 1; +} + +static inline void msb_set_bit(uint8_t *buf, int bn, int bit) +{ + int pos_byte = bn >> 3; + int pos_bit = 7 - (bn & 7); + + buf[pos_byte] |= (bit << pos_bit); +} + + +static int osmo_make_band_arfcn(struct osmocom_ms *ms, uint16_t arfcn) +{ + /* TODO: Include the band */ + return arfcn; +} + +static int rx_l1_fbsb_conf(struct osmocom_ms *ms, struct msgb *msg) +{ + struct l1ctl_info_dl *dl; + struct l1ctl_fbsb_conf *sb; + struct gsm_time tm; + struct osmobb_fbsb_res fr; + + if (msgb_l3len(msg) < sizeof(*dl) + sizeof(*sb)) { + LOGP(DL1C, LOGL_ERROR, "FBSB RESP: MSG too short %u\n", + msgb_l3len(msg)); + return -1; + } + + dl = (struct l1ctl_info_dl *) msg->l1h; + sb = (struct l1ctl_fbsb_conf *) dl->payload; + + LOGP(DL1C, LOGL_INFO, "snr=%04x, arfcn=%u result=%u\n", dl->snr, + ntohs(dl->band_arfcn), sb->result); + + if (sb->result != 0) { + LOGP(DL1C, LOGL_ERROR, "FBSB RESP: result=%u\n", sb->result); + fr.ms = ms; + fr.band_arfcn = ntohs(dl->band_arfcn); + osmo_signal_dispatch(SS_L1CTL, S_L1CTL_FBSB_ERR, &fr); + return 0; + } + + gsm_fn2gsmtime(&tm, ntohl(dl->frame_nr)); + DEBUGP(DL1C, "SCH: SNR: %u TDMA: (%.4u/%.2u/%.2u) bsic: %d\n", + dl->snr, tm.t1, tm.t2, tm.t3, sb->bsic); + fr.ms = ms; + fr.snr = dl->snr; + fr.bsic = sb->bsic; + fr.band_arfcn = ntohs(dl->band_arfcn); + osmo_signal_dispatch(SS_L1CTL, S_L1CTL_FBSB_RESP, &fr); + + return 0; +} + +static int rx_l1_rach_conf(struct osmocom_ms *ms, struct msgb *msg) +{ + struct lapdm_entity *le = &ms->lapdm_channel.lapdm_dcch; + struct osmo_phsap_prim pp; + struct l1ctl_info_dl *dl; + + if (msgb_l2len(msg) < sizeof(*dl)) { + LOGP(DL1C, LOGL_ERROR, "RACH CONF: MSG too short %u\n", + msgb_l3len(msg)); + msgb_free(msg); + return -1; + } + + dl = (struct l1ctl_info_dl *) msg->l1h; + + osmo_prim_init(&pp.oph, SAP_GSM_PH, PRIM_PH_RACH, + PRIM_OP_CONFIRM, msg); + pp.u.rach_ind.fn = ntohl(dl->frame_nr); + + return lapdm_phsap_up(&pp.oph, le); +} + +/* Receive L1CTL_DATA_IND (Data Indication from L1) */ +static int rx_ph_data_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + struct osmo_phsap_prim pp; + struct l1ctl_info_dl *dl; + struct l1ctl_data_ind *ccch; + struct lapdm_entity *le; + struct rx_meas_stat *meas = &ms->meas; + uint8_t chan_type, chan_ts, chan_ss; + uint8_t gsmtap_chan_type; + struct gsm_time tm; + + if (msgb_l3len(msg) < sizeof(*ccch)) { + LOGP(DL1C, LOGL_ERROR, "MSG too short Data Ind: %u\n", + msgb_l3len(msg)); + msgb_free(msg); + return -1; + } + + dl = (struct l1ctl_info_dl *) msg->l1h; + msg->l2h = dl->payload; + ccch = (struct l1ctl_data_ind *) msg->l2h; + + gsm_fn2gsmtime(&tm, ntohl(dl->frame_nr)); + rsl_dec_chan_nr(dl->chan_nr, &chan_type, &chan_ss, &chan_ts); + DEBUGP(DL1C, "%s (%.4u/%.2u/%.2u) %d dBm: %s\n", + rsl_chan_nr_str(dl->chan_nr), tm.t1, tm.t2, tm.t3, + (int)dl->rx_level-110, + osmo_hexdump(ccch->data, sizeof(ccch->data))); + + meas->last_fn = ntohl(dl->frame_nr); + meas->frames++; + meas->snr += dl->snr; + meas->berr += dl->num_biterr; + meas->rxlev += dl->rx_level; + + /* counting loss criteria */ + if (!(dl->link_id & 0x40)) { + switch (chan_type) { + case RSL_CHAN_PCH_AGCH: + if (!meas->ds_fail) + break; + if (dl->fire_crc >= 2) + meas->dsc -= 4; + else + meas->dsc += 1; + if (meas->dsc > meas->ds_fail) + meas->dsc = meas->ds_fail; + if (meas->dsc < meas->ds_fail) + printf("LOSS counter for CCCH %d\n", meas->dsc); + if (meas->dsc > 0) + break; + meas->ds_fail = 0; + osmo_signal_dispatch(SS_L1CTL, S_L1CTL_LOSS_IND, ms); + break; + } + } else { + switch (chan_type) { + case RSL_CHAN_Bm_ACCHs: + case RSL_CHAN_Lm_ACCHs: + case RSL_CHAN_SDCCH4_ACCH: + case RSL_CHAN_SDCCH8_ACCH: + if (!meas->rl_fail) + break; + if (dl->fire_crc >= 2) + meas->s -= 1; + else + meas->s += 2; + if (meas->s > meas->rl_fail) + meas->s = meas->rl_fail; + if (meas->s < meas->rl_fail) + printf("LOSS counter for ACCH %d\n", meas->s); + if (meas->s > 0) + break; + meas->rl_fail = 0; + osmo_signal_dispatch(SS_L1CTL, S_L1CTL_LOSS_IND, ms); + break; + } + } + + if (dl->fire_crc >= 2) { +printf("Dropping frame with %u bit errors\n", dl->num_biterr); + LOGP(DL1C, LOGL_NOTICE, "Dropping frame with %u bit errors\n", + dl->num_biterr); + msgb_free(msg); + return 0; + } + + /* send CCCH data via GSMTAP */ + gsmtap_chan_type = chantype_rsl2gsmtap(chan_type, dl->link_id); + gsmtap_send(gsmtap_inst, ntohs(dl->band_arfcn), chan_ts, + gsmtap_chan_type, chan_ss, tm.fn, dl->rx_level-110, + dl->snr, ccch->data, sizeof(ccch->data)); + + /* determine LAPDm entity based on SACCH or not */ + if (dl->link_id & 0x40) + le = &ms->lapdm_channel.lapdm_acch; + else + le = &ms->lapdm_channel.lapdm_dcch; + + /* pull the L1 header from the msgb */ + msgb_pull(msg, msg->l2h - (msg->l1h-sizeof(struct l1ctl_hdr))); + msg->l1h = NULL; + + osmo_prim_init(&pp.oph, SAP_GSM_PH, PRIM_PH_DATA, + PRIM_OP_INDICATION, msg); + pp.u.data.chan_nr = dl->chan_nr; + pp.u.data.link_id = dl->link_id; + + /* send it up into LAPDm */ + return lapdm_phsap_up(&pp.oph, le); +} + +/* Receive L1CTL_DATA_CONF (Data Confirm from L1) */ +static int rx_ph_data_conf(struct osmocom_ms *ms, struct msgb *msg) +{ + struct osmo_phsap_prim pp; + struct l1ctl_info_dl *dl = (struct l1ctl_info_dl *) msg->l1h; + struct lapdm_entity *le; + + osmo_prim_init(&pp.oph, SAP_GSM_PH, PRIM_PH_RTS, + PRIM_OP_INDICATION, msg); + + /* determine LAPDm entity based on SACCH or not */ + if (dl->link_id & 0x40) + le = &ms->lapdm_channel.lapdm_acch; + else + le = &ms->lapdm_channel.lapdm_dcch; + + /* send it up into LAPDm */ + return lapdm_phsap_up(&pp.oph, le); +} + +/* Transmit L1CTL_DATA_REQ */ +int l1ctl_tx_data_req(struct osmocom_ms *ms, struct msgb *msg, + uint8_t chan_nr, uint8_t link_id) +{ + struct l1ctl_hdr *l1h; + struct l1ctl_info_ul *l1i_ul; + uint8_t chan_type, chan_ts, chan_ss; + uint8_t gsmtap_chan_type; + + DEBUGP(DL1C, "(%s)\n", osmo_hexdump(msg->l2h, msgb_l2len(msg))); + + if (msgb_l2len(msg) > 23) { + LOGP(DL1C, LOGL_ERROR, "L1 cannot handle message length " + "> 23 (%u)\n", msgb_l2len(msg)); + msgb_free(msg); + return -EINVAL; + } else if (msgb_l2len(msg) < 23) + LOGP(DL1C, LOGL_ERROR, "L1 message length < 23 (%u) " + "doesn't seem right!\n", msgb_l2len(msg)); + + /* send copy via GSMTAP */ + rsl_dec_chan_nr(chan_nr, &chan_type, &chan_ss, &chan_ts); + gsmtap_chan_type = chantype_rsl2gsmtap(chan_type, link_id); + gsmtap_send(gsmtap_inst, 0|0x4000, chan_ts, gsmtap_chan_type, + chan_ss, 0, 127, 255, msg->l2h, msgb_l2len(msg)); + + /* prepend uplink info header */ + l1i_ul = (struct l1ctl_info_ul *) msgb_push(msg, sizeof(*l1i_ul)); + + l1i_ul->chan_nr = chan_nr; + l1i_ul->link_id = link_id; + + /* prepend l1 header */ + msg->l1h = msgb_push(msg, sizeof(*l1h)); + l1h = (struct l1ctl_hdr *) msg->l1h; + l1h->msg_type = L1CTL_DATA_REQ; + + return osmo_send_l1(ms, msg); +} + +/* Transmit FBSB_REQ */ +int l1ctl_tx_fbsb_req(struct osmocom_ms *ms, uint16_t arfcn, + uint8_t flags, uint16_t timeout, uint8_t sync_info_idx, + uint8_t ccch_mode) +{ + struct msgb *msg; + struct l1ctl_fbsb_req *req; + + LOGP(DL1C, LOGL_INFO, "Sync Req\n"); + + msg = osmo_l1_alloc(L1CTL_FBSB_REQ); + if (!msg) + return -1; + + req = (struct l1ctl_fbsb_req *) msgb_put(msg, sizeof(*req)); + req->band_arfcn = htons(osmo_make_band_arfcn(ms, arfcn)); + req->timeout = htons(timeout); + /* Threshold when to consider FB_MODE1: 4kHz - 1kHz */ + req->freq_err_thresh1 = htons(11000 - 1000); + /* Threshold when to consider SCH: 1kHz - 200Hz */ + req->freq_err_thresh2 = htons(1000 - 200); + /* not used yet! */ + req->num_freqerr_avg = 3; + req->flags = flags; + req->sync_info_idx = sync_info_idx; + req->ccch_mode = ccch_mode; + + return osmo_send_l1(ms, msg); +} + +/* Transmit L1CTL_CCCH_MODE_REQ */ +int l1ctl_tx_ccch_mode_req(struct osmocom_ms *ms, uint8_t ccch_mode) +{ + struct msgb *msg; + struct l1ctl_ccch_mode_req *req; + + LOGP(DL1C, LOGL_INFO, "CCCH Mode Req\n"); + + msg = osmo_l1_alloc(L1CTL_CCCH_MODE_REQ); + if (!msg) + return -1; + + req = (struct l1ctl_ccch_mode_req *) msgb_put(msg, sizeof(*req)); + req->ccch_mode = ccch_mode; + + return osmo_send_l1(ms, msg); +} + +/* Transmit L1CTL_TCH_MODE_REQ */ +int l1ctl_tx_tch_mode_req(struct osmocom_ms *ms, uint8_t tch_mode, + uint8_t audio_mode) +{ + struct msgb *msg; + struct l1ctl_tch_mode_req *req; + + LOGP(DL1C, LOGL_INFO, "TCH Mode Req\n"); + + msg = osmo_l1_alloc(L1CTL_TCH_MODE_REQ); + if (!msg) + return -1; + + req = (struct l1ctl_tch_mode_req *) msgb_put(msg, sizeof(*req)); + req->tch_mode = tch_mode; + req->audio_mode = audio_mode; + + return osmo_send_l1(ms, msg); +} + +/* Transmit L1CTL_PARAM_REQ */ +int l1ctl_tx_param_req(struct osmocom_ms *ms, uint8_t ta, uint8_t tx_power) +{ + struct msgb *msg; + struct l1ctl_info_ul *ul; + struct l1ctl_par_req *req; + + msg = osmo_l1_alloc(L1CTL_PARAM_REQ); + if (!msg) + return -1; + + DEBUGP(DL1C, "PARAM Req. ta=%d, tx_power=%d\n", ta, tx_power); + ul = (struct l1ctl_info_ul *) msgb_put(msg, sizeof(*ul)); + req = (struct l1ctl_par_req *) msgb_put(msg, sizeof(*req)); + req->tx_power = tx_power; + req->ta = ta; + + return osmo_send_l1(ms, msg); +} + +/* Transmit L1CTL_CRYPTO_REQ */ +int l1ctl_tx_crypto_req(struct osmocom_ms *ms, uint8_t algo, uint8_t *key, + uint8_t len) +{ + struct msgb *msg; + struct l1ctl_info_ul *ul; + struct l1ctl_crypto_req *req; + + msg = osmo_l1_alloc(L1CTL_CRYPTO_REQ); + if (!msg) + return -1; + + DEBUGP(DL1C, "CRYPTO Req. algo=%d, len=%d\n", algo, len); + ul = (struct l1ctl_info_ul *) msgb_put(msg, sizeof(*ul)); + req = (struct l1ctl_crypto_req *) msgb_put(msg, sizeof(*req) + len); + req->algo = algo; + if (len) + memcpy(req->key, key, len); + + return osmo_send_l1(ms, msg); +} + +/* Transmit L1CTL_RACH_REQ */ +int l1ctl_tx_rach_req(struct osmocom_ms *ms, uint8_t ra, uint16_t offset, + uint8_t combined) +{ + struct msgb *msg; + struct l1ctl_info_ul *ul; + struct l1ctl_rach_req *req; + + msg = osmo_l1_alloc(L1CTL_RACH_REQ); + if (!msg) + return -1; + + DEBUGP(DL1C, "RACH Req. offset=%d combined=%d\n", offset, combined); + ul = (struct l1ctl_info_ul *) msgb_put(msg, sizeof(*ul)); + req = (struct l1ctl_rach_req *) msgb_put(msg, sizeof(*req)); + req->ra = ra; + req->offset = htons(offset); + req->combined = combined; + + return osmo_send_l1(ms, msg); +} + +/* Transmit L1CTL_DM_EST_REQ */ +int l1ctl_tx_dm_est_req_h0(struct osmocom_ms *ms, uint16_t band_arfcn, + uint8_t chan_nr, uint8_t tsc, uint8_t tch_mode, + uint8_t audio_mode) +{ + struct msgb *msg; + struct l1ctl_info_ul *ul; + struct l1ctl_dm_est_req *req; + + msg = osmo_l1_alloc(L1CTL_DM_EST_REQ); + if (!msg) + return -1; + + LOGP(DL1C, LOGL_INFO, "Tx Dedic.Mode Est Req (arfcn=%u, " + "chan_nr=0x%02x)\n", band_arfcn, chan_nr); + + ul = (struct l1ctl_info_ul *) msgb_put(msg, sizeof(*ul)); + ul->chan_nr = chan_nr; + ul->link_id = 0; + + req = (struct l1ctl_dm_est_req *) msgb_put(msg, sizeof(*req)); + req->tsc = tsc; + req->h = 0; + req->h0.band_arfcn = htons(band_arfcn); + req->tch_mode = tch_mode; + req->audio_mode = audio_mode; + + return osmo_send_l1(ms, msg); +} + +int l1ctl_tx_dm_est_req_h1(struct osmocom_ms *ms, uint8_t maio, uint8_t hsn, + uint16_t *ma, uint8_t ma_len, + uint8_t chan_nr, uint8_t tsc, uint8_t tch_mode, + uint8_t audio_mode) +{ + struct msgb *msg; + struct l1ctl_info_ul *ul; + struct l1ctl_dm_est_req *req; + int i; + + msg = osmo_l1_alloc(L1CTL_DM_EST_REQ); + if (!msg) + return -1; + + LOGP(DL1C, LOGL_INFO, "Tx Dedic.Mode Est Req (maio=%u, hsn=%u, " + "chan_nr=0x%02x)\n", maio, hsn, chan_nr); + + ul = (struct l1ctl_info_ul *) msgb_put(msg, sizeof(*ul)); + ul->chan_nr = chan_nr; + ul->link_id = 0; + + req = (struct l1ctl_dm_est_req *) msgb_put(msg, sizeof(*req)); + req->tsc = tsc; + req->h = 1; + req->h1.maio = maio; + req->h1.hsn = hsn; + req->h1.n = ma_len; + for (i = 0; i < ma_len; i++) + req->h1.ma[i] = htons(ma[i]); + req->tch_mode = tch_mode; + req->audio_mode = audio_mode; + + return osmo_send_l1(ms, msg); +} + +/* Transmit L1CTL_DM_FREQ_REQ */ +int l1ctl_tx_dm_freq_req_h0(struct osmocom_ms *ms, uint16_t band_arfcn, + uint8_t tsc, uint16_t fn) +{ + struct msgb *msg; + struct l1ctl_info_ul *ul; + struct l1ctl_dm_freq_req *req; + + msg = osmo_l1_alloc(L1CTL_DM_FREQ_REQ); + if (!msg) + return -1; + + LOGP(DL1C, LOGL_INFO, "Tx Dedic.Mode Freq Req (arfcn=%u, fn=%d)\n", + band_arfcn, fn); + + ul = (struct l1ctl_info_ul *) msgb_put(msg, sizeof(*ul)); + ul->chan_nr = 0; + ul->link_id = 0; + + req = (struct l1ctl_dm_freq_req *) msgb_put(msg, sizeof(*req)); + req->fn = htons(fn); + req->tsc = tsc; + req->h = 0; + req->h0.band_arfcn = htons(band_arfcn); + + return osmo_send_l1(ms, msg); +} + +int l1ctl_tx_dm_freq_req_h1(struct osmocom_ms *ms, uint8_t maio, uint8_t hsn, + uint16_t *ma, uint8_t ma_len, + uint8_t tsc, uint16_t fn) +{ + struct msgb *msg; + struct l1ctl_info_ul *ul; + struct l1ctl_dm_freq_req *req; + int i; + + msg = osmo_l1_alloc(L1CTL_DM_FREQ_REQ); + if (!msg) + return -1; + + LOGP(DL1C, LOGL_INFO, "Tx Dedic.Mode Freq Req (maio=%u, hsn=%u, " + "fn=%d)\n", maio, hsn, fn); + + ul = (struct l1ctl_info_ul *) msgb_put(msg, sizeof(*ul)); + ul->chan_nr = 0; + ul->link_id = 0; + + req = (struct l1ctl_dm_freq_req *) msgb_put(msg, sizeof(*req)); + req->fn = htons(fn); + req->tsc = tsc; + req->h = 1; + req->h1.maio = maio; + req->h1.hsn = hsn; + req->h1.n = ma_len; + for (i = 0; i < ma_len; i++) + req->h1.ma[i] = htons(ma[i]); + + return osmo_send_l1(ms, msg); +} + +/* Transmit L1CTL_DM_REL_REQ */ +int l1ctl_tx_dm_rel_req(struct osmocom_ms *ms) +{ + struct msgb *msg; + struct l1ctl_info_ul *ul; + + msg = osmo_l1_alloc(L1CTL_DM_REL_REQ); + if (!msg) + return -1; + + LOGP(DL1C, LOGL_INFO, "Tx Dedic.Mode Rel Req\n"); + + ul = (struct l1ctl_info_ul *) msgb_put(msg, sizeof(*ul)); + + return osmo_send_l1(ms, msg); +} + +int l1ctl_tx_echo_req(struct osmocom_ms *ms, unsigned int len) +{ + struct msgb *msg; + uint8_t *data; + unsigned int i; + + msg = osmo_l1_alloc(L1CTL_ECHO_REQ); + if (!msg) + return -1; + + data = msgb_put(msg, len); + for (i = 0; i < len; i++) + data[i] = i % 8; + + return osmo_send_l1(ms, msg); +} + +int l1ctl_tx_sim_req(struct osmocom_ms *ms, uint8_t *data, uint16_t length) +{ + struct msgb *msg; + uint8_t *dat; + + msg = osmo_l1_alloc(L1CTL_SIM_REQ); + if (!msg) + return -1; + + dat = msgb_put(msg, length); + memcpy(dat, data, length); + + return osmo_send_l1(ms, msg); +} + +/* just forward the SIM response to the SIM handler */ +static int rx_l1_sim_conf(struct osmocom_ms *ms, struct msgb *msg) +{ + uint16_t len = msg->len - sizeof(struct l1ctl_hdr); + uint8_t *data = msg->data + sizeof(struct l1ctl_hdr); + + LOGP(DL1C, LOGL_INFO, "SIM %s\n", osmo_hexdump(data, len)); + + /* pull the L1 header from the msgb */ + msgb_pull(msg, sizeof(struct l1ctl_hdr)); + msg->l1h = NULL; + + sim_apdu_resp(ms, msg); + + return 0; +} + +/* Transmit L1CTL_PM_REQ */ +int l1ctl_tx_pm_req_range(struct osmocom_ms *ms, uint16_t arfcn_from, + uint16_t arfcn_to) +{ + struct msgb *msg; + struct l1ctl_pm_req *pm; + + msg = osmo_l1_alloc(L1CTL_PM_REQ); + if (!msg) + return -1; + + LOGP(DL1C, LOGL_INFO, "Tx PM Req (%u-%u)\n", arfcn_from, arfcn_to); + pm = (struct l1ctl_pm_req *) msgb_put(msg, sizeof(*pm)); + pm->type = 1; + pm->range.band_arfcn_from = htons(arfcn_from); + pm->range.band_arfcn_to = htons(arfcn_to); + + return osmo_send_l1(ms, msg); +} + +/* Transmit L1CTL_RESET_REQ */ +int l1ctl_tx_reset_req(struct osmocom_ms *ms, uint8_t type) +{ + struct msgb *msg; + struct l1ctl_reset *res; + + msg = osmo_l1_alloc(L1CTL_RESET_REQ); + if (!msg) + return -1; + + LOGP(DL1C, LOGL_INFO, "Tx Reset Req (%u)\n", type); + res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res)); + res->type = type; + + return osmo_send_l1(ms, msg); +} + +/* Receive L1CTL_RESET_IND */ +static int rx_l1_reset(struct osmocom_ms *ms) +{ + LOGP(DL1C, LOGL_INFO, "Layer1 Reset indication\n"); + osmo_signal_dispatch(SS_L1CTL, S_L1CTL_RESET, ms); + + return 0; +} + +/* Receive L1CTL_PM_CONF */ +static int rx_l1_pm_conf(struct osmocom_ms *ms, struct msgb *msg) +{ + struct l1ctl_pm_conf *pmr; + + for (pmr = (struct l1ctl_pm_conf *) msg->l1h; + (uint8_t *) pmr < msg->tail; pmr++) { + struct osmobb_meas_res mr; + DEBUGP(DL1C, "PM MEAS: ARFCN: %4u RxLev: %3d %3d\n", + ntohs(pmr->band_arfcn), pmr->pm[0], pmr->pm[1]); + mr.band_arfcn = ntohs(pmr->band_arfcn); + mr.rx_lev = pmr->pm[0]; + mr.ms = ms; + osmo_signal_dispatch(SS_L1CTL, S_L1CTL_PM_RES, &mr); + } + return 0; +} + +/* Receive L1CTL_CCCH_MODE_CONF */ +static int rx_l1_ccch_mode_conf(struct osmocom_ms *ms, struct msgb *msg) +{ + struct osmobb_ccch_mode_conf mc; + struct l1ctl_ccch_mode_conf *conf; + + if (msgb_l3len(msg) < sizeof(*conf)) { + LOGP(DL1C, LOGL_ERROR, "CCCH MODE CONF: MSG too short %u\n", + msgb_l3len(msg)); + return -1; + } + + conf = (struct l1ctl_ccch_mode_conf *) msg->l1h; + + LOGP(DL1C, LOGL_INFO, "CCCH MODE CONF: mode=%u\n", conf->ccch_mode); + + mc.ccch_mode = conf->ccch_mode; + mc.ms = ms; + osmo_signal_dispatch(SS_L1CTL, S_L1CTL_CCCH_MODE_CONF, &mc); + + return 0; +} + +/* Receive L1CTL_TCH_MODE_CONF */ +static int rx_l1_tch_mode_conf(struct osmocom_ms *ms, struct msgb *msg) +{ + struct osmobb_tch_mode_conf mc; + struct l1ctl_tch_mode_conf *conf; + + if (msgb_l3len(msg) < sizeof(*conf)) { + LOGP(DL1C, LOGL_ERROR, "TCH MODE CONF: MSG too short %u\n", + msgb_l3len(msg)); + return -1; + } + + conf = (struct l1ctl_tch_mode_conf *) msg->l1h; + + LOGP(DL1C, LOGL_INFO, "TCH MODE CONF: mode=%u\n", conf->tch_mode); + + mc.tch_mode = conf->tch_mode; + mc.audio_mode = conf->audio_mode; + mc.ms = ms; + osmo_signal_dispatch(SS_L1CTL, S_L1CTL_TCH_MODE_CONF, &mc); + + return 0; +} + +/* Receive L1CTL_TRAFFIC_IND (Traffic Indication from L1) */ +static int rx_l1_traffic_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + struct l1ctl_info_dl *dl; + struct l1ctl_traffic_ind *ti; + uint8_t fr[33]; + int i, di, si; + + /* Header handling */ + dl = (struct l1ctl_info_dl *) msg->l1h; + msg->l2h = dl->payload; + ti = (struct l1ctl_traffic_ind *) msg->l2h; + + memset(fr, 0x00, 33); + fr[0] = 0xd0; + for (i = 0; i < 260; i++) { + di = gsm610_bitorder[i]; + si = (i > 181) ? i + 4 : i; + msb_set_bit(fr, 4 + di, msb_get_bit(ti->data, si)); + } + memcpy(ti->data, fr, 33); + + DEBUGP(DL1C, "TRAFFIC IND (%s)\n", osmo_hexdump(ti->data, 33)); + + /* distribute or drop */ + if (ms->l1_entity.l1_traffic_ind) { + /* pull the L1 header from the msgb */ + msgb_pull(msg, msg->l2h - (msg->l1h-sizeof(struct l1ctl_hdr))); + msg->l1h = NULL; + + return ms->l1_entity.l1_traffic_ind(ms, msg); + } + + msgb_free(msg); + return 0; +} + +/* Transmit L1CTL_TRAFFIC_REQ (Traffic Request to L1) */ +int l1ctl_tx_traffic_req(struct osmocom_ms *ms, struct msgb *msg, + uint8_t chan_nr, uint8_t link_id) +{ + struct l1ctl_hdr *l1h; + struct l1ctl_info_ul *l1i_ul; + struct l1ctl_traffic_req *tr; + uint8_t fr[33]; + int i, di, si; + + /* Header handling */ + tr = (struct l1ctl_traffic_req *) msg->l2h; + + DEBUGP(DL1C, "TRAFFIC REQ (%s)\n", + osmo_hexdump(msg->l2h, msgb_l2len(msg))); + + if (msgb_l2len(msg) != 33) { + LOGP(DL1C, LOGL_ERROR, "Traffic Request has incorrect length " + "(%u != 33)\n", msgb_l2len(msg)); + msgb_free(msg); + return -EINVAL; + } + + if ((tr->data[0] >> 4) != 0xd) { + LOGP(DL1C, LOGL_ERROR, "Traffic Request has incorrect magic " + "(%u != 0xd)\n", tr->data[0] >> 4); + msgb_free(msg); + return -EINVAL; + } + + memset(fr, 0x00, 33); + for (i = 0; i < 260; i++) { + si = gsm610_bitorder[i]; + di = (i > 181) ? i + 4 : i; + msb_set_bit(fr, di, msb_get_bit(tr->data, 4 + si)); + } + memcpy(tr->data, fr, 33); +// printf("TX %s\n", osmo_hexdump(tr->data, 33)); + + /* prepend uplink info header */ + l1i_ul = (struct l1ctl_info_ul *) msgb_push(msg, sizeof(*l1i_ul)); + + l1i_ul->chan_nr = chan_nr; + l1i_ul->link_id = link_id; + + /* prepend l1 header */ + msg->l1h = msgb_push(msg, sizeof(*l1h)); + l1h = (struct l1ctl_hdr *) msg->l1h; + l1h->msg_type = L1CTL_TRAFFIC_REQ; + + return osmo_send_l1(ms, msg); +} + +/* Transmit L1CTL_NEIGH_PM_REQ */ +int l1ctl_tx_neigh_pm_req(struct osmocom_ms *ms, int num, uint16_t *arfcn) +{ + struct msgb *msg; + struct l1ctl_neigh_pm_req *pm_req; + int i; + + msg = osmo_l1_alloc(L1CTL_NEIGH_PM_REQ); + if (!msg) + return -1; + + LOGP(DL1C, LOGL_INFO, "Tx NEIGH PM Req (num %u)\n", num); + pm_req = (struct l1ctl_neigh_pm_req *) msgb_put(msg, sizeof(*pm_req)); + pm_req->n = num; + for (i = 0; i < num; i++) { + pm_req->band_arfcn[i] = htons(*arfcn++); + pm_req->tn[i] = 0; + } + + return osmo_send_l1(ms, msg); +} + +/* Receive L1CTL_NEIGH_PM_IND */ +static int rx_l1_neigh_pm_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + struct l1ctl_neigh_pm_ind *pm_ind; + + for (pm_ind = (struct l1ctl_neigh_pm_ind *) msg->l1h; + (uint8_t *) pm_ind < msg->tail; pm_ind++) { + struct osmobb_neigh_pm_ind mi; + DEBUGP(DL1C, "NEIGH_PM IND: ARFCN: %4u RxLev: %3d %3d\n", + ntohs(pm_ind->band_arfcn), pm_ind->pm[0], + pm_ind->pm[1]); + mi.band_arfcn = ntohs(pm_ind->band_arfcn); + mi.rx_lev = pm_ind->pm[0]; + mi.ms = ms; + osmo_signal_dispatch(SS_L1CTL, S_L1CTL_NEIGH_PM_IND, &mi); + } + return 0; +} + +/* Receive incoming data from L1 using L1CTL format */ +int l1ctl_recv(struct osmocom_ms *ms, struct msgb *msg) +{ + int rc = 0; + struct l1ctl_hdr *l1h; + struct l1ctl_info_dl *dl; + + if (msgb_l2len(msg) < sizeof(*dl)) { + LOGP(DL1C, LOGL_ERROR, "Short Layer2 message: %u\n", + msgb_l2len(msg)); + msgb_free(msg); + return -1; + } + + l1h = (struct l1ctl_hdr *) msg->l1h; + + /* move the l1 header pointer to point _BEHIND_ l1ctl_hdr, + as the l1ctl header is of no interest to subsequent code */ + msg->l1h = l1h->data; + + switch (l1h->msg_type) { + case L1CTL_FBSB_CONF: + rc = rx_l1_fbsb_conf(ms, msg); + msgb_free(msg); + break; + case L1CTL_DATA_IND: + rc = rx_ph_data_ind(ms, msg); + break; + case L1CTL_DATA_CONF: + rc = rx_ph_data_conf(ms, msg); + break; + case L1CTL_RESET_IND: + case L1CTL_RESET_CONF: + rc = rx_l1_reset(ms); + msgb_free(msg); + break; + case L1CTL_PM_CONF: + rc = rx_l1_pm_conf(ms, msg); + if (l1h->flags & L1CTL_F_DONE) + osmo_signal_dispatch(SS_L1CTL, S_L1CTL_PM_DONE, ms); + msgb_free(msg); + break; + case L1CTL_RACH_CONF: + rc = rx_l1_rach_conf(ms, msg); + break; + case L1CTL_CCCH_MODE_CONF: + rc = rx_l1_ccch_mode_conf(ms, msg); + msgb_free(msg); + break; + case L1CTL_TCH_MODE_CONF: + rc = rx_l1_tch_mode_conf(ms, msg); + msgb_free(msg); + break; + case L1CTL_SIM_CONF: + rc = rx_l1_sim_conf(ms, msg); + break; + case L1CTL_NEIGH_PM_IND: + rc = rx_l1_neigh_pm_ind(ms, msg); + msgb_free(msg); + break; + case L1CTL_TRAFFIC_IND: + rc = rx_l1_traffic_ind(ms, msg); + break; + case L1CTL_TRAFFIC_CONF: + msgb_free(msg); + break; + default: + LOGP(DL1C, LOGL_ERROR, "Unknown MSG: %u\n", l1h->msg_type); + msgb_free(msg); + break; + } + + return rc; +} diff --git a/src/host/layer23/src/common/l1ctl_lapdm_glue.c b/src/host/layer23/src/common/l1ctl_lapdm_glue.c new file mode 100644 index 00000000..0b2a8ed5 --- /dev/null +++ b/src/host/layer23/src/common/l1ctl_lapdm_glue.c @@ -0,0 +1,62 @@ +/* Glue code between L1CTL and LAPDm */ + +/* (C) 2011 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdint.h> + +#include <l1ctl_proto.h> + +#include <osmocom/gsm/prim.h> + +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/gsm/lapdm.h> + +/* LAPDm wants to send a PH-* primitive to the physical layer (L1) */ +int l1ctl_ph_prim_cb(struct osmo_prim_hdr *oph, void *ctx) +{ + struct osmocom_ms *ms = ctx; + struct osmo_phsap_prim *pp = (struct osmo_phsap_prim *) oph; + int rc = 0; + + if (oph->sap != SAP_GSM_PH) + return -ENODEV; + + if (oph->operation != PRIM_OP_REQUEST) + return -EINVAL; + + switch (oph->primitive) { + case PRIM_PH_DATA: + rc = l1ctl_tx_data_req(ms, oph->msg, pp->u.data.chan_nr, + pp->u.data.link_id); + break; + case PRIM_PH_RACH: + l1ctl_tx_param_req(ms, pp->u.rach_req.ta, + pp->u.rach_req.tx_power); + rc = l1ctl_tx_rach_req(ms, pp->u.rach_req.ra, + pp->u.rach_req.offset, + pp->u.rach_req.is_combined_ccch); + break; + default: + rc = -EINVAL; + } + + return rc; +} diff --git a/src/host/layer23/src/common/l1l2_interface.c b/src/host/layer23/src/common/l1l2_interface.c new file mode 100644 index 00000000..d89995d9 --- /dev/null +++ b/src/host/layer23/src/common/l1l2_interface.c @@ -0,0 +1,180 @@ +/* Layer 1 socket interface of layer2/3 stack */ + +/* (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/l1l2_interface.h> + +#include <osmocom/core/utils.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include <arpa/inet.h> + +#define _GNU_SOURCE +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> + +#define GSM_L2_LENGTH 256 +#define GSM_L2_HEADROOM 32 + +static int layer2_read(struct osmo_fd *fd) +{ + struct msgb *msg; + uint16_t len; + int rc; + + msg = msgb_alloc_headroom(GSM_L2_LENGTH+GSM_L2_HEADROOM, GSM_L2_HEADROOM, "Layer2"); + if (!msg) { + LOGP(DL1C, LOGL_ERROR, "Failed to allocate msg.\n"); + return -ENOMEM; + } + + rc = read(fd->fd, &len, sizeof(len)); + if (rc < sizeof(len)) { + fprintf(stderr, "Layer2 socket failed\n"); + msgb_free(msg); + if (rc >= 0) + rc = -EIO; + layer2_close((struct osmocom_ms *) fd->data); + return rc; + } + + len = ntohs(len); + if (len > GSM_L2_LENGTH) { + LOGP(DL1C, LOGL_ERROR, "Length is too big: %u\n", len); + msgb_free(msg); + return -EINVAL; + } + + + msg->l1h = msgb_put(msg, len); + rc = read(fd->fd, msg->l1h, msgb_l1len(msg)); + if (rc != msgb_l1len(msg)) { + LOGP(DL1C, LOGL_ERROR, "Can not read data: len=%d rc=%d " + "errno=%d\n", len, rc, errno); + msgb_free(msg); + return rc; + } + + l1ctl_recv((struct osmocom_ms *) fd->data, msg); + + return 0; +} + +static int layer2_write(struct osmo_fd *fd, struct msgb *msg) +{ + int rc; + + if (fd->fd <= 0) + return -EINVAL; + + rc = write(fd->fd, msg->data, msg->len); + if (rc != msg->len) { + LOGP(DL1C, LOGL_ERROR, "Failed to write data: rc: %d\n", rc); + return rc; + } + + return 0; +} + +int layer2_open(struct osmocom_ms *ms, const char *socket_path) +{ + int rc; + struct sockaddr_un local; + + ms->l2_wq.bfd.fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (ms->l2_wq.bfd.fd < 0) { + fprintf(stderr, "Failed to create unix domain socket.\n"); + return ms->l2_wq.bfd.fd; + } + + local.sun_family = AF_UNIX; + strncpy(local.sun_path, socket_path, sizeof(local.sun_path)); + local.sun_path[sizeof(local.sun_path) - 1] = '\0'; + + rc = connect(ms->l2_wq.bfd.fd, (struct sockaddr *) &local, + sizeof(local)); + if (rc < 0) { + fprintf(stderr, "Failed to connect to '%s': %s\n", local.sun_path, + strerror(errno)); + close(ms->l2_wq.bfd.fd); + return rc; + } + + osmo_wqueue_init(&ms->l2_wq, 100); + ms->l2_wq.bfd.data = ms; + ms->l2_wq.bfd.when = BSC_FD_READ; + ms->l2_wq.read_cb = layer2_read; + ms->l2_wq.write_cb = layer2_write; + + rc = osmo_fd_register(&ms->l2_wq.bfd); + if (rc != 0) { + fprintf(stderr, "Failed to register fd.\n"); + close(ms->l2_wq.bfd.fd); + return rc; + } + + return 0; +} + +int layer2_close(struct osmocom_ms *ms) +{ + if (ms->l2_wq.bfd.fd <= 0) + return -EINVAL; + + close(ms->l2_wq.bfd.fd); + ms->l2_wq.bfd.fd = -1; + osmo_fd_unregister(&ms->l2_wq.bfd); + osmo_wqueue_clear(&ms->l2_wq); + + return 0; +} + +int osmo_send_l1(struct osmocom_ms *ms, struct msgb *msg) +{ + uint16_t *len; + + DEBUGP(DL1C, "Sending: '%s'\n", osmo_hexdump(msg->data, msg->len)); + + if (msg->l1h != msg->data) + LOGP(DL1C, LOGL_ERROR, "Message L1 header != Message Data\n"); + + /* prepend 16bit length before sending */ + len = (uint16_t *) msgb_push(msg, sizeof(*len)); + *len = htons(msg->len - sizeof(*len)); + + if (osmo_wqueue_enqueue(&ms->l2_wq, msg) != 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to enqueue msg.\n"); + msgb_free(msg); + return -1; + } + + return 0; +} + + diff --git a/src/host/layer23/src/common/logging.c b/src/host/layer23/src/common/logging.c new file mode 100644 index 00000000..d8fd076b --- /dev/null +++ b/src/host/layer23/src/common/logging.c @@ -0,0 +1,137 @@ +/* Logging/Debug support of the layer2/3 stack */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + + +#include <osmocom/core/utils.h> +#include <osmocom/core/logging.h> +#include <osmocom/bb/common/logging.h> + +static const struct log_info_cat default_categories[] = { + [DRSL] = { + .name = "DRSL", + .description = "Radio Signalling Link (MS)", + .color = "\033[1;35m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DCS] = { + .name = "DCS", + .description = "Cell selection", + .color = "\033[34m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DNB] = { + .name = "DNB", + .description = "Neighbour cell measurement", + .color = "\033[0;31m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DPLMN] = { + .name = "DPLMN", + .description = "PLMN selection", + .color = "\033[32m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DRR] = { + .name = "DRR", + .description = "Radio Resource", + .color = "\033[1;34m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DMM] = { + .name = "DMM", + .description = "Mobility Management", + .color = "\033[1;32m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DCC] = { + .name = "DCC", + .description = "Call Control", + .color = "\033[1;33m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DSS] = { + .name = "DSS", + .description = "Supplenmentary Services", + .color = "\033[1;35m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DSMS] = { + .name = "DSMS", + .description = "Short Message Service", + .color = "\033[1;37m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DMNCC] = { + .name = "DMNCC", + .description = "Mobile Network Call Control", + .color = "\033[1;37m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DMEAS] = { + .name = "DMEAS", + .description = "MEasurement Reporting", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DPAG] = { + .name = "DPAG", + .description = "Paging", + .color = "\033[33m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DL1C] = { + .name = "DL1C", + .description = "Layer 1 Control", + .color = "\033[1;31m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DSAP] = { + .name = "DSAP", + .description = "SAP Control", + .color = "\033[1;31m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DSUM] = { + .name = "DSUM", + .description = "Summary of Process", + .color = "\033[1;37m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DSIM] = { + .name = "DSIM", + .description = "SIM client", + .color = "\033[0;35m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DGPS] = { + .name = "DGPS", + .description = "GPS", + .color = "\033[1;35m", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, +}; + +const struct log_info log_info = { + .filter_fn = NULL, + .cat = default_categories, + .num_cat = ARRAY_SIZE(default_categories), +}; + diff --git a/src/host/layer23/src/common/main.c b/src/host/layer23/src/common/main.c new file mode 100644 index 00000000..59cee03d --- /dev/null +++ b/src/host/layer23/src/common/main.c @@ -0,0 +1,297 @@ +/* Main method of the layer2/3 stack */ + +/* (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/common/l1l2_interface.h> +#include <osmocom/bb/common/sap_interface.h> +#include <osmocom/bb/misc/layer3.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/l23_app.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/core/gsmtap.h> +#include <osmocom/core/utils.h> + +#include <arpa/inet.h> + +#define _GNU_SOURCE +#include <getopt.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> + +struct log_target *stderr_target; + +void *l23_ctx = NULL; + +static char *layer2_socket_path = "/tmp/osmocom_l2"; +static char *sap_socket_path = "/tmp/osmocom_sap"; +struct llist_head ms_list; +static struct osmocom_ms *ms = NULL; +static char *gsmtap_ip = NULL; +static char *vty_ip = "127.0.0.1"; + +unsigned short vty_port = 4247; +int (*l23_app_work) (struct osmocom_ms *ms) = NULL; +int (*l23_app_exit) (struct osmocom_ms *ms) = NULL; +int quit = 0; +struct gsmtap_inst *gsmtap_inst; + +const char *openbsc_copyright = + "%s" + "%s\n" + "License GPLv2+: GNU GPL version 2 or later " + "<http://gnu.org/licenses/gpl.html>\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n\n"; + +static void print_usage(const char *app) +{ + printf("Usage: %s\n", app); +} + +static void print_help() +{ + int options = 0xff; + struct l23_app_info *app = l23_app_info(); + + if (app && app->cfg_supported != 0) + options = app->cfg_supported(); + + printf(" Some help...\n"); + printf(" -h --help this text\n"); + printf(" -s --socket /tmp/osmocom_l2. Path to the unix " + "domain socket (l2)\n"); + + if (options & L23_OPT_SAP) + printf(" -S --sap /tmp/osmocom_sap. Path to the " + "unix domain socket (BTSAP)\n"); + + if (options & L23_OPT_ARFCN) + printf(" -a --arfcn NR The ARFCN to be used for layer2.\n"); + + if (options & L23_OPT_TAP) + printf(" -i --gsmtap-ip The destination IP used for GSMTAP.\n"); + + if (options & L23_OPT_VTY) + printf(" -v --vty-port The VTY port number to telnet " + "to. (default %u)\n", vty_port); + + if (options & L23_OPT_DBG) + printf(" -d --debug Change debug flags.\n"); + + if (options & L23_OPT_VTYIP) + printf(" -u --vty-ip The VTY IP to bind telnet to. " + "(default %s)\n", vty_ip); + + if (app && app->cfg_print_help) + app->cfg_print_help(); +} + +static void build_config(char **opt, struct option **option) +{ + struct l23_app_info *app; + struct option *app_opp = NULL; + int app_len = 0, len; + + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"socket", 1, 0, 's'}, + {"sap", 1, 0, 'S'}, + {"arfcn", 1, 0, 'a'}, + {"gsmtap-ip", 1, 0, 'i'}, + {"vty-ip", 1, 0, 'u'}, + {"vty-port", 1, 0, 'v'}, + {"debug", 1, 0, 'd'}, + }; + + + app = l23_app_info(); + *opt = talloc_asprintf(l23_ctx, "hs:S:a:i:v:d:u:%s", + app && app->getopt_string ? app->getopt_string : ""); + + len = ARRAY_SIZE(long_options); + if (app && app->cfg_getopt_opt) + app_len = app->cfg_getopt_opt(&app_opp); + + *option = talloc_zero_array(l23_ctx, struct option, len + app_len + 1); + memcpy(*option, long_options, sizeof(long_options)); + memcpy(*option + len, app_opp, app_len * sizeof(struct option)); +} + +static void handle_options(int argc, char **argv) +{ + struct l23_app_info *app = l23_app_info(); + struct option *long_options; + char *opt; + + build_config(&opt, &long_options); + + while (1) { + int option_index = 0, c; + + c = getopt_long(argc, argv, opt, + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage(argv[0]); + print_help(); + exit(0); + break; + case 's': + layer2_socket_path = talloc_strdup(l23_ctx, optarg); + break; + case 'S': + sap_socket_path = talloc_strdup(l23_ctx, optarg); + break; + case 'a': + ms->test_arfcn = atoi(optarg); + break; + case 'i': + gsmtap_ip = optarg; + break; + case 'u': + vty_ip = optarg; + break; + case 'v': + vty_port = atoi(optarg); + break; + case 'd': + log_parse_category_mask(stderr_target, optarg); + break; + default: + if (app && app->cfg_handle_opt) + app->cfg_handle_opt(c, optarg); + break; + } + } + + talloc_free(opt); + talloc_free(long_options); +} + +void sighandler(int sigset) +{ + int rc = 0; + + if (sigset == SIGHUP || sigset == SIGPIPE) + return; + + fprintf(stderr, "Signal %d recevied.\n", sigset); + if (l23_app_exit) + rc = l23_app_exit(ms); + + if (rc != -EBUSY) + exit (0); +} + +static void print_copyright() +{ + struct l23_app_info *app; + app = l23_app_info(); + printf(openbsc_copyright, + app && app->copyright ? app->copyright : "", + app && app->contribution ? app->contribution : ""); +} + +int main(int argc, char **argv) +{ + int rc; + + INIT_LLIST_HEAD(&ms_list); + log_init(&log_info, NULL); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + log_set_all_filter(stderr_target, 1); + + l23_ctx = talloc_named_const(NULL, 1, "layer2 context"); + + ms = talloc_zero(l23_ctx, struct osmocom_ms); + if (!ms) { + fprintf(stderr, "Failed to allocate MS\n"); + exit(1); + } + + print_copyright(); + + llist_add_tail(&ms->entity, &ms_list); + + sprintf(ms->name, "1"); + + ms->test_arfcn = 871; + + handle_options(argc, argv); + + rc = layer2_open(ms, layer2_socket_path); + if (rc < 0) { + fprintf(stderr, "Failed during layer2_open()\n"); + exit(1); + } + + rc = sap_open(ms, sap_socket_path); + if (rc < 0) + fprintf(stderr, "Failed during sap_open(), no SIM reader\n"); + + ms->lapdm_channel.lapdm_dcch.l1_ctx = ms; + ms->lapdm_channel.lapdm_dcch.l3_ctx = ms; + ms->lapdm_channel.lapdm_acch.l1_ctx = ms; + ms->lapdm_channel.lapdm_acch.l3_ctx = ms; + lapdm_channel_init(&ms->lapdm_channel, LAPDM_MODE_MS); + lapdm_channel_set_l1(&ms->lapdm_channel, l1ctl_ph_prim_cb, ms); + + rc = l23_app_init(ms); + if (rc < 0) + exit(1); + + if (gsmtap_ip) { + gsmtap_inst = gsmtap_source_init(gsmtap_ip, GSMTAP_UDP_PORT, 1); + if (!gsmtap_inst) { + fprintf(stderr, "Failed during gsmtap_init()\n"); + exit(1); + } + gsmtap_source_add_sink(gsmtap_inst); + } + + signal(SIGINT, sighandler); + signal(SIGHUP, sighandler); + signal(SIGTERM, sighandler); + signal(SIGPIPE, sighandler); + + while (!quit) { + if (l23_app_work) + l23_app_work(ms); + osmo_select_main(0); + } + + return 0; +} diff --git a/src/host/layer23/src/common/networks.c b/src/host/layer23/src/common/networks.c new file mode 100644 index 00000000..40b70a10 --- /dev/null +++ b/src/host/layer23/src/common/networks.c @@ -0,0 +1,1986 @@ +#include <string.h> +#include <stdint.h> +#include <stdio.h> + +#include <osmocom/bb/common/networks.h> + +/* list of networks */ + +struct gsm_networks gsm_networks[] = { + { 0x001, -1, "Test" }, + { 0x001, 0x01f, "Test" }, + { 0x412, -1, "Afghanistan" }, + { 0x412, 0x01f, "AWCC" }, + { 0x412, 0x20f, "Roshan" }, + { 0x412, 0x30f, "New1" }, + { 0x412, 0x40f, "Areeba" }, + { 0x412, 0x50f, "Etisalat" }, /* ? */ + { 0x412, 0x88f, "Afghan Telecom" }, + { 0x276, -1, "Albania" }, + { 0x276, 0x01f, "AMC" }, + { 0x276, 0x02f, "Vodafone" }, + { 0x276, 0x03f, "Eagle Mobile" }, + { 0x276, 0x04f, "Mobile 4 AL" }, + { 0x603, -1, "Algeria" }, + { 0x603, 0x01f, "Algerie Telecom" }, + { 0x603, 0x02f, "Orascom Telecom Algerie" }, + { 0x603, 0x03f, "Nedjma" }, /* ? */ + { 0x213, -1, "Andorra" }, + { 0x213, 0x03f, "Mobiland" }, + { 0x631, -1, "Angola" }, + { 0x631, 0x02f, "UNITEL" }, + { 0x365, -1, "Anguilla" }, + { 0x365, 0x010, "Weblinks Limited" }, + { 0x365, 0x840, "Cable & Wireless" }, /* ? */ + { 0x344, -1, "Antigua and Barbuda" }, + { 0x344, 0x030, "APUA PCS" }, + { 0x338, 0x050, "Digicel" }, /* ? */ + { 0x344, 0x920, "Cable & Wireless (Antigua)" }, + { 0x344, 0x930, "AT&T Wireless (Antigua)" }, + { 0x722, -1, "Argentina" }, + { 0x722, 0x010, "Companie de Radiocomunicatciones Moviles S.A." }, + { 0x722, 0x020, "Nextel Argentina srl" }, + { 0x722, 0x070, "Telefonica Communicationes Personales S.A." }, + { 0x722, 0x310, "CTI PCS S.A" }, + { 0x722, 0x320, "Compania de Telefonos del Interior Norte S.A." }, + { 0x722, 0x330, "Companie de Telefonos del Interior S.A." }, + { 0x722, 0x340, "Personal" }, /* ? */ + { 0x722, 0x341, "Telecom Personal S.A." }, + { 0x722, 0x350, "Hutchinson (PORT HABLE)" }, /* ? */ + { 0x283, -1, "Armenia" }, + { 0x283, 0x01f, "Beeline" }, + { 0x283, 0x05f, "VivaCell-MTS" }, + { 0x283, 0x10f, "Orange" }, + { 0x363, -1, "Aruba" }, + { 0x363, 0x01f, "SETAR" }, + { 0x363, 0x02f, "Digicel" }, /* ? */ + { 0x505, -1, "Australia" }, + { 0x505, 0x01f, "Telstra" }, + { 0x505, 0x02f, "Optus" }, + { 0x505, 0x03f, "Vodafone" }, + { 0x505, 0x04f, "Department of Defence" }, + { 0x505, 0x05f, "Ozitel" }, + { 0x505, 0x06f, "Hutchison 3G"}, + { 0x505, 0x07f, "Vodafone" }, + { 0x505, 0x08f, "One.Tel" }, + { 0x505, 0x09f, "Airnet" }, + { 0x505, 0x10f, "Norfolk Telecom" }, + { 0x505, 0x11f, "Telstra" }, + { 0x505, 0x12f, "3" }, + { 0x505, 0x13f, "Railcorp" }, + { 0x505, 0x14f, "AAPT" }, + { 0x505, 0x15f, "3GIS" }, + { 0x505, 0x16f, "Victorian Rail Track" }, + { 0x505, 0x17f, "Vivid Wireless Pty Ltd" }, + { 0x505, 0x18f, "Pactel International Pty Ltd" }, + { 0x505, 0x19f, "Lycamobile Pty Ltd" }, + { 0x505, 0x21f, "SOUL" }, /* ? */ + { 0x505, 0x24f, "Advanced Communications Technologies Pty. Ltd." }, + { 0x505, 0x38f, "Crazy John's" }, /* ? */ + { 0x505, 0x71f, "Telstra" }, + { 0x505, 0x72f, "Telstra" }, + { 0x505, 0x88f, "Localstar Holding Pty. Ltd." }, + { 0x505, 0x90f, "Optus" }, + { 0x505, 0x99f, "One.Tel" }, + { 0x232, -1, "Austria" }, + { 0x232, 0x01f, "A1" }, + { 0x232, 0x02f, "A1" }, + { 0x232, 0x03f, "T-Mobile" }, + { 0x232, 0x04f, "T-Mobile" }, + { 0x232, 0x05f, "Orange" }, + { 0x232, 0x06f, "Orange" }, + { 0x232, 0x07f, "T-Mobile (tele.ring)" }, + { 0x232, 0x09f, "Mobilkom Austria" }, + { 0x232, 0x10f, "Hutchison 3G Austria" }, + { 0x232, 0x11f, "Mobilkom Austria" }, + { 0x232, 0x12f, "Orange Austria" }, + { 0x232, 0x14f, "Hutchison 3G Austria" }, + { 0x232, 0x15f, "Barablu Mobile Austria" }, + { 0x232, 0x16f, "3" }, /* ? */ + { 0x232, 0x91f, "OBB - Infrastruktur Bau AG" }, + { 0x400, -1, "Azerbaijan" }, + { 0x400, 0x01f, "Azercell" }, + { 0x400, 0x02f, "Bakcell" }, + { 0x400, 0x03f, "Catel JV" }, + { 0x400, 0x04f, "Azerphone LLC" }, + { 0x364, -1, "Bahamas" }, + { 0x364, 0x390, "BaTelCo" }, + { 0x426, -1, "Bahrain" }, + { 0x426, 0x01f, "BHR Mobile Plus" }, + { 0x426, 0x02f, "zain BH" }, /* ? */ + { 0x426, 0x04f, "VIVA" }, /* ? */ + { 0x470, -1, "Bangladesh" }, + { 0x470, 0x01f, "Grameenphone" }, + { 0x470, 0x02f, "Aktel" }, + { 0x470, 0x03f, "Mobile 2000" }, + { 0x470, 0x04f, "TeleTalk" }, /* ? */ + { 0x470, 0x05f, "Citycell" }, /* ? */ + { 0x470, 0x06f, "Warid" }, /* ? */ + { 0x470, 0x07f, "WTBL" }, /* ? */ + { 0x342, -1, "Barbados" }, + { 0x342, 0x600, "Cable & Wireless (Barbados) Ltd." }, + { 0x342, 0x750, "Digicel" }, /* ? */ + { 0x342, 0x820, "Sunbeach Communications" }, + { 0x257, -1, "Belarus" }, + { 0x257, 0x01f, "MCD Velcom" }, + { 0x257, 0x02f, "MTS" }, + { 0x257, 0x04f, "life:)" }, /* ? */ + { 0x257, 0x03f, "DIALLOG" }, /* ? */ + { 0x206, -1, "Belgium" }, + { 0x206, 0x01f, "Proximus" }, + { 0x206, 0x02f, "SNCB GSM-R" }, /* ? */ + { 0x206, 0x10f, "Mobistar" }, + { 0x206, 0x20f, "BASE" }, + { 0x702, -1, "Belize" }, + { 0x702, 0x67f, "Belize Telemedia" }, + { 0x702, 0x68f, "International Telecommunications Ltd." }, + { 0x702, 0x00f, "Smart" }, /* ? */ + { 0x616, -1, "Benin" }, + { 0x616, 0x01f, "Libercom" }, + { 0x616, 0x02f, "Telecel" }, + { 0x616, 0x03f, "Spacetel Benin" }, + { 0x616, 0x04f, "BBCOM" }, /* ? */ + { 0x616, 0x05f, "Glo" }, /* ? */ + { 0x350, -1, "Bermuda" }, + { 0x350, 0x01f, "Digicel Bermuda" }, /* ? */ + { 0x350, 0x02f, "Mobility" }, /* ? */ + { 0x338, 0x050, "Digicel Bermuda" }, /* ? */ + { 0x310, 0x00f, "Cellular One" }, /* ? */ + { 0x402, -1, "Bhutan" }, + { 0x402, 0x11f, "Bhutan Telecom Ltd" }, + { 0x402, 0x77f, "B-Mobile" }, + { 0x736, -1, "Bolivia" }, + { 0x736, 0x01f, "Nuevatel" }, + { 0x736, 0x02f, "Entel" }, + { 0x736, 0x03f, "Telecel" }, + { 0x218, -1, "Bosnia and Herzegovina" }, + { 0x218, 0x03f, "HT-ERONET" }, + { 0x218, 0x05f, "MOBI'S" }, + { 0x218, 0x90f, "GSMBIH" }, + { 0x652, -1, "Botswana" }, + { 0x652, 0x01f, "Mascom" }, + { 0x652, 0x02f, "Orange" }, + { 0x652, 0x04f, "BTC Mobile" }, + { 0x724, -1, "Brazil" }, + { 0x724, 0x00f, "Telet" }, + { 0x724, 0x01f, "CRT Cellular" }, + { 0x724, 0x02f, "TIM" }, + { 0x724, 0x03f, "TIM" }, + { 0x724, 0x04f, "TIM" }, + { 0x724, 0x05f, "Claro" }, + { 0x724, 0x06f, "Vivo" }, + { 0x724, 0x07f, "CTBC Celular" }, + { 0x724, 0x08f, "TIM" }, + { 0x724, 0x10f, "Vivo" }, + { 0x724, 0x11f, "Vivo" }, + { 0x724, 0x15f, "Sercomtel" }, + { 0x724, 0x16f, "Oi / Brasil Telecom" }, + { 0x724, 0x23f, "Vivo" }, + { 0x724, 0x24f, "Oi / Amazonia Celular" }, + { 0x724, 0x31f, "Oi" }, + { 0x724, 0x32f, "CTBC Celular" }, + { 0x724, 0x33f, "CTBC Celular" }, + { 0x724, 0x34f, "CTBC Celular" }, + { 0x724, 0x35f, "TIM" }, + { 0x724, 0x37f, "aeiou" }, + { 0x724, 0x39f, "TIM" }, + { 0x724, 0x41f, "TIM" }, + { 0x724, 0x43f, "TIM" }, + { 0x724, 0x45f, "TIM" }, + { 0x724, 0x47f, "TIM" }, + { 0x724, 0x48f, "TIM" }, + { 0x724, 0x51f, "TIM" }, + { 0x724, 0x53f, "TIM" }, + { 0x724, 0x55f, "TIM" }, + { 0x724, 0x57f, "TIM" }, + { 0x724, 0x59f, "TIM" }, + { 0x724, 0x00f, "Nextel" }, + { 0x348, -1, "British Virgin Islands" }, + { 0x348, 0x170, "Cable & Wireless" }, + { 0x348, 0x370, "BVI Cable TV Ltd" }, + { 0x348, 0x570, "CCT Boatphone" }, + { 0x348, 0x770, "Digicel (BVI) Ltd" }, + { 0x528, -1, "Brunei" }, + { 0x528, 0x01f, "Jabatan Telekom" }, /* ? */ + { 0x528, 0x02f, "B-Mobile" }, /* ? */ + { 0x528, 0x11f, "DSTCom" }, + { 0x284, -1, "Bulgaria" }, + { 0x284, 0x01f, "M-Tel" }, + { 0x284, 0x03f, "Vivacom" }, /* ? */ + { 0x284, 0x05f, "GLOBUL" }, + { 0x613, -1, "Burkina Faso" }, + { 0x613, 0x01f, "Onatel" }, /* ? */ + { 0x613, 0x02f, "Celtel / Zain" }, + { 0x613, 0x03f, "Telecel Faso" }, + { 0x642, -1, "Burundi" }, + { 0x642, 0x01f, "Econet / Spacetel" }, + { 0x642, 0x02f, "Africell" }, + { 0x642, 0x03f, "Onamob" }, + { 0x642, 0x07f, "Lacell" }, + { 0x642, 0x08f, "Hits" }, + { 0x642, 0x82f, "U.COM / Onatel" }, + { 0x456, -1, "Cambodia" }, + { 0x456, 0x01f, "Mobitel" }, + { 0x456, 0x02f, "hello" }, + { 0x456, 0x03f, "S Telecom" }, + { 0x456, 0x04f, "Cadcomms / qb" }, + { 0x456, 0x05f, "Star-Cell" }, + { 0x456, 0x06f, "Smart" }, + { 0x456, 0x08f, "Viettel" }, + { 0x456, 0x18f, "Mfone" }, +// { 0x456, ?, "Excell" }, /* ? */ + { 0x456, 0x09f, "Beeline" }, /* ? */ + { 0x456, 0x08f, "Metfone" }, /* ? */ + { 0x624, -1, "Cameroon" }, + { 0x624, 0x01f, "MTN Cameroon" }, + { 0x624, 0x02f, "Orange" }, + { 0x302, -1, "Canada" }, + { 0x302, 0x220, "Telus" }, + { 0x302, 0x221, "Telus" }, + { 0x302, 0x290, "Airtel Wireless" }, + { 0x302, 0x350, "FIRST" }, + { 0x302, 0x360, "MiKe" }, + { 0x302, 0x361, "Telus" }, + { 0x302, 0x370, "Fido" }, + { 0x302, 0x380, "DMTS" }, + { 0x302, 0x490, "WIND Mobile" }, + { 0x302, 0x500, "Videotron" }, + { 0x302, 0x510, "Videotron" }, + { 0x302, 0x610, "Bell" }, + { 0x302, 0x620, "ICE Wireless" }, + { 0x302, 0x640, "Bell" }, + { 0x302, 0x651, "Bell" }, + { 0x302, 0x652, "BC Tel Mobility (Telus)" }, + { 0x302, 0x653, "Telus" }, + { 0x302, 0x655, "MTS" }, + { 0x302, 0x656, "TBay" }, + { 0x302, 0x657, "Telus" }, + { 0x302, 0x680, "SaskTel" }, + { 0x302, 0x701, "MB Tel Mobility" }, + { 0x302, 0x702, "MT&T Mobility (Aliant)" }, + { 0x302, 0x703, "New Tel Mobility (Aliant)" }, + { 0x302, 0x710, "Globalstar" }, + { 0x302, 0x720, "Rogers Wireless" }, + { 0x302, 0x780, "SaskTel" }, + { 0x302, 0x880, "Bell / Telus" }, + { 0x625, -1, "Cape Verde" }, + { 0x625, 0x01f, "CVMOVEL" }, + { 0x625, 0x02f, "T+" }, + { 0x346, -1, "Cayman Islands" }, + { 0x346, 0x140, "Cable & Wireless" }, + { 0x338, 0x050, "Digicel" }, /* ? */ + { 0x623, -1, "Central African Republic" }, + { 0x623, 0x01f, "CTP" }, + { 0x623, 0x02f, "TC" }, + { 0x623, 0x03f, "Celca / Socatel / Orange" }, + { 0x623, 0x04f, "Nationlink" }, /* ? */ + { 0x622, -1, "Chad" }, + { 0x622, 0x01f, "Celtel / Zain" }, + { 0x622, 0x02f, "Tchad Mobile" }, + { 0x622, 0x03f, "TIGO - Millicom" }, /* ? */ + { 0x622, 0x02f, "TAWALI" }, /* ? */ + { 0x622, 0x04f, "Salam" }, /* ? */ + { 0x730, -1, "Chile" }, + { 0x730, 0x01f, "Entel" }, + { 0x730, 0x02f, "movistar" }, + { 0x730, 0x03f, "Smartcom / Claro" }, + { 0x730, 0x04f, "Centennial Cayman Corp / Nextel" }, + { 0x730, 0x05f, "Multikom S.A." }, + { 0x730, 0x06f, "Blue Two Chile S.A." }, + { 0x730, 0x07f, "Telefonica" }, + { 0x730, 0x10f, "Entel" }, + { 0x730, 0x99f, "WILL" }, /* ? */ + { 0x460, -1, "China" }, + { 0x460, 0x00f, "China Mobile" }, + { 0x460, 0x01f, "China Unicom" }, + { 0x460, 0x02f, "China Mobile" }, + { 0x460, 0x03f, "China Unicom CMDA" }, + { 0x460, 0x04f, "China Satellite Global Star Network" }, + { 0x460, 0x05f, "China Telecom" }, /* ? */ + { 0x460, 0x06f, "China Unicom" }, /* ? */ + { 0x460, 0x20f, "China TIETONG" }, /* ? */ + { 0x732, -1, "Colombia" }, + { 0x732, 0x001, "Colombia Telecomunicaciones S.A." }, + { 0x732, 0x002, "Edatel" }, + { 0x732, 0x020, "Emtelsa" }, + { 0x732, 0x099, "Emcali" }, + { 0x732, 0x101, "Comcel" }, + { 0x732, 0x102, "Bellsouth / movistar" }, + { 0x732, 0x103, "Colombia Movil / Tigo" }, + { 0x732, 0x111, "Colombia Movil / Tigo" }, + { 0x732, 0x123, "movistar" }, + { 0x732, 0x12f, "movistar" }, /* ? */ + { 0x732, 0x130, "Avantel" }, + { 0x654, -1, "Comoros" }, + { 0x654, 0x01f, "HURI - SNPT" }, + { 0x629, -1, "Republic of the Congo" }, + { 0x629, 0x01f, "Celtel / Zain" }, + { 0x629, 0x10f, "Libertis Telecom" }, +// { 0x629, ?, "Warid Telecom" }, + { 0x548, -1, "Cook Islands" }, + { 0x548, 0x01f, "Telecom Cook" }, + { 0x712, -1, "Costa Rica" }, + { 0x712, 0x01f, "ICE" }, + { 0x712, 0x02f, "ICE" }, /* ? */ + { 0x712, 0x03f, "ICE" }, /* ? */ + { 0x219, -1, "Croatia" }, + { 0x219, 0x01f, "T-Mobile" }, + { 0x219, 0x02f, "Tele2" }, + { 0x219, 0x10f, "VIPnet" }, + { 0x368, -1, "Cuba" }, + { 0x368, 0x01f, "ETECSA" }, + { 0x280, -1, "Cyprus" }, + { 0x280, 0x01f, "Cytamobile-Vodafone" }, + { 0x280, 0x10f, "Scanacom / MTN" }, + { 0x230, -1, "Czech Republic" }, + { 0x230, 0x01f, "T-Mobile" }, + { 0x230, 0x02f, "O2" }, + { 0x230, 0x03f, "Vodafone" }, + { 0x230, 0x04f, "Mobilkom / U:fon" }, + { 0x230, 0x98f, "SZDC s.o." }, + { 0x230, 0x99f, "Vodafone" }, + { 0x630, -1, "Democratic Republic of the Congo" }, + { 0x630, 0x01f, "Vodacom" }, + { 0x630, 0x02f, "Zain" }, /* ? */ + { 0x630, 0x04f, "Cellco" }, /* ? */ + { 0x630, 0x05f, "Supercell" }, + { 0x630, 0x86f, "CCT" }, + { 0x630, 0x89f, "SAIT Telecom" }, /* ? */ +// { 0x630, ?, "Africell" }, + { 0x238, -1, "Denmark" }, + { 0x238, 0x01f, "TDC" }, + { 0x238, 0x02f, "Sonofon / Telenor" }, + { 0x238, 0x03f, "MIGway A/S" }, + { 0x238, 0x05f, "ApS KBUS" }, + { 0x238, 0x06f, "Hi3G" }, + { 0x238, 0x07f, "Lycamobile / Barablu Mobile" }, + { 0x238, 0x09f, "Dansk Beredskabskommunikation A/S" }, /* ? */ + { 0x238, 0x10f, "TDC" }, + { 0x238, 0x11f, "Dansk Beredskabskommunikation A/S" }, /* ? */ + { 0x238, 0x12f, "Lycamobile Denmark Ltd" }, + { 0x238, 0x20f, "Telia" }, + { 0x238, 0x30f, "Telia" }, + { 0x238, 0x40f, "Ericsson Danmark A/S" }, /* ? */ + { 0x238, 0x77f, "Tele2 / Telenor" }, + { 0x638, -1, "Djibouti" }, + { 0x638, 0x01f, "Evatis" }, + { 0x366, -1, "Dominica" }, + { 0x366, 0x020, "Digicel" }, /* ? */ + { 0x366, 0x110, "Cable & Wireless" }, /* ? */ + { 0x370, -1, "Dominican Republic" }, + { 0x370, 0x01f, "Orange" }, + { 0x370, 0x02f, "Verizon / Claro" }, + { 0x370, 0x03f, "Tricom" }, + { 0x370, 0x04f, "CentennialDominicana / Viva" }, + { 0x514, -1, "East Timor" }, + { 0x514, 0x02f, "Timor Telecom" }, /* ? */ + { 0x740, -1, "Ecuador" }, + { 0x740, 0x00f, "Otecel / Bellsouth / Movistar" }, + { 0x740, 0x01f, "Porta GSM" }, + { 0x740, 0x02f, "Telecsa / Alegro" }, + { 0x602, -1, "Egypt" }, + { 0x602, 0x01f, "Mobinil" }, + { 0x602, 0x02f, "Vodafone" }, + { 0x602, 0x03f, "Etisalat" }, + { 0x706, -1, "El Salvador" }, + { 0x706, 0x01f, "CTE Telecom Personal" }, + { 0x706, 0x02f, "digicel" }, + { 0x706, 0x03f, "Telemovil EL Salvador" }, + { 0x706, 0x04f, "movistar" }, /* ? */ + { 0x706, 0x10f, "Claro" }, /* ? */ + { 0x627, -1, "Equatorial Guinea" }, + { 0x627, 0x01f, "Orange GQ" }, + { 0x627, 0x03f, "Hits GQ" }, + { 0x657, -1, "Eritrea" }, + { 0x657, 0x01f, "Eritel" }, /* ? */ + { 0x248, -1, "Estonia" }, + { 0x248, 0x01f, "EMT" }, + { 0x248, 0x02f, "RLE / Elisa" }, + { 0x248, 0x03f, "Tele 2" }, + { 0x248, 0x04f, "OY Top Connect" }, + { 0x248, 0x05f, "AS Bravocom Mobiil" }, + { 0x248, 0x06f, "Pro Group Holding / ViaTel" }, + { 0x248, 0x07f, "Televorgu AS" }, + { 0x248, 0x71f, "Siseministeerium" }, + { 0x636, -1, "Ethiopia" }, + { 0x636, 0x01f, "ETMTN" }, + { 0x750, -1, "Falkland Islands (Malvinas)" }, + { 0x750, 0x001, "Touch" }, + { 0x288, -1, "Faroe Islands" }, + { 0x288, 0x01f, "Faroese Telecom" }, + { 0x288, 0x02f, "Kall / Vodafone" }, + { 0x274, 0x02f, "P/F Kall" }, + { 0x542, -1, "Fiji" }, + { 0x542, 0x01f, "Vodafone" }, + { 0x542, 0x02f, "Digicel" }, + { 0x542, 0x03f, "Telecom Fiji" }, + { 0x244, -1, "Finland" }, + { 0x244, 0x03f, "DNA" }, /* ? */ + { 0x244, 0x04f, "Finnet" }, + { 0x244, 0x05f, "Elisa" }, + { 0x244, 0x07f, "Nokia" }, + { 0x244, 0x08f, "Unknown" }, + { 0x244, 0x09f, "Finnet Group" }, + { 0x244, 0x10f, "TDC Oy" }, + { 0x244, 0x12f, "Finnet Networks / DNA" }, + { 0x244, 0x14f, "AMT" }, + { 0x244, 0x16f, "Oy Finland Tele2" }, + { 0x244, 0x21f, "Saunalahti" }, + { 0x244, 0x29f, "Scnl Truphone" }, /* ? */ + { 0x244, 0x91f, "Sonera" }, + { 0x208, -1, "France" }, + { 0x208, 0x00f, "Orange" }, /* ? */ + { 0x208, 0x01f, "Orange" }, + { 0x208, 0x02f, "Orange" }, + { 0x208, 0x05f, "Globalstar Europe" }, + { 0x208, 0x06f, "Globalstar Europe" }, + { 0x208, 0x07f, "Globalstar Europe" }, + { 0x208, 0x10f, "SFR" }, + { 0x208, 0x11f, "SFR" }, + { 0x208, 0x13f, "SFR" }, /* ? */ + { 0x208, 0x20f, "Bouygues" }, + { 0x208, 0x21f, "Bouygues" }, + { 0x208, 0x22f, "Transatel Mobile" }, + { 0x208, 0x88f, "Bouygues" }, + { 0x628, -1, "Gabon" }, + { 0x628, 0x01f, "Libertis" }, + { 0x628, 0x02f, "Moov (Telecel) Gabon S.A." }, + { 0x628, 0x03f, "Celtel / Zain" }, + { 0x628, 0x04f, "USAN Gabon" }, + { 0x607, -1, "Gambia" }, + { 0x607, 0x01f, "Gamcel" }, + { 0x607, 0x02f, "Africel" }, + { 0x607, 0x03f, "Comium" }, + { 0x607, 0x04f, "QCell" }, /* ? */ + { 0x282, -1, "Georgia" }, + { 0x282, 0x01f, "Geocell" }, + { 0x282, 0x02f, "MagtiCom" }, + { 0x282, 0x03f, "Iberiatel" }, + { 0x282, 0x04f, "Beeline" }, + { 0x282, 0x05f, "Silknet JSC" }, + { 0x289, 0x67f, "Aquafon" }, /* ? */ + { 0x289, 0x88f, "A-Mobile" }, /* ? */ + { 0x262, -1, "Germany" }, + { 0x262, 0x01f, "T-Mobile" }, + { 0x262, 0x02f, "Vodafone" }, + { 0x262, 0x03f, "E-Plus" }, + { 0x262, 0x04f, "Vodafone" }, + { 0x262, 0x05f, "E-Plus" }, + { 0x262, 0x06f, "T-Mobile" }, + { 0x262, 0x07f, "O2" }, + { 0x262, 0x08f, "O2" }, + { 0x262, 0x09f, "Vodafone" }, + { 0x262, 0x10f, "DB Systel GSM-R" }, + { 0x262, 0x11f, "O2" }, + { 0x262, 0x12f, "Dolphin Telecom" }, + { 0x262, 0x13f, "Mobilcom Multimedia" }, + { 0x262, 0x14f, "Group 3G UMTS" }, + { 0x262, 0x15f, "Airdata" }, + { 0x262, 0x16f, "Vistream" }, /* ? */ + { 0x262, 0x42f, "OpenBSC" }, /* ? */ + { 0x262, 0x60f, "DB Telematik" }, + { 0x262, 0x76f, "Siemens AG" }, + { 0x262, 0x77f, "E-Plus" }, + { 0x262, 0x901, "Debitel" }, /* ? */ + { 0x620, -1, "Ghana" }, + { 0x620, 0x01f, "Spacefon / MTN" }, + { 0x620, 0x02f, "Ghana Telecom Mobile / Vodafone" }, + { 0x620, 0x03f, "Mobiltel / tiGO" }, + { 0x620, 0x04f, "Kasapa / Hutchison Telecom" }, + { 0x620, 0x06f, "Zain" }, /* ? */ + { 0x620, 0x10f, "Netafriques" }, /* ? */ + { 0x266, -1, "Gibraltar" }, + { 0x266, 0x01f, "GibTel" }, + { 0x266, 0x06f, "CTS Mobile" }, + { 0x266, 0x09f, "Cloud9 Mobile Communications" }, + { 0x202, -1, "Greece" }, + { 0x202, 0x01f, "Cosmote" }, + { 0x202, 0x05f, "Vodafone" }, + { 0x202, 0x09f, "Infoquest / Wind" }, + { 0x202, 0x10f, "Wind" }, + { 0x290, -1, "Greenland" }, + { 0x290, 0x01f, "TELE Greenland A/S" }, + { 0x352, -1, "Grenada" }, + { 0x352, 0x030, "Digicel" }, + { 0x352, 0x110, "Cable & Wireless" }, + { 0x340, -1, "Guadeloupe" }, + { 0x340, 0x01f, "Orange" }, + { 0x340, 0x02f, "Outremer" }, + { 0x340, 0x03f, "Telcell" }, + { 0x340, 0x08f, "MIO GSM" }, + { 0x340, 0x10f, "Guadeloupe Telephone Mobile" }, + { 0x340, 0x20f, "Digicel" }, + { 0x310, -1, "United States of America" }, + { 0x310, 0x010, "Verizon Wireless" }, + { 0x310, 0x012, "Verizon Wireless" }, + { 0x310, 0x013, "Verizon Wireless" }, + { 0x310, 0x016, "Cricket Communications" }, + { 0x310, 0x017, "North Sight Communications Inc." }, + { 0x310, 0x020, "Union Telephone Company" }, + { 0x310, 0x030, "Centennial Communications" }, + { 0x310, 0x035, "ETEX Communications dba ETEX Wireless" }, + { 0x310, 0x040, "MTA Communications dba MTA Wireless" }, + { 0x310, 0x050, "ACS Wireless Inc." }, + { 0x310, 0x060, "Consolidated Telecom" }, + { 0x310, 0x070, "Cingular Wireless" }, + { 0x310, 0x080, "Corr Wireless Communications LLC" }, + { 0x310, 0x090, "Cingular Wireless" }, + { 0x310, 0x100, "New Mexicu RSA 4 East Ltd. Partnership" }, + { 0x310, 0x110, "Pacific Telecom Inc." }, + { 0x310, 0x130, "Carolina West Wireless" }, + { 0x310, 0x140, "GTA Wireless LLC" }, + { 0x310, 0x150, "Cingular Wireless" }, + { 0x310, 0x160, "T-Mobile USA" }, + { 0x310, 0x170, "Cingular Wireless" }, + { 0x310, 0x180, "West Central Wireless" }, + { 0x310, 0x190, "Alaska Wireless Communications LLC" }, + { 0x310, 0x200, "T-Mobile USA" }, + { 0x310, 0x210, "T-Mobile USA" }, + { 0x310, 0x220, "T-Mobile USA" }, + { 0x310, 0x230, "T-Mobile USA" }, + { 0x310, 0x240, "T-Mobile USA" }, + { 0x310, 0x250, "T-Mobile USA" }, + { 0x310, 0x260, "T-Mobile USA" }, + { 0x310, 0x270, "T-Mobile USA" }, + { 0x310, 0x280, "Contennial Puerto Rio License Corp." }, + { 0x310, 0x290, "Nep Cellcorp Inc." }, + { 0x310, 0x300, "Blanca Telephone Company" }, + { 0x310, 0x310, "T-Mobile USA" }, + { 0x310, 0x320, "Simth Bagley Inc, dba Cellular One" }, + { 0x310, 0x340, "High Plains Midwest LLC, dba Wetlink Communications" }, + { 0x310, 0x350, "Mohave Cellular L.P." }, + { 0x310, 0x360, "Cellular Network Partnership dba Pioneer Cellular" }, + { 0x310, 0x370, "Guamcell Cellular and Paging" }, + { 0x310, 0x380, "New Cingular Wireless PCS, LLC" }, + { 0x310, 0x390, "TX-11 Acquisition LLC" }, + { 0x310, 0x400, "Wave Runner LLC" }, + { 0x310, 0x410, "Cingular Wireless" }, + { 0x310, 0x420, "Cincinnati Bell Wireless LLC" }, + { 0x310, 0x430, "Alaska Digital LLC" }, + { 0x310, 0x440, "Numerex Corp" }, + { 0x310, 0x450, "North East Cellular Inc" }, + { 0x310, 0x460, "TMP Corporation" }, + { 0x310, 0x470, "nTELOS Communications Inc" }, + { 0x310, 0x480, "Choice Phone LLC" }, + { 0x310, 0x490, "T-Mobile USA" }, + { 0x310, 0x500, "Public Service Cellular, Inc." }, + { 0x310, 0x520, "Transactions Network Services" }, + { 0x310, 0x530, "Iowa Wireless Services LLC" }, + { 0x310, 0x540, "Oklahoma Western Telephone Company" }, + { 0x310, 0x550, "Wireless Solutions International" }, + { 0x310, 0x560, "Cingular Wireless" }, + { 0x310, 0x570, "MTPCS LLC" }, + { 0x310, 0x580, "Inland Celluar Telephone Company" }, + { 0x310, 0x590, "Western Wireless Corporation" }, + { 0x310, 0x600, "New Cell Inc. dba Cellcom" }, + { 0x310, 0x610, "Elkhart Telephone Co. Inc. dba Epic Touch Co." }, + { 0x310, 0x620, "Coleman County Telecommunications Inc. (Trans Texas PCS)" }, + { 0x310, 0x640, "Airadigm Communications" }, + { 0x310, 0x650, "Jasper Wireless Inc." }, + { 0x310, 0x660, "T-Mobile USA" }, + { 0x310, 0x670, "AT&T Mobility Vanguard Services" }, + { 0x310, 0x680, "Cingular Wireless" }, + { 0x310, 0x690, "Keystane Wireless LLC" }, + { 0x310, 0x700, "Cross Valiant Cellular Partnership" }, + { 0x310, 0x710, "Arctic Slope Telephone Association Cooperative" }, + { 0x310, 0x720, "Wireless Solutions International Inc." }, + { 0x310, 0x730, "US Cellular" }, + { 0x310, 0x740, "Convey Communications Inc" }, + { 0x310, 0x750, "East Kentucky Network LLC dba Appalachian Wireless" }, + { 0x310, 0x760, "Lynch 3G Communcations Corporation" }, + { 0x310, 0x770, "Iowa Wireless Services LLC dba I Wireless" }, + { 0x310, 0x780, "Connect Net Inc" }, + { 0x310, 0x790, "PinPoint Communications Inc."}, + { 0x310, 0x800, "T-Mobile USA" }, + { 0x310, 0x810, "LCFR LLC" }, + { 0x310, 0x820, "South Canaan Cellular Communications Co. LP" }, + { 0x310, 0x830, "Caprock Cellular Ltd. Partnership" }, + { 0x310, 0x840, "Telecom North America Mobile Inc" }, + { 0x310, 0x850, "Aeris Communications Inc." }, + { 0x310, 0x860, "TX RSA 15B2, LP dba Five Star Wireless" }, + { 0x310, 0x870, "Kaplan Telephone Company, Inc" }, + { 0x310, 0x890, "Rural Cellular Corporation" }, + { 0x310, 0x900, "Cable & Communications Corporation dba Mid-Rivers Wireless" }, + { 0x310, 0x910, "Verizon Wireless" }, + { 0x310, 0x930, "Copper Valley Wireless" }, + { 0x310, 0x940, "Iris Wireless LLC" }, + { 0x310, 0x950, "Texas RSA 1 dba XIT Wireless" }, + { 0x310, 0x960, "UBET Wireless" }, + { 0x310, 0x970, "Globalstar USA" }, + { 0x310, 0x980, "Texas RSA 7B3 dba Peoples Wireless Services" }, + { 0x310, 0x99, "Worldcall Interconnect" }, + + { 0x704, -1, "Guatemala" }, + { 0x704, 0x01f, "Claro" }, + { 0x704, 0x02f, "Comcel / Tigo" }, + { 0x704, 0x03f, "movistar" }, +// { 0x704, ?, "digicel" }, + { 0x234, -1, "Guernsey" }, + { 0x234, 0x55f, "Sure Mobile" }, + { 0x234, 0x50f, "Wave Telecom" }, + { 0x234, 0x03f, "Airtel Vodafone" }, + { 0x611, -1, "Guinea" }, + { 0x611, 0x01f, "Orange / Spacetel" }, + { 0x611, 0x02f, "Sotelgui / Lagui" }, + { 0x611, 0x03f, "Telecel Guinee" }, /* ? */ + { 0x611, 0x04f, "MTN" }, /* ? */ + { 0x611, 0x05f, "Cellcom Guinee" }, + { 0x632, -1, "Guinea-Bissau" }, + { 0x632, 0x01f, "Guinetel" }, + { 0x632, 0x02f, "Spacetel / Areeba" }, + { 0x632, 0x03f, "Orange" }, + { 0x738, -1, "Guyana" }, + { 0x738, 0x01f, "Digicel" }, + { 0x738, 0x02f, "GT&T Cellink Plus" }, /* ? */ + { 0x372, -1, "Haiti" }, + { 0x372, 0x01f, "Comcel / Voila" }, + { 0x338, 0x050, "Digicel" }, + { 0x338, 0x03f, "Rectel" }, + { 0x708, -1, "Honduras" }, + { 0x708, 0x01f, "Claro" }, + { 0x708, 0x02f, "Celtel / Tigo" }, + { 0x708, 0x30f, "Hondutel" }, /* ? */ + { 0x708, 0x40f, "DIGICEL" }, + { 0x454, -1, "Hong Kong" }, + { 0x454, 0x00f, "1O1O and One2Free" }, + { 0x454, 0x01f, "CITIC Telecom 1616" }, + { 0x454, 0x02f, "CSL Limited" }, + { 0x454, 0x03f, "3 (3G)" }, + { 0x454, 0x04f, "3 DualBand (2G)" }, + { 0x454, 0x05f, "3 CDMA" }, + { 0x454, 0x06f, "SmarTone-Vodafone" }, + { 0x454, 0x07f, "China Unicom (Hong Kong) Limited" }, + { 0x454, 0x08f, "Trident" }, + { 0x454, 0x09f, "China Motion Telecom" }, + { 0x454, 0x10f, "New World Mobility" }, + { 0x454, 0x11f, "China-Hongkong Telecom" }, + { 0x454, 0x12f, "CMCC HK" }, + { 0x454, 0x14f, "Hutchison Telecom" }, + { 0x454, 0x15f, "SmarTone Mobile Communications Limited" }, + { 0x454, 0x16f, "PCCW Mobile (2G)" }, + { 0x454, 0x17f, "SmarTone Mobile Communications Limited" }, + { 0x454, 0x18f, "CSL Limited" }, + { 0x454, 0x19f, "Sunday3G" }, + { 0x454, 0x19f, "PCCW Mobile (3G)" }, + { 0x454, 0x29f, "PCCW Mobile (CDMA)" }, + { 0x216, -1, "Hungary" }, + { 0x216, 0x01f, "Pannon GSM" }, + { 0x216, 0x30f, "Westel 900" }, + { 0x216, 0x70f, "Vodafone" }, + { 0x274, -1, "Iceland" }, + { 0x274, 0x01f, "Siminn" }, + { 0x274, 0x02f, "Vodafone" }, + { 0x274, 0x03f, "Vodafone" }, + { 0x274, 0x04f, "IMC Viking" }, + { 0x274, 0x06f, "N?ll n?u ehf" }, /* ? */ + { 0x274, 0x07f, "IceCell" }, + { 0x274, 0x08f, "On-waves" }, + { 0x274, 0x11f, "Nova" }, + /* FIXME: update the list from here below */ + { 0x404, -1, "India" }, + { 0x404, 0x01f, "Vodafone IN" }, + { 0x404, 0x02f, "AirTel" }, + { 0x404, 0x04f, "IDEA" }, + { 0x404, 0x05f, "Vodafone IN" }, + { 0x404, 0x07f, "IDEA" }, + { 0x404, 0x09f, "Reliance" }, + { 0x404, 0x10f, "AirTel" }, + { 0x404, 0x11f, "Vodafone IN" }, + { 0x404, 0x12f, "IDEA" }, + { 0x404, 0x13f, "Vodafone IN" }, + { 0x404, 0x14f, "IDEA" }, + { 0x404, 0x15f, "Vodafone IN" }, + { 0x404, 0x17f, "AIRCEL" }, + { 0x404, 0x19f, "IDEA" }, + { 0x404, 0x20f, "Vodafone IN" }, + { 0x404, 0x21f, "Loop Mobile" }, + { 0x404, 0x22f, "IDEA" }, + { 0x404, 0x24f, "IDEA" }, + { 0x404, 0x27f, "Vodafone IN" }, + { 0x404, 0x28f, "AIRCEL" }, + { 0x404, 0x29f, "AIRCEL" }, + { 0x404, 0x30f, "Vodafone IN" }, + { 0x404, 0x31f, "AirTel" }, + { 0x404, 0x34f, "CellOne" }, + { 0x404, 0x36f, "Reliance" }, + { 0x404, 0x37f, "Aircel" }, + { 0x404, 0x38f, "CellOne" }, + { 0x404, 0x41f, "Aircel" }, + { 0x404, 0x42f, "Aircel" }, + { 0x404, 0x44f, "IDEA" }, + { 0x404, 0x45f, "Airtel" }, + { 0x404, 0x51f, "CellOne" }, + { 0x404, 0x52f, "Reliance" }, + { 0x404, 0x53f, "CellOne" }, + { 0x404, 0x54f, "CellOne" }, + { 0x404, 0x55f, "CellOne" }, + { 0x404, 0x56f, "IDEA" }, + { 0x404, 0x57f, "CellOne" }, + { 0x404, 0x58f, "CellOne" }, + { 0x404, 0x59f, "CellOne" }, + { 0x404, 0x60f, "Vodafone IN" }, + { 0x404, 0x62f, "CellOne" }, + { 0x404, 0x64f, "CellOne" }, + { 0x404, 0x66f, "CellOne" }, + { 0x404, 0x67f, "Reliance GSM" }, + { 0x404, 0x68f, "DOLPHIN" }, + { 0x404, 0x69f, "DOLPHIN" }, + { 0x404, 0x72f, "CellOne" }, + { 0x404, 0x74f, "CellOne" }, + { 0x404, 0x76f, "CellOne" }, + { 0x404, 0x78f, "Idea Cellular Ltd" }, + { 0x404, 0x80f, "BSNL MOBILE" }, + { 0x404, 0x81f, "CellOne" }, + { 0x404, 0x82f, "Idea" }, + { 0x404, 0x83f, "Reliance Smart GSM" }, + { 0x404, 0x84f, "Vodafone IN" }, + { 0x404, 0x85f, "Reliance" }, + { 0x404, 0x86f, "Vodafone IN" }, + { 0x404, 0x90f, "AirTel" }, + { 0x404, 0x91f, "AIRCEL" }, + { 0x404, 0x92f, "AirTel" }, + { 0x404, 0x93f, "AirTel" }, + { 0x404, 0x96f, "AirTel" }, + { 0x405, 0x05f, "Reliance" }, + { 0x405, 0x10f, "Reliance" }, + { 0x405, 0x13f, "Reliance" }, + { 0x405, 0x025, "TATA DOCOMO" }, + { 0x405, 0x026, "TATA DOCOMO" }, + { 0x405, 0x027, "TATA DOCOMO" }, + { 0x405, 0x029, "TATA DOCOMO" }, + { 0x405, 0x030, "TATA DOCOMO" }, + { 0x405, 0x031, "TATA DOCOMO" }, + { 0x405, 0x032, "TATA DOCOMO" }, + { 0x405, 0x034, "TATA DOCOMO" }, + { 0x405, 0x035, "TATA DOCOMO" }, + { 0x405, 0x036, "TATA DOCOMO" }, + { 0x405, 0x037, "TATA DOCOMO" }, + { 0x405, 0x038, "TATA DOCOMO" }, + { 0x405, 0x039, "TATA DOCOMO" }, + { 0x405, 0x041, "TATA DOCOMO" }, + { 0x405, 0x042, "TATA DOCOMO" }, + { 0x405, 0x043, "TATA DOCOMO" }, + { 0x405, 0x044, "TATA DOCOMO" }, + { 0x405, 0x045, "TATA DOCOMO" }, + { 0x405, 0x046, "TATA DOCOMO" }, + { 0x405, 0x047, "TATA DOCOMO" }, + { 0x405, 0x51f, "AirTel" }, + { 0x405, 0x52f, "AirTel" }, + { 0x405, 0x54f, "AirTel" }, + { 0x405, 0x56f, "AirTel" }, + { 0x405, 0x66f, "Vodafone IN" }, + { 0x405, 0x70f, "IDEA" }, + { 0x405, 0x750, "Vodafone IN" }, + { 0x405, 0x751, "Vodafone IN" }, + { 0x405, 0x752, "Vodafone IN" }, + { 0x405, 0x753, "Vodafone IN" }, + { 0x405, 0x754, "Vodafone IN" }, + { 0x405, 0x755, "Vodafone IN" }, + { 0x405, 0x756, "Vodafone IN" }, + { 0x405, 0x799, "IDEA" }, + { 0x405, 0x800, "AIRCEL" }, + { 0x405, 0x801, "AIRCEL" }, + { 0x405, 0x802, "AIRCEL" }, + { 0x405, 0x803, "AIRCEL" }, + { 0x405, 0x804, "AIRCEL" }, + { 0x405, 0x805, "AIRCEL" }, + { 0x405, 0x806, "AIRCEL" }, + { 0x405, 0x807, "AIRCEL" }, + { 0x405, 0x808, "AIRCEL" }, + { 0x405, 0x809, "AIRCEL" }, + { 0x405, 0x810, "AIRCEL" }, + { 0x405, 0x811, "AIRCEL" }, + { 0x405, 0x812, "AIRCEL" }, + { 0x405, 0x819, "Uninor" }, + { 0x405, 0x818, "[Uninor]" }, + { 0x405, 0x820, "Uninor" }, + { 0x405, 0x821, "Uninor" }, + { 0x405, 0x822, "Uninor" }, + { 0x405, 0x880, "Uninor" }, + { 0x405, 0x824, "Videocon Datacom" }, + { 0x405, 0x834, "Videocon Datacom" }, + { 0x405, 0x844, "Uninor" }, + { 0x405, 0x845, "IDEA" }, + { 0x405, 0x848, "IDEA" }, + { 0x405, 0x850, "IDEA" }, + { 0x405, 0x855, "Loop Mobile" }, + { 0x405, 0x864, "Loop Mobile" }, + { 0x405, 0x865, "Loop Mobile" }, + { 0x405, 0x875, "Uninor" }, + { 0x405, 0x881, "S Tel" }, + { 0x405, 0x912, "Etisalat DB" }, + { 0x405, 0x913, "Etisalat DB" }, + { 0x405, 0x917, "Etisalat DB" }, + { 0x405, 0x929, "Uninor" }, + { 0x510, -1, "Indonesia" }, + { 0x510, 0x00f, "PSN" }, + { 0x510, 0x01f, "INDOSAT" }, + { 0x510, 0x03f, "StarOne" }, + { 0x510, 0x07f, "TelkomFlexi" }, + { 0x510, 0x08f, "AXIS" }, + { 0x510, 0x09f, "SMART" }, + { 0x510, 0x10f, "Telkomsel" }, + { 0x510, 0x11f, "XL" }, + { 0x510, 0x20f, "TELKOMMobile" }, + { 0x510, 0x21f, "IM3" }, + { 0x510, 0x27f, "Ceria" }, + { 0x510, 0x28f, "Fren/Hepi" }, + { 0x510, 0x89f, "3" }, + { 0x510, 0x99f, "Esia " }, + { 0x432, -1, "Iran" }, + { 0x432, 0x11f, "MCI" }, + { 0x432, 0x14f, "TKC" }, + { 0x432, 0x19f, "MTCE" }, + { 0x432, 0x32f, "Taliya" }, + { 0x432, 0x35f, "Irancell" }, + { 0x418, -1, "Iraq" }, + { 0x418, 0x20f, "Zain IQ" }, + { 0x418, 0x30f, "Zain IQ" }, + { 0x418, 0x05f, "Asia Cell" }, + { 0x418, 0x40f, "Korek" }, + { 0x418, 0x08f, "SanaTel" }, +// { 0x418, ?, "IRAQNA" }, + { 0x272, -1, "Ireland" }, + { 0x272, 0x01f, "Vodafone" }, + { 0x272, 0x02f, "O2" }, + { 0x272, 0x03f, "Meteor" }, + { 0x272, 0x04f, "Access Telecom" }, + { 0x272, 0x05f, "3" }, + { 0x272, 0x07f, "Eircom" }, + { 0x272, 0x09f, "Clever Communications" }, + { 0x234, -1, "Isle of Man" }, + { 0x234, 0x58f, "Pronto GSM" }, + { 0x425, -1, "Israel" }, + { 0x425, 0x01f, "Orange" }, + { 0x425, 0x02f, "Cellcom" }, + { 0x425, 0x03f, "Pelephone" }, + { 0x425, 0x77f, "Mirs" }, + { 0x222, -1, "Italy" }, + { 0x222, 0x01f, "TIM" }, + { 0x222, 0x02f, "Elsacom" }, + { 0x222, 0x10f, "Vodafone" }, + { 0x222, 0x30f, "RFI" }, + { 0x222, 0x77f, "IPSE 2000" }, + { 0x222, 0x88f, "Wind" }, + { 0x222, 0x98f, "Blu" }, + { 0x222, 0x99f, "3 Italia" }, + { 0x612, -1, "Ivory Coast" }, + { 0x612, 0x01f, "Cora de Comstar" }, + { 0x612, 0x02f, "Moov" }, + { 0x612, 0x03f, "Orange" }, + { 0x612, 0x04f, "KoZ" }, + { 0x612, 0x05f, "MTN" }, + { 0x612, 0x06f, "ORICEL" }, + { 0x338, -1, "Jamaica" }, + { 0x338, 0x020, "LIME (formerly known as Cable & Wireless)" }, + { 0x338, 0x050, "Digicel" }, + { 0x338, 0x070, "Claro" }, + { 0x338, 0x180, "LIME (formerly known as Cable & Wireless)" }, + { 0x440, -1, "Japan" }, + { 0x440, 0x00f, "eMobile" }, + { 0x440, 0x01f, "NTT docomo" }, + { 0x440, 0x02f, "NTT docomo" }, + { 0x440, 0x03f, "NTT docomo" }, + { 0x440, 0x04f, "SoftBank" }, + { 0x440, 0x06f, "SoftBank" }, + { 0x440, 0x07f, "KDDI" }, + { 0x440, 0x08f, "KDDI" }, + { 0x440, 0x09f, "NTT docomo" }, + { 0x440, 0x10f, "NTT docomo" }, + { 0x440, 0x11f, "NTT docomo" }, + { 0x440, 0x12f, "NTT docomo" }, + { 0x440, 0x13f, "NTT docomo" }, + { 0x440, 0x14f, "NTT docomo" }, + { 0x440, 0x15f, "NTT docomo" }, + { 0x440, 0x16f, "NTT docomo" }, + { 0x440, 0x17f, "NTT docomo" }, + { 0x440, 0x18f, "NTT docomo" }, + { 0x440, 0x19f, "NTT docomo" }, + { 0x440, 0x20f, "SoftBank" }, + { 0x440, 0x21f, "NTT docomo" }, + { 0x440, 0x22f, "NTT docomo" }, + { 0x440, 0x23f, "DoCoMo" }, + { 0x440, 0x24f, "DoCoMo" }, + { 0x440, 0x25f, "DoCoMo" }, + { 0x440, 0x26f, "DoCoMo" }, + { 0x440, 0x27f, "DoCoMo" }, + { 0x440, 0x28f, "DoCoMo" }, + { 0x440, 0x29f, "DoCoMo" }, + { 0x440, 0x30f, "DoCoMo" }, + { 0x440, 0x31f, "DoCoMo" }, + { 0x440, 0x32f, "DoCoMo" }, + { 0x440, 0x33f, "DoCoMo" }, + { 0x440, 0x34f, "DoCoMo" }, + { 0x440, 0x35f, "DoCoMo" }, + { 0x440, 0x36f, "DoCoMo" }, + { 0x440, 0x37f, "DoCoMo" }, + { 0x440, 0x38f, "DoCoMo" }, + { 0x440, 0x39f, "DoCoMo" }, + { 0x440, 0x40f, "SoftBank" }, + { 0x440, 0x41f, "SoftBank" }, + { 0x440, 0x42f, "SoftBank" }, + { 0x440, 0x43f, "SoftBank" }, + { 0x440, 0x44f, "SoftBank" }, + { 0x440, 0x45f, "SoftBank" }, + { 0x440, 0x46f, "SoftBank" }, + { 0x440, 0x47f, "SoftBank" }, + { 0x440, 0x48f, "SoftBank" }, + { 0x440, 0x49f, "DoCoMo" }, + { 0x440, 0x50f, "KDDI" }, + { 0x440, 0x51f, "KDDI" }, + { 0x440, 0x52f, "KDDI" }, + { 0x440, 0x53f, "KDDI" }, + { 0x440, 0x54f, "KDDI" }, + { 0x440, 0x55f, "KDDI" }, + { 0x440, 0x56f, "KDDI" }, + { 0x440, 0x58f, "DoCoMo" }, + { 0x440, 0x60f, "DoCoMo" }, + { 0x440, 0x61f, "DoCoMo" }, + { 0x440, 0x62f, "DoCoMo" }, + { 0x440, 0x63f, "DoCoMo" }, + { 0x440, 0x64f, "DoCoMo" }, + { 0x440, 0x65f, "DoCoMo" }, + { 0x440, 0x66f, "DoCoMo" }, + { 0x440, 0x67f, "DoCoMo" }, + { 0x440, 0x68f, "DoCoMo" }, + { 0x440, 0x69f, "DoCoMo" }, + { 0x440, 0x70f, "au" }, + { 0x440, 0x71f, "KDDI" }, + { 0x440, 0x72f, "KDDI" }, + { 0x440, 0x73f, "KDDI" }, + { 0x440, 0x74f, "KDDI" }, + { 0x440, 0x75f, "KDDI" }, + { 0x440, 0x76f, "KDDI" }, + { 0x440, 0x77f, "KDDI" }, + { 0x440, 0x78f, "Okinawa Cellular Telephone" }, + { 0x440, 0x79f, "KDDI" }, + { 0x440, 0x80f, "TU-KA" }, + { 0x440, 0x81f, "TU-KA" }, + { 0x440, 0x82f, "TU-KA" }, + { 0x440, 0x83f, "TU-KA" }, + { 0x440, 0x84f, "TU-KA" }, + { 0x440, 0x85f, "TU-KA" }, + { 0x440, 0x86f, "TU-KA" }, + { 0x440, 0x87f, "DoCoMo" }, + { 0x440, 0x88f, "KDDI" }, + { 0x440, 0x89f, "KDDI" }, + { 0x440, 0x90f, "SoftBank" }, + { 0x440, 0x92f, "SoftBank" }, + { 0x440, 0x93f, "SoftBank" }, + { 0x440, 0x94f, "SoftBank" }, + { 0x440, 0x95f, "SoftBank" }, + { 0x440, 0x96f, "SoftBank" }, + { 0x440, 0x97f, "SoftBank" }, + { 0x440, 0x98f, "SoftBank" }, + { 0x440, 0x99f, "DoCoMo" }, + { 0x234, -1, "Jersey" }, + { 0x234, 0x50f, "JT-Wave" }, + { 0x234, 0x55f, "Sure Mobile" }, + { 0x234, 0x03f, "Airtel Vodafone" }, + { 0x416, -1, "Jordan" }, + { 0x416, 0x01f, "zain JO" }, + { 0x416, 0x02f, "XPress Telecom" }, + { 0x416, 0x03f, "Umniah" }, + { 0x416, 0x77f, "Orange" }, + { 0x401, -1, "Kazakhstan" }, + { 0x401, 0x01f, "Beeline" }, + { 0x401, 0x02f, "Kcell" }, + { 0x401, 0x07f, "Dalacom" }, + { 0x401, 0x77f, "Mobile Telecom Service" }, + { 0x639, -1, "Kenya" }, + { 0x639, 0x02f, "Safaricom" }, + { 0x639, 0x03f, "Zain" }, + { 0x639, 0x07f, "Orange Kenya" }, + { 0x545, -1, "Kiribati" }, + { 0x545, 0x09f, "Kiribati Frigate" }, + { 0x467, -1, "North Korea" }, + { 0x467, 0x193, "SUN NET" }, + { 0x450, -1, "South Korea" }, + { 0x450, 0x02f, "KT" }, + { 0x450, 0x03f, "Power 017" }, + { 0x450, 0x04f, "KT" }, + { 0x450, 0x05f, "SKT" }, + { 0x450, 0x06f, "LGT" }, + { 0x450, 0x08f, "KT SHOW" }, + { 0x212, -1, "Kosovo" }, + { 0x212, 0x01f, "Vala" }, + { 0x293, 0x41f, "iPKO" }, + { 0x293, 0x41f, "D3 Mobile" }, + { 0x212, 0x01f, "Z Mobile" }, + { 0x419, -1, "Kuwait" }, + { 0x419, 0x02f, "zain KW" }, + { 0x419, 0x03f, "Wataniya" }, + { 0x419, 0x04f, "Viva" }, + { 0x437, -1, "Kyrgyzstan" }, + { 0x437, 0x01f, "Beeline" }, + { 0x437, 0x05f, "MegaCom" }, + { 0x437, 0x09f, "O!" }, + { 0x457, -1, "Laos" }, + { 0x457, 0x01f, "LaoTel" }, + { 0x457, 0x02f, "ETL" }, + { 0x457, 0x03f, "Unitel" }, + { 0x457, 0x08f, "Tigo" }, + { 0x247, -1, "Latvia" }, + { 0x247, 0x01f, "LMT" }, + { 0x247, 0x02f, "Tele2" }, + { 0x247, 0x03f, "TRIATEL" }, + { 0x247, 0x05f, "Bite" }, + { 0x247, 0x06f, "Rigatta" }, + { 0x247, 0x07f, "MTS" }, + { 0x247, 0x08f, "IZZI" }, + { 0x247, 0x09f, "Camel Mobile" }, + { 0x415, -1, "Lebanon" }, + { 0x415, 0x01f, "Alfa" }, + { 0x415, 0x03f, "MTC-Touch" }, + { 0x651, -1, "Lesotho" }, + { 0x651, 0x01f, "Vodacom" }, + { 0x651, 0x02f, "Econet Ezin-cel" }, + { 0x618, -1, "Liberia" }, + { 0x618, 0x01f, "Lonestar Cell" }, + { 0x618, 0x04f, "Comium" }, + { 0x618, 0x20f, "LIBTELCO" }, + { 0x606, -1, "Libya" }, + { 0x606, 0x00f, "Libyana" }, + { 0x606, 0x01f, "Madar" }, + { 0x295, -1, "Liechtenstein" }, + { 0x295, 0x01f, "Swisscom" }, + { 0x295, 0x02f, "Orange" }, + { 0x295, 0x05f, "FL1" }, + { 0x295, 0x77f, "Tele 2" }, + { 0x246, -1, "Lithuania" }, + { 0x246, 0x01f, "Omnitel" }, + { 0x246, 0x02f, "BITE" }, + { 0x246, 0x03f, "Tele 2" }, + { 0x270, -1, "Luxembourg" }, + { 0x270, 0x01f, "LuxGSM" }, + { 0x270, 0x77f, "Tango" }, + { 0x270, 0x99f, "Orange" }, + { 0x455, -1, "Macau" }, + { 0x455, 0x00f, "SmarTone" }, + { 0x455, 0x01f, "CTM" }, + { 0x455, 0x02f, "China Telecom" }, + { 0x455, 0x03f, "3" }, + { 0x455, 0x04f, "CTM" }, + { 0x455, 0x05f, "3" }, + { 0x294, -1, "Republic of Macedonia" }, + { 0x294, 0x01f, "T-Mobile MK" }, + { 0x294, 0x02f, "ONE" }, + { 0x294, 0x03f, "Vip MK" }, + { 0x646, -1, "Madagascar" }, + { 0x646, 0x01f, "Zain" }, + { 0x646, 0x02f, "Orange" }, + { 0x646, 0x03f, "Sacel" }, + { 0x646, 0x04f, "Telma" }, + { 0x650, -1, "Malawi" }, + { 0x650, 0x01f, "TNM" }, + { 0x650, 0x10f, "Zain" }, + { 0x502, -1, "Malaysia" }, + { 0x502, 0x12f, "Maxis" }, + { 0x502, 0x13f, "Celcom" }, + { 0x502, 0x16f, "DiGi" }, + { 0x502, 0x17f, "Maxis" }, + { 0x502, 0x18f, "U Mobile" }, + { 0x502, 0x19f, "Celcom" }, + { 0x472, -1, "Maldives" }, + { 0x472, 0x01f, "Dhiraagu" }, + { 0x472, 0x02f, "Wataniya" }, + { 0x610, -1, "Mali" }, + { 0x610, 0x01f, "Malitel" }, + { 0x610, 0x02f, "Orange" }, + { 0x278, -1, "Malta" }, + { 0x278, 0x01f, "Vodafone" }, + { 0x278, 0x21f, "GO" }, + { 0x278, 0x77f, "Melita" }, + { 0x000, -1, "Marshall Islands" }, +// { 0x000, ?, "?" }, + { 0x340, -1, "Martinique" }, + { 0x340, 0x01f, "Orange" }, + { 0x340, 0x02f, "Outremer" }, + { 0x340, 0x20f, "Digicel" }, + { 0x609, -1, "Mauritania" }, + { 0x609, 0x01f, "Mattel" }, + { 0x609, 0x10f, "Mauritel" }, + { 0x617, -1, "Mauritius" }, + { 0x617, 0x01f, "Orange" }, + { 0x617, 0x02f, "MTML" }, + { 0x617, 0x10f, "Emtel" }, + { 0x334, -1, "Mexico" }, + { 0x334, 0x01f, "Nextel" }, + { 0x334, 0x02f, "Telcel" }, + { 0x334, 0x03f, "movistar" }, + { 0x334, 0x04f, "Iusacell / Unefon" }, + { 0x550, -1, "Federated States of Micronesia" }, + { 0x550, 0x01f, "FSM Telecom" }, + { 0x259, -1, "Moldova" }, + { 0x259, 0x01f, "Orange" }, + { 0x259, 0x02f, "Moldcell" }, + { 0x259, 0x03f, "IDC" }, + { 0x259, 0x03f, "Unit?" }, + { 0x259, 0x04f, "Eventis" }, + { 0x259, 0x05f, "Unit?" }, + { 0x212, -1, "Monaco" }, + { 0x212, 0x01f, "Office des Telephones" }, + { 0x428, -1, "Mongolia" }, + { 0x428, 0x99f, "MobiCom" }, + { 0x428, 0x88f, "Unitel" }, + { 0x428, 0x91f, "Skytel" }, + { 0x428, 0x98f, "G.Mobile" }, + { 0x297, -1, "Montenegro" }, + { 0x297, 0x01f, "Telenor" }, + { 0x297, 0x02f, "T-Mobile" }, + { 0x297, 0x03f, "m:tel CG" }, + { 0x604, -1, "Morocco" }, + { 0x604, 0x00f, "Moditel" }, + { 0x604, 0x01f, "IAM" }, + { 0x604, 0x02f, "INWI" }, + { 0x605, 0x03f, "yassine" }, + { 0x643, -1, "Mozambique" }, + { 0x643, 0x01f, "mCel" }, + { 0x643, 0x04f, "Vodacom" }, + { 0x414, -1, "Myanmar" }, + { 0x414, 0x01f, "MPT" }, + { 0x649, -1, "Namibia" }, + { 0x649, 0x01f, "MTC" }, + { 0x649, 0x02f, "switch" }, + { 0x649, 0x03f, "Leo" }, + { 0x536, -1, "Nauru" }, + { 0x429, -1, "Nepal" }, + { 0x429, 0x01f, "Namaste / NT Mobile" }, + { 0x429, 0x02f, "Ncell" }, + { 0x429, 0x03f, "Sky/C-Phone" }, + { 0x204, -1, "Netherlands" }, + { 0x204, 0x01f, "OneFoon" }, + { 0x204, 0x02f, "Tele2" }, + { 0x204, 0x03f, "Blyk" }, + { 0x204, 0x04f, "Vodafone" }, + { 0x204, 0x05f, "Elephant Talk" }, + { 0x204, 0x06f, "Barablu Mobile" }, + { 0x204, 0x07f, "Teleena" }, + { 0x204, 0x08f, "KPN" }, + { 0x204, 0x09f, "Lycamobile" }, + { 0x204, 0x10f, "KPN" }, + { 0x204, 0x12f, "Telfort" }, + { 0x204, 0x14f, "6Gmobile" }, + { 0x204, 0x16f, "T-Mobile" }, + { 0x204, 0x18f, "Telfort" }, + { 0x204, 0x20f, "Orange Nederland" }, + { 0x204, 0x21f, "NS Railinfrabeheer B.V." }, + { 0x204, 0x67f, "RadioAccess" }, + { 0x204, 0x69f, "KPN Mobile" }, + { 0x362, -1, "Netherlands Antilles" }, + { 0x362, 0x51f, "Telcell" }, + { 0x362, 0x69f, "Digicel" }, + { 0x362, 0x91f, "UTS" }, + { 0x362, 0x00f, "East Caribbean Cellular" }, + { 0x362, 0x00f, "Antiliano Por N.V." }, + { 0x362, 0x95f, "MIO" }, + { 0x362, 0x94f, "Bay?s" }, + { 0x546, -1, "New Caledonia" }, + { 0x546, 0x01f, "Mobilis" }, + { 0x530, -1, "New Zealand" }, + { 0x530, 0x00f, "Telecom" }, + { 0x530, 0x01f, "Vodafone" }, + { 0x530, 0x02f, "Telecom" }, + { 0x530, 0x03f, "Woosh" }, + { 0x530, 0x04f, "TelstraClear" }, + { 0x530, 0x05f, "XT Mobile Network" }, + { 0x530, 0x12f, "360" }, + { 0x530, 0x24f, "2degrees" }, + { 0x710, -1, "Nicaragua" }, + { 0x710, 0x21f, "Claro" }, + { 0x710, 0x30f, "movistar" }, + { 0x710, 0x73f, "SERCOM" }, + { 0x614, -1, "Niger" }, + { 0x614, 0x01f, "SahelCom" }, + { 0x614, 0x02f, "Zain" }, + { 0x614, 0x03f, "Telecel" }, + { 0x614, 0x04f, "Orange" }, + { 0x621, -1, "Nigeria" }, + { 0x621, 0x20f, "Zain" }, + { 0x621, 0x30f, "MTN" }, + { 0x621, 0x40f, "M-Tel" }, + { 0x621, 0x50f, "Glo" }, + { 0x621, 0x60f, "Etisalat" }, + { 0x242, -1, "Norway" }, + { 0x242, 0x01f, "Telenor" }, + { 0x242, 0x02f, "NetCom" }, + { 0x242, 0x03f, "Teletopia" }, + { 0x242, 0x04f, "Tele2" }, + { 0x242, 0x05f, "Network Norway" }, + { 0x242, 0x06f, "Ice" }, + { 0x242, 0x07f, "Ventelo" }, + { 0x242, 0x08f, "TDC Mobil AS" }, + { 0x242, 0x09f, "Barablu Mobile Norway Ltd" }, + { 0x242, 0x20f, "Jernbaneverket AS" }, + { 0x422, -1, "Oman" }, + { 0x422, 0x02f, "Oman Mobile" }, + { 0x422, 0x03f, "Nawras" }, + { 0x410, -1, "Pakistan" }, + { 0x410, 0x01f, "Mobilink" }, + { 0x410, 0x03f, "Ufone" }, + { 0x410, 0x04f, "Zong" }, + { 0x410, 0x06f, "Telenor" }, + { 0x410, 0x07f, "Warid" }, + { 0x552, -1, "Palau" }, + { 0x552, 0x01f, "PNCC" }, + { 0x552, 0x80f, "Palau Mobile" }, + { 0x423, -1, "Palestinian Authority" }, + { 0x423, 0x05f, "Jawwal" }, + { 0x423, 0x06f, "Wataniya" }, + { 0x714, -1, "Panama" }, + { 0x714, 0x01f, "Cable & Wireless" }, + { 0x714, 0x02f, "movistar" }, + { 0x714, 0x04f, "Digicel" }, + { 0x714, 0x03f, "Claro" }, + { 0x537, -1, "Papua New Guinea" }, + { 0x537, 0x01f, "B-Mobile" }, + { 0x537, 0x03f, "Digicel" }, + { 0x744, -1, "Paraguay" }, + { 0x744, 0x01f, "VOX" }, + { 0x744, 0x02f, "Claro" }, + { 0x744, 0x04f, "Tigo" }, + { 0x744, 0x05f, "Personal" }, + { 0x716, -1, "Peru" }, + { 0x716, 0x06f, "movistar" }, + { 0x716, 0x10f, "Claro" }, + { 0x716, 0x17f, "NEXTEL" }, + { 0x515, -1, "Philippines" }, + { 0x515, 0x01f, "Islacom" }, + { 0x515, 0x02f, "Globe" }, + { 0x515, 0x03f, "Smart" }, + { 0x515, 0x05f, "Sun" }, + { 0x515, 0x11f, "PLDT via ACeS Philippines" }, + { 0x515, 0x18f, "Cure" }, + { 0x515, 0x88f, "Nextel" }, + { 0x260, -1, "Poland" }, + { 0x260, 0x01f, "Plus" }, + { 0x260, 0x02f, "Era" }, + { 0x260, 0x03f, "Orange" }, + { 0x260, 0x04f, "Netia S.A." }, + { 0x260, 0x05f, "Polska Telefonia Kom?rkowa Centertel Sp. z o.o." }, + { 0x260, 0x06f, "Play" }, + { 0x260, 0x07f, "Netia" }, + { 0x260, 0x08f, "E-Telko Sp. z o.o." }, + { 0x260, 0x09f, "Telekomunikacja Kolejowa Sp. z o.o." }, + { 0x260, 0x10f, "Sferia" }, + { 0x260, 0x11f, "Nordisk Polska" }, + { 0x260, 0x12f, "Cyfrowy Polsat" }, + { 0x260, 0x13f, "Sferia" }, + { 0x260, 0x14f, "Sferia" }, + { 0x260, 0x15f, "CenterNet" }, + { 0x260, 0x16f, "Mobyland" }, + { 0x260, 0x17f, "Aero2" }, + { 0x268, -1, "Portugal" }, + { 0x268, 0x01f, "Vodafone" }, + { 0x268, 0x03f, "Optimus" }, + { 0x268, 0x06f, "TMN" }, + { 0x268, 0x21f, "Zapp" }, + { 0x330, -1, "Puerto Rico" }, + { 0x330, 0x11f, "Claro" }, + { 0x427, -1, "Qatar" }, + { 0x427, 0x01f, "Qatarnet" }, + { 0x427, 0x02f, "Vodafone Qatar" }, + { 0x647, -1, "R&?union" }, + { 0x647, 0x00f, "Orange" }, + { 0x647, 0x02f, "Outremer" }, + { 0x647, 0x10f, "SFR Reunion" }, + { 0x226, -1, "Romania" }, + { 0x226, 0x01f, "Vodafone" }, + { 0x226, 0x02f, "Romtelecom" }, + { 0x226, 0x03f, "Cosmote" }, + { 0x226, 0x04f, "Cosmote" }, + { 0x226, 0x05f, "Digi.Mobil" }, + { 0x226, 0x06f, "Cosmote" }, + { 0x226, 0x10f, "Orange" }, + { 0x250, -1, "Russian Federation" }, + { 0x250, 0x01f, "MTS" }, + { 0x250, 0x02f, "MegaFon" }, + { 0x250, 0x03f, "NCC" }, + { 0x250, 0x04f, "Sibchallenge" }, + { 0x250, 0x05f, "ETK" }, + { 0x250, 0x06f, "Skylink" }, + { 0x250, 0x07f, "SMARTS" }, + { 0x250, 0x09f, "Skylink" }, + { 0x250, 0x10f, "DTC" }, + { 0x250, 0x11f, "Orensot" }, + { 0x250, 0x12f, "Baykalwestcom" }, + { 0x250, 0x12f, "Akos" }, + { 0x250, 0x13f, "KUGSM" }, + { 0x250, 0x15f, "SMARTS" }, + { 0x250, 0x16f, "NTC" }, + { 0x250, 0x17f, "Utel" }, + { 0x250, 0x19f, "INDIGO" }, + { 0x250, 0x20f, "Tele2" }, + { 0x250, 0x23f, "Mobicom - Novosibirsk" }, + { 0x250, 0x28f, "Beeline" }, + { 0x250, 0x35f, "MOTIV" }, + { 0x250, 0x38f, "Tambov GSM" }, + { 0x250, 0x39f, "Utel" }, + { 0x250, 0x44f, "Stavtelesot / North Caucasian GSM" }, + { 0x250, 0x92f, "Primtelefon" }, + { 0x250, 0x93f, "Telecom XXI" }, + { 0x250, 0x99f, "Beeline" }, +// { 0x250, ?, "SkyLink/MTS/the Moscow Cellular communication" }, + { 0x635, -1, "Rwanda" }, + { 0x635, 0x10f, "MTN" }, + { 0x635, 0x13f, "Tigo" }, + { 0x356, -1, "Saint Kitts and Nevis" }, + { 0x356, 0x050, "Digicel" }, + { 0x356, 0x110, "Cable & Wireless" }, + { 0x358, -1, "Saint Lucia" }, + { 0x358, 0x050, "Digicel" }, + { 0x358, 0x110, "Cable & Wireless" }, + { 0x308, -1, "Saint Pierre and Miquelon" }, + { 0x308, 0x01f, "Ameris" }, + { 0x360, -1, "Saint Vincent and the Grenadines" }, + { 0x360, 0x070, "Digicel" }, + { 0x360, 0x100, "Cingular Wireless" }, + { 0x360, 0x110, "Cable & Wireless" }, + { 0x549, -1, "Samoa" }, + { 0x549, 0x01f, "Digicel" }, + { 0x549, 0x27f, "SamoaTel" }, + { 0x292, -1, "San Marino" }, + { 0x292, 0x01f, "PRIMA" }, + { 0x626, -1, "Sao Tome and Principe" }, + { 0x626, 0x01f, "CSTmovel" }, + { 0x420, -1, "Saudi Arabia" }, + { 0x420, 0x01f, "Al Jawal" }, + { 0x420, 0x03f, "Mobily" }, + { 0x420, 0x07f, "EAE" }, + { 0x420, 0x04f, "Zain SA" }, + { 0x608, -1, "Senegal" }, + { 0x608, 0x01f, "Orange (telecommunications)" }, + { 0x608, 0x02f, "Tigo" }, + { 0x608, 0x03f, "Expresso" }, + { 0x220, -1, "Serbia" }, + { 0x220, 0x01f, "Telenor" }, + { 0x220, 0x03f, "mt:s" }, + { 0x220, 0x05f, "VIP" }, + { 0x633, -1, "Seychelles" }, + { 0x633, 0x01f, "Cable & Wireless" }, + { 0x633, 0x02f, "Mediatech International" }, + { 0x633, 0x10f, "Airtel" }, + { 0x619, -1, "Sierra Leone" }, + { 0x619, 0x01f, "Zain" }, + { 0x619, 0x02f, "Millicom" }, + { 0x619, 0x03f, "Datatel" }, + { 0x619, 0x04f, "Comium" }, + { 0x619, 0x05f, "Africell" }, + { 0x619, 0x25f, "Mobitel" }, +// { 0x619, ?, "LeoneCel" }, + { 0x525, -1, "Singapore" }, + { 0x525, 0x01f, "SingTel" }, + { 0x525, 0x02f, "SingTel-G18" }, + { 0x525, 0x03f, "M1" }, + { 0x525, 0x05f, "StarHub" }, + { 0x525, 0x12f, "Digital Trunked Radio Network" }, + { 0x231, -1, "Slovakia" }, + { 0x231, 0x01f, "Orange" }, + { 0x231, 0x02f, "T-Mobile" }, + { 0x231, 0x03f, "Unient Communications" }, + { 0x231, 0x04f, "T-Mobile" }, + { 0x231, 0x05f, "Mobile Entertainment Company" }, + { 0x231, 0x06f, "O2" }, + { 0x231, 0x99f, "?SR" }, + { 0x293, -1, "Slovenia" }, + { 0x293, 0x40f, "Si.mobil" }, + { 0x293, 0x41f, "Mobitel" }, + { 0x293, 0x64f, "T-2" }, + { 0x293, 0x70f, "Tu?mobil" }, + { 0x540, -1, "Solomon Islands" }, + { 0x637, -1, "Somalia" }, + { 0x637, 0x01f, "Telesom" }, + { 0x637, 0x04f, "Somafone" }, + { 0x637, 0x10f, "Nationlink" }, + { 0x637, 0x25f, "Hormuud" }, + { 0x637, 0x30f, "Golis" }, + { 0x637, 0x82f, "Telcom" }, + { 0x655, -1, "South Africa" }, + { 0x655, 0x01f, "Vodacom" }, + { 0x655, 0x06f, "Sentech" }, + { 0x655, 0x07f, "Cell C" }, + { 0x655, 0x10f, "MTN" }, + { 0x655, 0x11f, "SAPS Gauteng" }, + { 0x655, 0x13f, "Neotel" }, + { 0x655, 0x21f, "Cape Town Metropolitan Council" }, + { 0x655, 0x30f, "Bokamoso Consortium" }, + { 0x655, 0x31f, "Karabo Telecoms (Pty) Ltd." }, + { 0x655, 0x32f, "Ilizwi Telecommunications" }, + { 0x655, 0x33f, "Thinta Thinta Telecommunications" }, + { 0x655, 0x02f, "Telkom" }, + { 0x214, -1, "Spain" }, + { 0x214, 0x01f, "Vodafone" }, + { 0x214, 0x03f, "Orange" }, + { 0x214, 0x04f, "Yoigo" }, + { 0x214, 0x05f, "TME" }, + { 0x214, 0x06f, "Vodafone" }, + { 0x214, 0x07f, "movistar" }, + { 0x214, 0x08f, "Euskaltel" }, + { 0x214, 0x09f, "Orange" }, + { 0x214, 0x15f, "BT" }, + { 0x214, 0x16f, "TeleCable" }, + { 0x214, 0x17f, "M?bil R" }, + { 0x214, 0x18f, "ONO" }, + { 0x214, 0x19f, "Simyo" }, + { 0x214, 0x21f, "Jazztel" }, + { 0x214, 0x22f, "DigiMobil" }, + { 0x214, 0x23f, "Barablu" }, + { 0x413, -1, "Sri Lanka" }, + { 0x413, 0x01f, "Mobitel" }, + { 0x413, 0x02f, "Dialog" }, + { 0x413, 0x03f, "Etisalat" }, + { 0x413, 0x05f, "Airtel" }, + { 0x413, 0x08f, "Hutch" }, + { 0x413, 0x00f, "RTEC Mobile" }, + { 0x634, -1, "Sudan" }, + { 0x634, 0x01f, "Zain SD" }, + { 0x634, 0x02f, "MTN" }, + { 0x634, 0x05f, "Vivacell" }, + { 0x746, -1, "Suriname" }, + { 0x746, 0x05f, "Telesur" }, + { 0x653, -1, "Swaziland" }, + { 0x653, 0x10f, "Swazi MTN" }, + { 0x240, -1, "Sweden" }, + { 0x240, 0x01f, "Telia" }, + { 0x240, 0x02f, "3" }, + { 0x240, 0x03f, "Ice.net" }, + { 0x240, 0x04f, "3G Infrastructure Services" }, + { 0x240, 0x05f, "Sweden 3G" }, + { 0x240, 0x06f, "Telenor" }, + { 0x240, 0x07f, "Tele2" }, + { 0x240, 0x08f, "Telenor" }, + { 0x240, 0x09f, "djuice" }, + { 0x240, 0x10f, "Spring Mobil" }, + { 0x240, 0x11f, "Lindholmen Science Park" }, + { 0x240, 0x12f, "Barablu Mobile Scandinavia" }, + { 0x240, 0x13f, "Ventelo Sverige" }, + { 0x240, 0x14f, "TDC Mobil" }, + { 0x240, 0x15f, "Wireless Maingate Nordic" }, + { 0x240, 0x16f, "42IT" }, + { 0x240, 0x17f, "G?talandsn?tet" }, + { 0x240, 0x20f, "Wireless Maingate Message Services" }, + { 0x240, 0x21f, "MobiSir" }, + { 0x240, 0x25f, "DigiTelMobile" }, + { 0x228, -1, "Switzerland" }, + { 0x228, 0x01f, "Swisscom" }, + { 0x228, 0x02f, "Sunrise" }, + { 0x228, 0x03f, "Orange" }, + { 0x228, 0x05f, "Togewanet AG (Comfone)" }, + { 0x228, 0x06f, "SBB AG" }, + { 0x228, 0x07f, "IN&Phone" }, + { 0x228, 0x08f, "Tele2" }, + { 0x228, 0x50f, "3G Mobile AG" }, + { 0x228, 0x51f, "BebbiCell AG" }, + { 0x417, -1, "Syria" }, + { 0x417, 0x01f, "Syriatel" }, + { 0x417, 0x02f, "MTN" }, + { 0x466, -1, "Taiwan" }, + { 0x466, 0x01f, "FarEasTone" }, + { 0x466, 0x02f, "APTG" }, + { 0x466, 0x06f, "Tuntex" }, + { 0x466, 0x11f, "Chunghwa LDM" }, + { 0x466, 0x88f, "KG Telecom" }, + { 0x466, 0x89f, "VIBO" }, + { 0x466, 0x92f, "Chungwa" }, + { 0x466, 0x93f, "MobiTai" }, + { 0x466, 0x97f, "Taiwan Mobile" }, + { 0x466, 0x99f, "TransAsia" }, + { 0x436, -1, "Tajikistan" }, + { 0x436, 0x01f, "Tcell" }, + { 0x436, 0x02f, "Indigo" }, + { 0x436, 0x03f, "MLT" }, + { 0x436, 0x04f, "Babilon-M" }, + { 0x436, 0x05f, "Beeline" }, + { 0x640, -1, "Tanzania" }, + { 0x640, 0x06f, "SasaTel" }, + { 0x640, 0x02f, "tiGO" }, + { 0x640, 0x03f, "Zantel" }, + { 0x640, 0x04f, "Vodacom" }, + { 0x640, 0x05f, "Zain" }, + { 0x520, -1, "Thailand" }, + { 0x520, 0x00f, "Hutch" }, + { 0x520, 0x01f, "AIS" }, + { 0x520, 0x02f, "CAT CDMA" }, + { 0x520, 0x10f, "?" }, + { 0x520, 0x15f, "Thai Mobile" }, + { 0x520, 0x15f, "TOT 3G" }, + { 0x520, 0x18f, "dtac" }, + { 0x520, 0x23f, "AIS GSM 1800" }, + { 0x520, 0x99f, "True Move" }, + { 0x520, 0x00f, "WE PCT" }, + { 0x615, -1, "Togo" }, + { 0x615, 0x01f, "Togo Cell" }, + { 0x615, 0x03f, "Moov" }, + { 0x539, -1, "Tonga" }, + { 0x539, 0x01f, "Tonga Communications Corporation" }, + { 0x539, 0x43f, "Shoreline Communication" }, + { 0x539, 0x88f, "Digicel" }, + { 0x374, -1, "Trinidad and Tobago" }, + { 0x374, 0x12f, "bmobile" }, + { 0x374, 0x13f, "Digicel" }, + { 0x605, -1, "Tunisia" }, + { 0x605, 0x01f, "Orange" }, + { 0x605, 0x02f, "Tunicell" }, + { 0x605, 0x03f, "Tunisiana" }, + { 0x286, -1, "Turkey" }, + { 0x286, 0x01f, "Turkcell" }, + { 0x286, 0x02f, "Vodafone" }, + { 0x286, 0x03f, "Avea" }, + { 0x286, 0x04f, "Aycell" }, + { 0x438, -1, "Turkmenistan" }, + { 0x438, 0x01f, "MTS" }, + { 0x438, 0x02f, "TM-Cell" }, + { 0x376, -1, "Turks and Caicos Islands" }, + { 0x376, 0x350, "C&W" }, + { 0x376, 0x352, "Islandcom" }, + { 0x338, 0x05f, "Digicel" }, + { 0x553, -1, "Tuvalu" }, + { 0x553, 0x01f, "TTC" }, + { 0x641, -1, "Uganda" }, + { 0x641, 0x01f, "Zain" }, + { 0x641, 0x10f, "MTN" }, + { 0x641, 0x11f, "Uganda Telecom Ltd." }, + { 0x641, 0x22f, "Warid Telecom" }, + { 0x641, 0x14f, "Orange" }, + { 0x255, -1, "Ukraine" }, + { 0x255, 0x01f, "MTS" }, + { 0x255, 0x02f, "Beeline" }, + { 0x255, 0x03f, "Kyivstar" }, + { 0x255, 0x04f, "IT" }, + { 0x255, 0x05f, "Golden Telecom" }, + { 0x255, 0x06f, "life:)" }, + { 0x255, 0x07f, "Ukrtelecom" }, + { 0x255, 0x21f, "PEOPLEnet" }, + { 0x255, 0x23f, "CDMA Ukraine" }, + { 0x424, -1, "United Arab Emirates" }, + { 0x424, 0x02f, "Etisalat" }, + { 0x424, 0x03f, "du" }, + { 0x234, -1, "United Kingdom" }, + { 0x234, 0x00f, "BT" }, + { 0x234, 0x01f, "UK01" }, + { 0x234, 0x02f, "O2" }, + { 0x234, 0x03f, "Airtel-Vodafone" }, + { 0x234, 0x04f, "FMS Solutions Ltd" }, + { 0x234, 0x07f, "Cable and Wireless UK" }, + { 0x234, 0x08f, "OnePhone Ltd" }, + { 0x234, 0x10f, "O2" }, + { 0x234, 0x11f, "O2" }, + { 0x234, 0x12f, "Railtrack" }, + { 0x234, 0x14f, "Hay Systems Ltd" }, + { 0x234, 0x15f, "Vodafone" }, + { 0x234, 0x16f, "Opal Telecom Ltd" }, + { 0x234, 0x18f, "Cloud9" }, + { 0x234, 0x19f, "Teleware" }, + { 0x234, 0x20f, "3" }, + { 0x234, 0x22f, "RoutoMessaging" }, + { 0x234, 0x25f, "Truphone" }, + { 0x234, 0x30f, "T-Mobile" }, + { 0x234, 0x31f, "Virgin" }, + { 0x234, 0x32f, "Virgin" }, + { 0x234, 0x33f, "Orange" }, + { 0x234, 0x34f, "Orange" }, + { 0x234, 0x50f, "JT-Wave" }, + { 0x234, 0x55f, "Cable & Wireless Guernsey / Sure Mobile (Jersey)" }, + { 0x234, 0x58f, "Manx Telecom" }, + { 0x234, 0x75f, "Inquam" }, + { 0x234, 0x77f, "BT" }, + { 0x200, -1, "United States of America" }, + { 0x200, 0x053, "Virgin Mobile US" }, + { 0x200, 0x054, "Alltel US" }, + { 0x200, 0x066, "U.S. Cellular" }, + /* 0x310 taken from Annex to ITU Operational Bulletin No. 958 – 15.VI.2010 */ + { 0x310, 0x00f, "nTelos" }, + { 0x310, 0x000, "Mid-Tex Cellular" }, + { 0x310, 0x004, "Verizon" }, + { 0x310, 0x010, "MCI" }, + { 0x310, 0x012, "Verizon" }, + { 0x310, 0x013, "MobileTel" }, + { 0x310, 0x014, "Testing" }, + { 0x310, 0x016, "Cricket Communications" }, + { 0x310, 0x017, "North Sight Communications Inc." }, + { 0x310, 0x020, "Union Telephone Company" }, + { 0x310, 0x026, "T-Mobile" }, + { 0x310, 0x030, "Centennial" }, + { 0x310, 0x034, "Airpeak" }, + { 0x310, 0x038, "AT&T" }, + { 0x310, 0x040, "Concho" }, + { 0x310, 0x046, "SIMMETRY" }, + { 0x310, 0x060, "Consolidated Telcom" }, + { 0x310, 0x070, "Highland Cellular" }, + { 0x310, 0x080, "Corr" }, + { 0x310, 0x090, "AT&T" }, + { 0x310, 0x100, "Plateau Wireless" }, + { 0x310, 0x110, "PTI Pacifica" }, + { 0x310, 0x120, "Sprint" }, + { 0x310, 0x150, "AT&T" }, + { 0x310, 0x160, "T-Mobile" }, + { 0x310, 0x170, "T-Mobile" }, + { 0x310, 0x180, "West Central" }, + { 0x310, 0x190, "Dutch Harbor" }, + { 0x310, 0x200, "T-Mobile" }, + { 0x310, 0x210, "T-Mobile" }, + { 0x310, 0x220, "T-Mobile" }, + { 0x310, 0x230, "T-Mobile" }, + { 0x310, 0x240, "T-Mobile" }, + { 0x310, 0x250, "T-Mobile" }, + { 0x310, 0x260, "T-Mobile" }, + { 0x310, 0x270, "T-Mobile" }, + { 0x310, 0x280, "T-Mobile" }, + { 0x310, 0x290, "T-Mobile" }, + { 0x310, 0x300, "iSmart Mobile" }, + { 0x310, 0x310, "T-Mobile" }, + { 0x310, 0x311, "Farmers Wireless" }, + { 0x310, 0x320, "Cellular One" }, + { 0x310, 0x330, "T-Mobile" }, + { 0x310, 0x340, "Westlink" }, + { 0x310, 0x350, "Carolina Phone" }, + { 0x310, 0x380, "AT&T Mobility" }, + { 0x310, 0x390, "Cellular One of East Texas" }, + { 0x310, 0x400, "i CAN_GSM" }, + { 0x310, 0x410, "AT&T" }, + { 0x310, 0x420, "Cincinnati Bell" }, + { 0x310, 0x430, "Alaska Digitel" }, + { 0x310, 0x440, "Cellular One" }, + { 0x310, 0x450, "Viaero" }, + { 0x310, 0x460, "Simmetry" }, + { 0x310, 0x480, "Choice Phone" }, + { 0x310, 0x490, "T-Mobile" }, + { 0x310, 0x500, "Alltel" }, + { 0x310, 0x510, "Airtel" }, + { 0x310, 0x520, "VeriSign" }, + { 0x310, 0x530, "West Virginia Wireless" }, + { 0x310, 0x540, "Oklahoma Western" }, + { 0x310, 0x560, "AT&T" }, + { 0x310, 0x570, "Cellular One" }, + { 0x310, 0x580, "T-Mobile" }, + { 0x310, 0x590, "Alltel" }, + { 0x310, 0x610, "Epic Touch" }, + { 0x310, 0x620, "Coleman County Telecom" }, + { 0x310, 0x630, "AmeriLink PCS" }, + { 0x310, 0x640, "Airadigm" }, + { 0x310, 0x650, "Jasper" }, + { 0x310, 0x660, "T-Mobile" }, + { 0x310, 0x670, "Northstar" }, + { 0x310, 0x680, "AT&T" }, + { 0x310, 0x690, "Conestoga" }, + { 0x310, 0x730, "SeaMobile" }, + { 0x310, 0x740, "Convey" }, + { 0x310, 0x760, "Panhandle" }, + { 0x310, 0x770, "i wireless" }, + { 0x310, 0x780, "Airlink PCS" }, + { 0x310, 0x790, "PinPoint" }, + { 0x310, 0x800, "T-Mobile" }, + { 0x310, 0x830, "Caprock" }, + { 0x310, 0x850, "Aeris" }, + { 0x310, 0x870, "PACE" }, + { 0x310, 0x880, "Advantage" }, + { 0x310, 0x890, "Unicel" }, + { 0x310, 0x900, "Mid-Rivers Wireless" }, + { 0x310, 0x910, "First Cellular" }, + { 0x310, 0x940, "Iris Wireless LLC" }, + { 0x310, 0x950, "XIT Wireless" }, + { 0x310, 0x960, "Plateau Wireless" }, + { 0x310, 0x970, "Globalstar" }, + { 0x310, 0x980, "AT&T Mobility" }, + { 0x310, 0x990, "AT&T Mobility" }, + { 0x311, 0x000, "Mid-Tex Cellular" }, + { 0x311, 0x010, "Chariton Valley" }, + { 0x311, 0x020, "Missouri RSA 5 Partnership" }, + { 0x311, 0x030, "Indigo Wireless" }, + { 0x311, 0x040, "Commnet Wireless" }, + { 0x311, 0x050, "Wikes Cellular" }, + { 0x311, 0x060, "Farmers Cellular" }, + { 0x311, 0x070, "Easterbrooke" }, + { 0x311, 0x080, "Pine Cellular" }, + { 0x311, 0x090, "Long Lines Wireless" }, + { 0x311, 0x100, "High Plains Wireless" }, + { 0x311, 0x110, "High Plains Wireless" }, + { 0x311, 0x120, "Choice Phone" }, + { 0x311, 0x130, "Cell One Amarillo" }, + { 0x311, 0x140, "Sprocket" }, + { 0x311, 0x150, "Wilkes Cellular" }, + { 0x311, 0x160, "Endless Mountains Wireless" }, + { 0x311, 0x170, "PetroCom" }, + { 0x311, 0x180, "Cingular Wireless" }, + { 0x311, 0x190, "Cellular Properties" }, + { 0x311, 0x210, "Farmers Cellular" }, + { 0x316, 0x010, "Nextel" }, + { 0x316, 0x011, "Southern Communications Services" }, + { 0x748, -1, "Uruguay" }, + { 0x748, 0x00f, "Ancel" }, + { 0x748, 0x01f, "Ancel" }, + { 0x748, 0x07f, "Movistar" }, + { 0x748, 0x10f, "Claro" }, + { 0x434, -1, "Uzbekistan" }, + { 0x434, 0x01f, "Buztel" }, + { 0x434, 0x02f, "Uzmacom" }, + { 0x434, 0x04f, "Beeline" }, + { 0x434, 0x05f, "Ucell" }, + { 0x434, 0x06f, "Perfectum Mobile" }, + { 0x434, 0x07f, "MTS" }, + { 0x541, -1, "Vanuatu" }, + { 0x541, 0x01f, "SMILE" }, + { 0x225, -1, "Vatican" }, + { 0x734, -1, "Venezuela" }, + { 0x734, 0x01f, "Digitel" }, + { 0x734, 0x02f, "Digitel" }, + { 0x734, 0x03f, "Digitel" }, + { 0x734, 0x04f, "movistar" }, + { 0x734, 0x06f, "Movilnet" }, + { 0x452, -1, "Vietnam" }, + { 0x452, 0x01f, "MobiFone" }, + { 0x452, 0x02f, "Vinaphone" }, + { 0x452, 0x03f, "S-Fone" }, + { 0x452, 0x04f, "Viettel Mobile" }, + { 0x452, 0x05f, "Vietnamobile" }, + { 0x452, 0x06f, "E-Mobile" }, + { 0x452, 0x07f, "Beeline VN" }, + { 0x421, -1, "Yemen" }, + { 0x421, 0x01f, "SabaFon" }, + { 0x421, 0x02f, "MTN" }, + { 0x421, 0x03f, "Yemen Mobile" }, + { 0x421, 0x04f, "HiTS-UNITEL" }, + { 0x645, -1, "Zambia" }, + { 0x645, 0x01f, "Zain" }, + { 0x645, 0x02f, "MTN" }, + { 0x645, 0x03f, "ZAMTEL" }, + { 0x648, -1, "Zimbabwe" }, + { 0x648, 0x01f, "Net*One" }, + { 0x648, 0x03f, "Telecel" }, + { 0x648, 0x04f, "Econet" }, + { 0x901, -1, "International" }, + { 0x901, 0x01f, "ICO" }, + { 0x901, 0x02f, "Sense Communications International" }, + { 0x901, 0x03f, "Iridium" }, + { 0x901, 0x04f, "Globalstar" }, + { 0x901, 0x05f, "Thuraya RMSS Network" }, + { 0x901, 0x06f, "Thuraya Satellite Telecommunications Company" }, + { 0x901, 0x07f, "Ellipso" }, + { 0x901, 0x08f, "" }, + { 0x901, 0x09f, "Tele1 Europe" }, + { 0x901, 0x10f, "ACeS" }, + { 0x901, 0x11f, "Inmarsat" }, + { 0x901, 0x12f, "MCP" }, + { 0x901, 0x13f, "GSM.AQ" }, + { 0x901, 0x14f, "AeroMobile AS" }, + { 0x901, 0x15f, "OnAir Switzerland Sarl" }, + { 0x901, 0x16f, "Jasper Systems" }, + { 0x901, 0x17f, "Navitas" }, + { 0x901, 0x18f, "Cellular @Sea" }, + { 0x901, 0x19f, "Vodafone Malta Maritime" }, + { 0x901, 0x21f, "Seanet" }, + { 0x901, 0x24f, "iNum" }, + { 0x901, 0x29f, "Telenor" }, + { 0, 0, NULL } +}; + +/* GSM 03.22 Annex A */ +int gsm_match_mcc(uint16_t mcc, char *imsi) +{ + uint16_t sim_mcc; + + sim_mcc = ((imsi[0] - '0') << 8) + + ((imsi[1] - '0') << 4) + + imsi[2] - '0'; + + return (mcc == sim_mcc); +} + +/* GSM 03.22 Annex A */ +int gsm_match_mnc(uint16_t mcc, uint16_t mnc, char *imsi) +{ + uint16_t sim_mnc; + + /* 1. SIM-MCC = BCCH-MCC */ + if (!gsm_match_mcc(mcc, imsi)) + return 0; + + /* 2. 3rd digit of BCCH-MNC is not 0xf */ + if ((mnc & 0x00f) != 0x00f) { + /* 3. 3 digit SIM-MNC = BCCH-MNC */ + sim_mnc = ((imsi[3] - '0') << 8) + + ((imsi[4] - '0') << 4) + + imsi[5] - '0'; + + return (mnc == sim_mnc); + } + + /* 4. BCCH-MCC in the range 310-316 */ + if (mcc >= 310 && mcc <= 316) { + /* 5. 3rd diit of SIM-MNC is 0 */ + if (imsi[5] != 0) + return 0; + } + + /* 6. 1st 2 digits of SIM-MNC and BCCH-MNC match */ + sim_mnc = ((imsi[3] - '0') << 8) + + ((imsi[4] - '0') << 4) + + 0x00f; + + return (mnc == sim_mnc); +} + +const char *gsm_print_mcc(uint16_t mcc) +{ + static char string[5] = "000"; + + snprintf(string, 4, "%03x", mcc); + return string; +} + +const char *gsm_print_mnc(uint16_t mnc) +{ + static char string[7]; + + /* invalid format: return hex value */ + if ((mnc & 0xf000) + || (mnc & 0x0f00) > 0x0900 + || (mnc & 0x00f0) > 0x0090 + || ((mnc & 0x000f) > 0x0009 && (mnc & 0x000f) < 0x000f)) { + snprintf(string, 6, "0x%03x", mnc); + return string; + } + + /* two digits */ + if ((mnc & 0x000f) == 0x000f) { + snprintf(string, 6, "%02x", mnc >> 4); + return string; + } + + /* three digits */ + snprintf(string, 6, "%03x", mnc); + return string; +} + +const uint16_t gsm_input_mcc(char *string) +{ + uint16_t mcc; + + if (strlen(string) != 3) + return GSM_INPUT_INVALID; + if (string[0] < '0' || string [0] > '9' + || string[1] < '0' || string [1] > '9' + || string[2] < '0' || string [2] > '9') + return GSM_INPUT_INVALID; + + mcc = ((string[0] - '0') << 8) + | ((string[1] - '0') << 4) + | ((string[2] - '0')); + + if (mcc == 0x000) + return GSM_INPUT_INVALID; + + return mcc; +} + +const uint16_t gsm_input_mnc(char *string) +{ + uint16_t mnc = 0; + + if (strlen(string) == 2) { + if (string[0] < '0' || string [0] > '9' + || string[1] < '0' || string [1] > '9') + return GSM_INPUT_INVALID; + + mnc = ((string[0] - '0') << 8) + | ((string[1] - '0') << 4) + | 0x00f; + } else + if (strlen(string) == 3) { + if (string[0] < '0' || string [0] > '9' + || string[1] < '0' || string [1] > '9' + || string[2] < '0' || string [2] > '9') + return GSM_INPUT_INVALID; + + mnc = ((string[0] - '0') << 8) + | ((string[1] - '0') << 4) + | ((string[2] - '0')); + } + + return mnc; +} + +const char *gsm_get_mcc(uint16_t mcc) +{ + int i; + + for (i = 0; gsm_networks[i].name; i++) + if (gsm_networks[i].mnc < 0 && gsm_networks[i].mcc == mcc) + return gsm_networks[i].name; + + return gsm_print_mcc(mcc); +} + +const char *gsm_get_mnc(uint16_t mcc, uint16_t mnc) +{ + int i; + + for (i = 0; gsm_networks[i].name; i++) + if (gsm_networks[i].mcc == mcc && gsm_networks[i].mnc == mnc) + return gsm_networks[i].name; + + return gsm_print_mnc(mnc); +} + +/* get MCC from IMSI */ +const char *gsm_imsi_mcc(char *imsi) +{ + int i, found = 0; + uint16_t mcc; + + mcc = ((imsi[0] - '0') << 8) + | ((imsi[1] - '0') << 4) + | ((imsi[2] - '0')); + + for (i = 0; gsm_networks[i].name; i++) { + if (gsm_networks[i].mcc == mcc) { + found = 1; + break; + } + } + if (found == 0) + return "Unknown"; + + return gsm_networks[i].name; +} + +/* get MNC from IMSI */ +const char *gsm_imsi_mnc(char *imsi) +{ + int i, found = 0, position = 0; + uint16_t mcc, mnc2, mnc3; + + mcc = ((imsi[0] - '0') << 8) + | ((imsi[1] - '0') << 4) + | ((imsi[2] - '0')); + mnc2 = ((imsi[3] - '0') << 8) + + ((imsi[4] - '0') << 4) + + 0x00f; + mnc3 = ((imsi[3] - '0') << 8) + + ((imsi[4] - '0') << 4) + + imsi[5] - '0'; + + for (i = 0; gsm_networks[i].name; i++) { + if (gsm_networks[i].mcc != mcc) + continue; + if ((gsm_networks[i].mnc & 0x00f) == 0x00f) { + if (mnc2 == gsm_networks[i].mnc) { + found++; + position = i; + } + } else { + if (mnc3 == gsm_networks[i].mnc) { + found++; + position = i; + } + } + } + + if (found == 0) + return "Unknown"; + if (found > 1) + return "Ambiguous"; + return gsm_networks[position].name; +} + + diff --git a/src/host/layer23/src/common/sap_interface.c b/src/host/layer23/src/common/sap_interface.c new file mode 100644 index 00000000..1dad748d --- /dev/null +++ b/src/host/layer23/src/common/sap_interface.c @@ -0,0 +1,190 @@ +/* BTSAP socket interface of layer2/3 stack */ + +/* (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by Harald Welte <laforge@gnumonks.org> + * (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/sap_interface.h> + +#include <osmocom/core/utils.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include <arpa/inet.h> + +#define _GNU_SOURCE +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> + +#define GSM_SAP_LENGTH 300 +#define GSM_SAP_HEADROOM 32 + +static int sap_read(struct osmo_fd *fd) +{ + struct msgb *msg; + uint16_t len; + int rc; + struct osmocom_ms *ms = (struct osmocom_ms *) fd->data; + + msg = msgb_alloc_headroom(GSM_SAP_LENGTH+GSM_SAP_HEADROOM, GSM_SAP_HEADROOM, "Layer2"); + if (!msg) { + LOGP(DSAP, LOGL_ERROR, "Failed to allocate msg.\n"); + return -ENOMEM; + } + + rc = read(fd->fd, &len, sizeof(len)); + if (rc < sizeof(len)) { + fprintf(stderr, "SAP socket failed\n"); + msgb_free(msg); + if (rc >= 0) + rc = -EIO; + sap_close(ms); + return rc; + } + + len = ntohs(len); + if (len > GSM_SAP_LENGTH) { + LOGP(DSAP, LOGL_ERROR, "Length is too big: %u\n", len); + msgb_free(msg); + return -EINVAL; + } + + + msg->l1h = msgb_put(msg, len); + rc = read(fd->fd, msg->l1h, msgb_l1len(msg)); + if (rc != msgb_l1len(msg)) { + LOGP(DSAP, LOGL_ERROR, "Can not read data: len=%d rc=%d " + "errno=%d\n", len, rc, errno); + msgb_free(msg); + return rc; + } + + if (ms->sap_entity.msg_handler) + ms->sap_entity.msg_handler(msg, ms); + + return 0; +} + +static int sap_write(struct osmo_fd *fd, struct msgb *msg) +{ + int rc; + + if (fd->fd <= 0) + return -EINVAL; + + rc = write(fd->fd, msg->data, msg->len); + if (rc != msg->len) { + LOGP(DSAP, LOGL_ERROR, "Failed to write data: rc: %d\n", rc); + return rc; + } + + return 0; +} + +int sap_open(struct osmocom_ms *ms, const char *socket_path) +{ + int rc; + struct sockaddr_un local; + + ms->sap_wq.bfd.fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (ms->sap_wq.bfd.fd < 0) { + fprintf(stderr, "Failed to create unix domain socket.\n"); + return ms->sap_wq.bfd.fd; + } + + local.sun_family = AF_UNIX; + strncpy(local.sun_path, socket_path, sizeof(local.sun_path)); + local.sun_path[sizeof(local.sun_path) - 1] = '\0'; + + rc = connect(ms->sap_wq.bfd.fd, (struct sockaddr *) &local, + sizeof(local.sun_family) + strlen(local.sun_path)); + if (rc < 0) { + fprintf(stderr, "Failed to connect to '%s'.\n", local.sun_path); + close(ms->sap_wq.bfd.fd); + return rc; + } + + osmo_wqueue_init(&ms->sap_wq, 100); + ms->sap_wq.bfd.data = ms; + ms->sap_wq.bfd.when = BSC_FD_READ; + ms->sap_wq.read_cb = sap_read; + ms->sap_wq.write_cb = sap_write; + + rc = osmo_fd_register(&ms->sap_wq.bfd); + if (rc != 0) { + fprintf(stderr, "Failed to register fd.\n"); + return rc; + } + + return 0; +} + +int sap_close(struct osmocom_ms *ms) +{ + if (ms->sap_wq.bfd.fd <= 0) + return -EINVAL; + + close(ms->sap_wq.bfd.fd); + ms->sap_wq.bfd.fd = -1; + osmo_fd_unregister(&ms->sap_wq.bfd); + osmo_wqueue_clear(&ms->sap_wq); + + return 0; +} + +int osmosap_send(struct osmocom_ms *ms, struct msgb *msg) +{ + uint16_t *len; + + if (ms->sap_wq.bfd.fd <= 0) + return -EINVAL; + + DEBUGP(DSAP, "Sending: '%s'\n", osmo_hexdump(msg->data, msg->len)); + + if (msg->l1h != msg->data) + LOGP(DSAP, LOGL_ERROR, "Message SAP header != Message Data\n"); + + /* prepend 16bit length before sending */ + len = (uint16_t *) msgb_push(msg, sizeof(*len)); + *len = htons(msg->len - sizeof(*len)); + + if (osmo_wqueue_enqueue(&ms->sap_wq, msg) != 0) { + LOGP(DSAP, LOGL_ERROR, "Failed to enqueue msg.\n"); + msgb_free(msg); + return -1; + } + + return 0; +} + +/* register message handler for messages that are sent from L2->L3 */ +int osmosap_register_handler(struct osmocom_ms *ms, osmosap_cb_t cb) +{ + ms->sap_entity.msg_handler = cb; + + return 0; +} + diff --git a/src/host/layer23/src/common/sim.c b/src/host/layer23/src/common/sim.c new file mode 100644 index 00000000..8c89cf0b --- /dev/null +++ b/src/host/layer23/src/common/sim.c @@ -0,0 +1,1253 @@ +/* + * (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdint.h> +#include <errno.h> +#include <arpa/inet.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/l1ctl.h> + +extern void *l23_ctx; +static int sim_process_job(struct osmocom_ms *ms); + +/* + * support + */ + +uint32_t new_handle = 1; + +static struct gsm1111_df_name { + uint16_t file; + const char *name; +} gsm1111_df_name[] = { + { 0x3f00, "MF" }, + { 0x7f20, "DFgsm" }, + { 0x7f10, "DFtelecom" }, + { 0x7f22, "DFis-41" }, + { 0x7f23, "DFfp-cts" }, + { 0x5f50, "DFgraphics" }, + { 0x5f30, "DFiridium" }, + { 0x5f31, "DFglobst" }, + { 0x5f32, "DFico" }, + { 0x5f33, "DFaces" }, + { 0x5f40, "DFeia/tia-553" }, + { 0x5f60, "DFcts" }, + { 0x5f70, "DFsolsa" }, + { 0x5f3c, "DFmexe" }, + { 0, NULL } +}; + +static const char *get_df_name(uint16_t fid) +{ + int i; + static char text[7]; + + for (i = 0; gsm1111_df_name[i].file; i++) + if (gsm1111_df_name[i].file == fid) + break; + if (gsm1111_df_name[i].file) + return gsm1111_df_name[i].name; + + sprintf(text, "0x%04x", fid); + return text; +} + +static struct gsm_sim_handler *sim_get_handler(struct gsm_sim *sim, + uint32_t handle) +{ + struct gsm_sim_handler *handler; + + llist_for_each_entry(handler, &sim->handlers, entry) + if (handler->handle == handle) + return handler; + + return NULL; +} + +/* + * messages + */ + +static const struct value_string sim_job_names[] = { + { SIM_JOB_READ_BINARY, "SIM_JOB_READ_BINARY" }, + { SIM_JOB_UPDATE_BINARY, "SIM_JOB_UPDATE_BINARY" }, + { SIM_JOB_READ_RECORD, "SIM_JOB_READ_RECORD" }, + { SIM_JOB_UPDATE_RECORD, "SIM_JOB_UPDATE_RECORD" }, + { SIM_JOB_SEEK_RECORD, "SIM_JOB_SEEK_RECORD" }, + { SIM_JOB_INCREASE, "SIM_JOB_INCREASE" }, + { SIM_JOB_INVALIDATE, "SIM_JOB_INVALIDATE" }, + { SIM_JOB_REHABILITATE, "SIM_JOB_REHABILITATE" }, + { SIM_JOB_RUN_GSM_ALGO, "SIM_JOB_RUN_GSM_ALGO" }, + { SIM_JOB_PIN1_UNLOCK, "SIM_JOB_PIN1_UNLOCK" }, + { SIM_JOB_PIN1_CHANGE, "SIM_JOB_PIN1_CHANGE" }, + { SIM_JOB_PIN1_DISABLE, "SIM_JOB_PIN1_DISABLE" }, + { SIM_JOB_PIN1_ENABLE, "SIM_JOB_PIN1_ENABLE" }, + { SIM_JOB_PIN1_UNBLOCK, "SIM_JOB_PIN1_UNBLOCK" }, + { SIM_JOB_PIN2_UNLOCK, "SIM_JOB_PIN2_UNLOCK" }, + { SIM_JOB_PIN2_CHANGE, "SIM_JOB_PIN2_CHANGE" }, + { SIM_JOB_PIN2_UNBLOCK, "SIM_JOB_PIN2_UNBLOCK" }, + { SIM_JOB_OK, "SIM_JOB_OK" }, + { SIM_JOB_ERROR, "SIM_JOB_ERROR" }, + { 0, NULL } +}; + +static const char *get_job_name(int value) +{ + return get_value_string(sim_job_names, value); +} + +/* allocate sim client message (upper layer) */ +struct msgb *gsm_sim_msgb_alloc(uint32_t handle, uint8_t job_type) +{ + struct msgb *msg; + struct sim_hdr *nsh; + + msg = msgb_alloc_headroom(SIM_ALLOC_SIZE+SIM_ALLOC_HEADROOM, + SIM_ALLOC_HEADROOM, "SIM"); + if (!msg) + return NULL; + + nsh = (struct sim_hdr *) msgb_put(msg, sizeof(*nsh)); + nsh->handle = handle; + nsh->job_type = job_type; + + return msg; +} + +/* reply to job, after it is done. reuse the msgb in the job */ +void gsm_sim_reply(struct osmocom_ms *ms, uint8_t result_type, uint8_t *result, + uint16_t result_len) +{ + struct gsm_sim *sim = &ms->sim; + struct msgb *msg = sim->job_msg; + struct sim_hdr *sh; + uint8_t *payload; + uint16_t payload_len; + struct gsm_sim_handler *handler; + + LOGP(DSIM, LOGL_INFO, "sending result to callback function " + "(type=%d)\n", result_type); + + /* if no handler, or no callback, just free the job */ + sh = (struct sim_hdr *)msg->data; + handler = sim_get_handler(sim, sh->handle); + if (!handler || !handler->cb) { + LOGP(DSIM, LOGL_INFO, "no callback or no handler, " + "dropping result\n"); + msgb_free(sim->job_msg); + sim->job_msg = NULL; + sim->job_state = SIM_JST_IDLE; + return; + } + + payload = msg->data + sizeof(*sh); + payload_len = msg->len - sizeof(*sh); + + /* remove data */ + msg->tail -= payload_len; + msg->len -= payload_len; + + /* add reply data */ + sh->job_type = result_type; + if (result_len) + memcpy(msgb_put(msg, result_len), result, result_len); + + /* callback */ + sim->job_state = SIM_JST_IDLE; + sim->job_msg = NULL; + handler->cb(ms, msg); +} + +/* send APDU to card reader */ +static int sim_apdu_send(struct osmocom_ms *ms, uint8_t *data, uint16_t length) +{ + LOGP(DSIM, LOGL_INFO, "sending APDU (class 0x%02x, ins 0x%02x)\n", + data[0], data[1]); + l1ctl_tx_sim_req(ms, data, length); + return 0; +} + +/* dequeue messages (RSL-SAP) */ +int gsm_sim_job_dequeue(struct osmocom_ms *ms) +{ + struct gsm_sim *sim = &ms->sim; + struct sim_hdr *sh; + struct msgb *msg; + struct gsm_sim_handler *handler; + + /* already have a job */ + if (sim->job_msg) + return 0; + + /* get next job */ + while ((msg = msgb_dequeue(&sim->jobs))) { + /* resolve handler */ + sh = (struct sim_hdr *) msg->data; + LOGP(DSIM, LOGL_INFO, "got new job: %s (handle=%08x)\n", + get_job_name(sh->job_type), sh->handle); + handler = sim_get_handler(sim, sh->handle); + if (!handler) { + LOGP(DSIM, LOGL_INFO, "no handler, ignoring job\n"); + /* does not exist anymore */ + msgb_free(msg); + continue; + } + + /* init job */ + sim->job_state = SIM_JST_IDLE; + sim->job_msg = msg; + sim->job_handle = sh->handle; + + /* process current job, message is freed there */ + sim_process_job(ms); + return 1; /* work done */ + } + + return 0; +} + + +/* + * SIM commands + */ + +/* 9.2.1 */ +static int gsm1111_tx_select(struct osmocom_ms *ms, uint16_t fid) +{ + uint8_t buffer[5 + 2]; + + LOGP(DSIM, LOGL_INFO, "SELECT (file=0x%04x)\n", fid); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_SELECT; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = 2; + buffer[5] = fid >> 8; + buffer[6] = fid; + + return sim_apdu_send(ms, buffer, 5 + 2); +} + +#if 0 +/* 9.2.2 */ +static int gsm1111_tx_status(struct osmocom_ms *ms) +{ + uint8_t buffer[5]; + + LOGP(DSIM, LOGL_INFO, "STATUS\n"); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_STATUS; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = 0; + + return sim_apdu_send(ms, buffer, 5); +} +#endif + +/* 9.2.3 */ +static int gsm1111_tx_read_binary(struct osmocom_ms *ms, uint16_t offset, + uint8_t length) +{ + uint8_t buffer[5]; + + LOGP(DSIM, LOGL_INFO, "READ BINARY (offset=%d len=%d)\n", offset, + length); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_READ_BINARY; + buffer[2] = offset >> 8; + buffer[3] = offset; + buffer[4] = length; + + return sim_apdu_send(ms, buffer, 5); +} + +/* 9.2.4 */ +static int gsm1111_tx_update_binary(struct osmocom_ms *ms, uint16_t offset, + uint8_t *data, uint8_t length) +{ + uint8_t buffer[5 + length]; + + LOGP(DSIM, LOGL_INFO, "UPDATE BINARY (offset=%d len=%d)\n", offset, + length); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_UPDATE_BINARY; + buffer[2] = offset >> 8; + buffer[3] = offset; + buffer[4] = length; + memcpy(buffer + 5, data, length); + + return sim_apdu_send(ms, buffer, 5 + length); +} + +/* 9.2.5 */ +static int gsm1111_tx_read_record(struct osmocom_ms *ms, uint8_t rec_no, + uint8_t mode, uint8_t length) +{ + uint8_t buffer[5]; + + LOGP(DSIM, LOGL_INFO, "READ RECORD (rec_no=%d mode=%d len=%d)\n", + rec_no, mode, length); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_READ_RECORD; + buffer[2] = rec_no; + buffer[3] = mode; + buffer[4] = length; + + return sim_apdu_send(ms, buffer, 5); +} + +/* 9.2.6 */ +static int gsm1111_tx_update_record(struct osmocom_ms *ms, uint8_t rec_no, + uint8_t mode, uint8_t *data, uint8_t length) +{ + uint8_t buffer[5 + length]; + + LOGP(DSIM, LOGL_INFO, "UPDATE RECORD (rec_no=%d mode=%d len=%d)\n", + rec_no, mode, length); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_UPDATE_RECORD; + buffer[2] = rec_no; + buffer[3] = mode; + buffer[4] = length; + memcpy(buffer + 5, data, length); + + return sim_apdu_send(ms, buffer, 5 + length); +} + +/* 9.2.7 */ +static int gsm1111_tx_seek(struct osmocom_ms *ms, uint8_t type_mode, + uint8_t *pattern, uint8_t length) +{ + uint8_t buffer[5 + length]; + uint8_t type = type_mode >> 4; + uint8_t mode = type_mode & 0x0f; + + LOGP(DSIM, LOGL_INFO, "SEEK (type=%d mode=%d len=%d)\n", type, mode, + length); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_SEEK; + buffer[2] = 0x00; + buffer[3] = type_mode; + buffer[4] = length; + memcpy(buffer + 5, pattern, length); + + return sim_apdu_send(ms, buffer, 5 + length); +} + +/* 9.2.8 */ +static int gsm1111_tx_increase(struct osmocom_ms *ms, uint32_t value) +{ + uint8_t buffer[5 + 3]; + + LOGP(DSIM, LOGL_INFO, "INCREASE (value=%d)\n", value); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_INCREASE; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = 3; + buffer[5] = value >> 16; + buffer[6] = value >> 8; + buffer[7] = value; + + return sim_apdu_send(ms, buffer, 5 + 3); +} + +/* 9.2.9 */ +static int gsm1111_tx_verify_chv(struct osmocom_ms *ms, uint8_t chv_no, + uint8_t *chv, uint8_t length) +{ + uint8_t buffer[5 + 8]; + int i; + + LOGP(DSIM, LOGL_INFO, "VERIFY CHV (CHV%d)\n", chv_no); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_VERIFY_CHV; + buffer[2] = 0x00; + buffer[3] = chv_no; + buffer[4] = 8; + for (i = 0; i < 8; i++) { + if (i < length) + buffer[5 + i] = chv[i]; + else + buffer[5 + i] = 0xff; + } + + return sim_apdu_send(ms, buffer, 5 + 8); +} + +/* 9.2.10 */ +static int gsm1111_tx_change_chv(struct osmocom_ms *ms, uint8_t chv_no, + uint8_t *chv_old, uint8_t length_old, uint8_t *chv_new, + uint8_t length_new) +{ + uint8_t buffer[5 + 16]; + int i; + + LOGP(DSIM, LOGL_INFO, "CHANGE CHV (CHV%d)\n", chv_no); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_CHANGE_CHV; + buffer[2] = 0x00; + buffer[3] = chv_no; + buffer[4] = 16; + for (i = 0; i < 8; i++) { + if (i < length_old) + buffer[5 + i] = chv_old[i]; + else + buffer[5 + i] = 0xff; + if (i < length_new) + buffer[13 + i] = chv_new[i]; + else + buffer[13 + i] = 0xff; + } + + return sim_apdu_send(ms, buffer, 5 + 16); +} + +/* 9.2.11 */ +static int gsm1111_tx_disable_chv(struct osmocom_ms *ms, uint8_t *chv, + uint8_t length) +{ + uint8_t buffer[5 + 8]; + int i; + + LOGP(DSIM, LOGL_INFO, "DISABLE CHV (CHV1)\n"); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_DISABLE_CHV; + buffer[2] = 0x00; + buffer[3] = 0x01; + buffer[4] = 8; + for (i = 0; i < 8; i++) { + if (i < length) + buffer[5 + i] = chv[i]; + else + buffer[5 + i] = 0xff; + } + + return sim_apdu_send(ms, buffer, 5 + 8); +} + +/* 9.2.12 */ +static int gsm1111_tx_enable_chv(struct osmocom_ms *ms, uint8_t *chv, + uint8_t length) +{ + uint8_t buffer[5 + 8]; + int i; + + LOGP(DSIM, LOGL_INFO, "ENABLE CHV (CHV1)\n"); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_ENABLE_CHV; + buffer[2] = 0x00; + buffer[3] = 0x01; + buffer[4] = 8; + for (i = 0; i < 8; i++) { + if (i < length) + buffer[5 + i] = chv[i]; + else + buffer[5 + i] = 0xff; + } + + return sim_apdu_send(ms, buffer, 5 + 8); +} + +/* 9.2.13 */ +static int gsm1111_tx_unblock_chv(struct osmocom_ms *ms, uint8_t chv_no, + uint8_t *chv_unblk, uint8_t length_unblk, uint8_t *chv_new, + uint8_t length_new) +{ + uint8_t buffer[5 + 16]; + int i; + + LOGP(DSIM, LOGL_INFO, "UNBLOCK CHV (CHV%d)\n", (chv_no == 2) ? 2 : 1); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_UNBLOCK_CHV; + buffer[2] = 0x00; + buffer[3] = (chv_no == 1) ? 0 : chv_no; + buffer[4] = 16; + for (i = 0; i < 8; i++) { + if (i < length_unblk) + buffer[5 + i] = chv_unblk[i]; + else + buffer[5 + i] = 0xff; + if (i < length_new) + buffer[13 + i] = chv_new[i]; + else + buffer[13 + i] = 0xff; + } + + return sim_apdu_send(ms, buffer, 5 + 16); +} + +/* 9.2.14 */ +static int gsm1111_tx_invalidate(struct osmocom_ms *ms) +{ + uint8_t buffer[5]; + + LOGP(DSIM, LOGL_INFO, "INVALIDATE\n"); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_INVALIDATE; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = 0; + + return sim_apdu_send(ms, buffer, 5); +} + +/* 9.2.15 */ +static int gsm1111_tx_rehabilitate(struct osmocom_ms *ms) +{ + uint8_t buffer[5]; + + LOGP(DSIM, LOGL_INFO, "REHABILITATE\n"); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_REHABLILITATE; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = 0; + + return sim_apdu_send(ms, buffer, 5); +} + +/* 9.2.16 */ +static int gsm1111_tx_run_gsm_algo(struct osmocom_ms *ms, uint8_t *rand) +{ + uint8_t buffer[5 + 16]; + + LOGP(DSIM, LOGL_INFO, "RUN GSM ALGORITHM\n"); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_RUN_GSM_ALGO; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = 16; + memcpy(buffer + 5, rand, 16); + + return sim_apdu_send(ms, buffer, 5 + 16); +} + +#if 0 +/* 9.2.17 */ +static int gsm1111_tx_sleep(struct osmocom_ms *ms) +{ + uint8_t buffer[5]; + + LOGP(DSIM, LOGL_INFO, "\n"); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_SLEEP; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = 0; + + return sim_apdu_send(ms, buffer, 5); +} +#endif + +/* 9.2.18 */ +static int gsm1111_tx_get_response(struct osmocom_ms *ms, uint8_t length) +{ + uint8_t buffer[5]; + + LOGP(DSIM, LOGL_INFO, "GET RESPONSE (len=%d)\n", length); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_GET_RESPONSE; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = length; + + return sim_apdu_send(ms, buffer, 5); +} + +#if 0 +/* 9.2.19 */ +static int gsm1111_tx_terminal_profile(struct osmocom_ms *ms, uint8_t *data, + uint8_t length) +{ + uint8_t buffer[5 + length]; + + LOGP(DSIM, LOGL_INFO, "TERMINAL PROFILE (len=%d)\n", length); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_TERMINAL_PROFILE; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = length; + memcpy(buffer + 5, data, length); + + return sim_apdu_send(ms, buffer, 5 + length); +} + +/* 9.2.20 */ +static int gsm1111_tx_envelope(struct osmocom_ms *ms, uint8_t *data, + uint8_t length) +{ + uint8_t buffer[5 + length]; + + LOGP(DSIM, LOGL_INFO, "ENVELOPE (len=%d)\n", length); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_ENVELOPE; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = length; + memcpy(buffer + 5, data, length); + + return sim_apdu_send(ms, buffer, 5 + length); +} + +/* 9.2.21 */ +static int gsm1111_tx_fetch(struct osmocom_ms *ms, uint8_t length) +{ + uint8_t buffer[5]; + + LOGP(DSIM, LOGL_INFO, "FETCH (len=%d)\n", length); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_FETCH; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = length; + + return sim_apdu_send(ms, buffer, 5); +} + +/* 9.2.22 */ +static int gsm1111_tx_terminal_response(struct osmocom_ms *ms, uint8_t *data, + uint8_t length) +{ + uint8_t buffer[5 + length]; + + LOGP(DSIM, LOGL_INFO, "TERMINAL RESPONSE (len=%d)\n", length); + buffer[0] = GSM1111_CLASS_GSM; + buffer[1] = GSM1111_INST_TERMINAL_RESPONSE; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = length; + memcpy(buffer + 5, data, length); + + return sim_apdu_send(ms, buffer, 5 + length); +} +#endif + +/* + * SIM state machine + */ + +/* process job */ +static int sim_process_job(struct osmocom_ms *ms) +{ + struct gsm_sim *sim = &ms->sim; + uint8_t *payload, *payload2; + uint16_t payload_len, payload_len2; + struct sim_hdr *sh; + uint8_t cause; + int i; + + /* no current */ + if (!sim->job_msg) + return 0; + + sh = (struct sim_hdr *)sim->job_msg->data; + payload = sim->job_msg->data + sizeof(*sh); + payload_len = sim->job_msg->len - sizeof(*sh); + + /* do reset before sim reading */ + if (!sim->reset) { + sim->reset = 1; + // FIXME: send reset command to L1 + } + + /* navigate to right DF */ + switch (sh->job_type) { + case SIM_JOB_READ_BINARY: + case SIM_JOB_UPDATE_BINARY: + case SIM_JOB_READ_RECORD: + case SIM_JOB_UPDATE_RECORD: + case SIM_JOB_SEEK_RECORD: + case SIM_JOB_INCREASE: + case SIM_JOB_INVALIDATE: + case SIM_JOB_REHABILITATE: + case SIM_JOB_RUN_GSM_ALGO: + /* check MF / DF */ + i = 0; + while (sh->path[i] && sim->path[i]) { + if (sh->path[i] != sim->path[i]) + break; + i++; + } + /* if path in message is shorter or if paths are different */ + if (sim->path[i]) { + LOGP(DSIM, LOGL_INFO, "go MF\n"); + sim->job_state = SIM_JST_SELECT_MFDF; + /* go MF */ + sim->path[0] = 0; + return gsm1111_tx_select(ms, 0x3f00); + } + /* if path in message is longer */ + if (sh->path[i]) { + LOGP(DSIM, LOGL_INFO, "requested path is longer, go " + "child %s\n", get_df_name(sh->path[i])); + sim->job_state = SIM_JST_SELECT_MFDF; + /* select child */ + sim->path[i] = sh->path[i]; + sim->path[i + 1] = 0; + return gsm1111_tx_select(ms, sh->path[i]); + } + /* if paths are equal, continue */ + } + + /* set state and trigger SIM process */ + switch (sh->job_type) { + case SIM_JOB_READ_BINARY: + case SIM_JOB_UPDATE_BINARY: + case SIM_JOB_READ_RECORD: + case SIM_JOB_UPDATE_RECORD: + case SIM_JOB_SEEK_RECORD: + case SIM_JOB_INCREASE: + case SIM_JOB_INVALIDATE: + case SIM_JOB_REHABILITATE: + sim->job_state = SIM_JST_SELECT_EF; + sim->file = sh->file; + return gsm1111_tx_select(ms, sh->file); + case SIM_JOB_RUN_GSM_ALGO: + if (payload_len != 16) { + LOGP(DSIM, LOGL_ERROR, "random not 16 bytes\n"); + break; + } + sim->job_state = SIM_JST_RUN_GSM_ALGO; + return gsm1111_tx_run_gsm_algo(ms, payload); + case SIM_JOB_PIN1_UNLOCK: + payload_len = strlen((char *)payload); + if (payload_len < 4 || payload_len > 8) { + LOGP(DSIM, LOGL_ERROR, "key not in range 4..8\n"); + break; + } + sim->job_state = SIM_JST_PIN1_UNLOCK; + return gsm1111_tx_verify_chv(ms, 0x01, payload, payload_len); + case SIM_JOB_PIN2_UNLOCK: + payload_len = strlen((char *)payload); + if (payload_len < 4 || payload_len > 8) { + LOGP(DSIM, LOGL_ERROR, "key not in range 4..8\n"); + break; + } + sim->job_state = SIM_JST_PIN2_UNLOCK; + return gsm1111_tx_verify_chv(ms, 0x02, payload, payload_len); + case SIM_JOB_PIN1_CHANGE: + payload_len = strlen((char *)payload); + payload2 = payload + payload_len + 1; + payload_len2 = strlen((char *)payload2); + if (payload_len < 4 || payload_len > 8) { + LOGP(DSIM, LOGL_ERROR, "key1 not in range 4..8\n"); + break; + } + if (payload_len2 < 4 || payload_len2 > 8) { + LOGP(DSIM, LOGL_ERROR, "key2 not in range 4..8\n"); + break; + } + sim->job_state = SIM_JST_PIN1_CHANGE; + return gsm1111_tx_change_chv(ms, 0x01, payload, payload_len, + payload2, payload_len2); + case SIM_JOB_PIN2_CHANGE: + payload_len = strlen((char *)payload); + payload2 = payload + payload_len + 1; + payload_len2 = strlen((char *)payload2); + if (payload_len < 4 || payload_len > 8) { + LOGP(DSIM, LOGL_ERROR, "key1 not in range 4..8\n"); + break; + } + if (payload_len2 < 4 || payload_len2 > 8) { + LOGP(DSIM, LOGL_ERROR, "key2 not in range 4..8\n"); + break; + } + sim->job_state = SIM_JST_PIN2_CHANGE; + return gsm1111_tx_change_chv(ms, 0x02, payload, payload_len, + payload2, payload_len2); + case SIM_JOB_PIN1_DISABLE: + payload_len = strlen((char *)payload); + if (payload_len < 4 || payload_len > 8) { + LOGP(DSIM, LOGL_ERROR, "key not in range 4..8\n"); + break; + } + sim->job_state = SIM_JST_PIN1_DISABLE; + return gsm1111_tx_disable_chv(ms, payload, payload_len); + case SIM_JOB_PIN1_ENABLE: + payload_len = strlen((char *)payload); + if (payload_len < 4 || payload_len > 8) { + LOGP(DSIM, LOGL_ERROR, "key not in range 4..8\n"); + break; + } + sim->job_state = SIM_JST_PIN1_ENABLE; + return gsm1111_tx_enable_chv(ms, payload, payload_len); + case SIM_JOB_PIN1_UNBLOCK: + payload_len = strlen((char *)payload); + payload2 = payload + payload_len + 1; + payload_len2 = strlen((char *)payload2); + if (payload_len != 8) { + LOGP(DSIM, LOGL_ERROR, "key1 not 8 digits\n"); + break; + } + if (payload_len2 < 4 || payload_len2 > 8) { + LOGP(DSIM, LOGL_ERROR, "key2 not in range 4..8\n"); + break; + } + sim->job_state = SIM_JST_PIN1_UNBLOCK; + /* NOTE: CHV1 is coded 0x00 here */ + return gsm1111_tx_unblock_chv(ms, 0x00, payload, payload_len, + payload2, payload_len2); + case SIM_JOB_PIN2_UNBLOCK: + payload_len = strlen((char *)payload); + payload2 = payload + payload_len + 1; + payload_len2 = strlen((char *)payload2); + if (payload_len != 8) { + LOGP(DSIM, LOGL_ERROR, "key1 not 8 digits\n"); + break; + } + if (payload_len2 < 4 || payload_len2 > 8) { + LOGP(DSIM, LOGL_ERROR, "key2 not in range 4..8\n"); + break; + } + sim->job_state = SIM_JST_PIN2_UNBLOCK; + return gsm1111_tx_unblock_chv(ms, 0x02, payload, payload_len, + payload2, payload_len2); + } + + LOGP(DSIM, LOGL_ERROR, "unknown job %x, please fix\n", sh->job_type); + cause = SIM_CAUSE_REQUEST_ERROR; + gsm_sim_reply(ms, SIM_JOB_ERROR, &cause, 1); + + return 0; +} + +/* receive SIM response */ +int sim_apdu_resp(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_sim *sim = &ms->sim; + uint8_t *payload; + uint16_t payload_len; + uint8_t *data = msg->data; + int length = msg->len, ef_len; + uint8_t sw1, sw2; + uint8_t cause; + uint8_t pin_cause[2]; + struct sim_hdr *sh; + struct gsm1111_response_ef *ef; + struct gsm1111_response_mfdf *mfdf; + struct gsm1111_response_mfdf_gsm *mfdf_gsm; + int i; + + /* ignore, if current job already gone */ + if (!sim->job_msg) { + LOGP(DSIM, LOGL_ERROR, "received APDU but no job, " + "please fix!\n"); + msgb_free(msg); + return 0; + } + + sh = (struct sim_hdr *)sim->job_msg->data; + payload = sim->job_msg->data + sizeof(*sh); + payload_len = sim->job_msg->len - sizeof(*sh); + + /* process status */ + if (length < 2) { + msgb_free(msg); + return 0; + } + sw1 = data[length - 2]; + sw2 = data[length - 1]; + length -= 2; + LOGP(DSIM, LOGL_INFO, "received APDU (len=%d sw1=0x%02x sw2=0x%02x)\n", + length, sw1, sw2); + + switch (sw1) { + case GSM1111_STAT_SECURITY: + LOGP(DSIM, LOGL_NOTICE, "SIM Security\n"); + /* error */ + if (sw2 != GSM1111_SEC_NO_ACCESS && sw2 != GSM1111_SEC_BLOCKED) + goto sim_error; + + /* select the right remaining counter an cause */ + // FIXME: read status to replace "*_remain"-counters + switch (sim->job_state) { + case SIM_JST_PIN1_UNBLOCK: + if (sw2 == GSM1111_SEC_NO_ACCESS) { + pin_cause[0] = SIM_CAUSE_PIN1_BLOCKED; + pin_cause[1] = --sim->unblk1_remain; + } else { + pin_cause[0] = SIM_CAUSE_PUC_BLOCKED; + pin_cause[1] = 0; + } + break; + case SIM_JST_PIN2_UNLOCK: + case SIM_JST_PIN2_CHANGE: + if (sw2 == GSM1111_SEC_NO_ACCESS && sim->chv2_remain) { + pin_cause[0] = SIM_CAUSE_PIN2_REQUIRED; + pin_cause[1] = sim->chv2_remain--; + } else { + pin_cause[0] = SIM_CAUSE_PIN2_BLOCKED; + pin_cause[1] = sim->unblk2_remain; + } + break; + case SIM_JST_PIN2_UNBLOCK: + if (sw2 == GSM1111_SEC_NO_ACCESS) { + pin_cause[0] = SIM_CAUSE_PIN2_BLOCKED; + pin_cause[1] = --sim->unblk2_remain; + } else { + pin_cause[0] = SIM_CAUSE_PUC_BLOCKED; + pin_cause[1] = 0; + } + case SIM_JST_PIN1_UNLOCK: + case SIM_JST_PIN1_CHANGE: + case SIM_JST_PIN1_DISABLE: + case SIM_JST_PIN1_ENABLE: + default: + if (sw2 == GSM1111_SEC_NO_ACCESS && sim->chv1_remain) { + pin_cause[0] = SIM_CAUSE_PIN1_REQUIRED; + pin_cause[1] = sim->chv1_remain--; + } else { + pin_cause[0] = SIM_CAUSE_PIN1_BLOCKED; + pin_cause[1] = sim->unblk1_remain; + } + break; + } + gsm_sim_reply(ms, SIM_JOB_ERROR, pin_cause, 2); + msgb_free(msg); + return 0; + case GSM1111_STAT_MEM_PROBLEM: + if (sw2 >= 0x40) { + LOGP(DSIM, LOGL_NOTICE, "memory of SIM failed\n"); + sim_error: + cause = SIM_CAUSE_SIM_ERROR; + gsm_sim_reply(ms, SIM_JOB_ERROR, &cause, 1); + msgb_free(msg); + return 0; + } + LOGP(DSIM, LOGL_NOTICE, "memory of SIM is bad (write took %d " + "times to succeed)\n", sw2); + /* fall through */ + case GSM1111_STAT_NORMAL: + case GSM1111_STAT_PROACTIVE: + case GSM1111_STAT_DL_ERROR: + case GSM1111_STAT_RESPONSE: + case GSM1111_STAT_RESPONSE_TOO: + LOGP(DSIM, LOGL_INFO, "command successfull\n"); + break; + default: + LOGP(DSIM, LOGL_INFO, "command failed\n"); + request_error: + cause = SIM_CAUSE_REQUEST_ERROR; + gsm_sim_reply(ms, SIM_JOB_ERROR, &cause, 1); + msgb_free(msg); + return 0; + } + + + switch (sim->job_state) { + /* step 1: after selecting MF / DF, request the response */ + case SIM_JST_SELECT_MFDF: + /* not enough data */ + if (sw2 < 22) { + LOGP(DSIM, LOGL_NOTICE, "expecting minimum 22 bytes\n"); + goto sim_error; + } + /* request response */ + sim->job_state = SIM_JST_SELECT_MFDF_RESP; + gsm1111_tx_get_response(ms, sw2); + msgb_free(msg); + return 0; + /* step 2: after getting response of selecting MF / DF, continue + * to "process_job". + */ + case SIM_JST_SELECT_MFDF_RESP: + if (length < 22) { + LOGP(DSIM, LOGL_NOTICE, "expecting minimum 22 bytes\n"); + goto sim_error; + } + mfdf = (struct gsm1111_response_mfdf *)data; + mfdf_gsm = (struct gsm1111_response_mfdf_gsm *)(data + 13); + sim->chv1_remain = mfdf_gsm->chv1_remain; + sim->chv2_remain = mfdf_gsm->chv2_remain; + sim->unblk1_remain = mfdf_gsm->unblk1_remain; + sim->unblk2_remain = mfdf_gsm->unblk2_remain; + /* if MF was selected */ + if (sim->path[0] == 0) { + /* if MF was selected, but MF is not indicated */ + if (ntohs(mfdf->file_id) != 0x3f00) { + LOGP(DSIM, LOGL_NOTICE, "Not MF\n"); + goto sim_error; + } + /* if MF was selected, but type is not indicated */ + if (mfdf->tof != GSM1111_TOF_MF) { + LOGP(DSIM, LOGL_NOTICE, "MF %02x != %02x " + "%04x\n", mfdf->tof, GSM1111_TOF_MF, + sim->path[0]); + goto sim_error; + } + /* now continue */ + msgb_free(msg); + return sim_process_job(ms); + } + /* if DF was selected, but this DF is not indicated */ + i = 0; + while (sim->path[i + 1]) + i++; + if (ntohs(mfdf->file_id) != sim->path[i]) { + LOGP(DSIM, LOGL_NOTICE, "Path %04x != %04x\n", + ntohs(mfdf->file_id), sim->path[i]); + goto sim_error; + } + /* if DF was selected, but type is not indicated */ + if (mfdf->tof != GSM1111_TOF_DF) { + LOGP(DSIM, LOGL_NOTICE, "TOF error\n"); + goto sim_error; + } + /* now continue */ + msgb_free(msg); + return sim_process_job(ms); + /* step 1: after selecting EF, request response of SELECT */ + case SIM_JST_SELECT_EF: + /* not enough data */ + if (sw2 < 14) { + LOGP(DSIM, LOGL_NOTICE, "expecting minimum 14 bytes\n"); + goto sim_error; + } + /* request response */ + sim->job_state = SIM_JST_SELECT_EF_RESP; + gsm1111_tx_get_response(ms, sw2); + msgb_free(msg); + return 0; + /* step 2: after getting response of selecting EF, do file command */ + case SIM_JST_SELECT_EF_RESP: + if (length < 14) { + LOGP(DSIM, LOGL_NOTICE, "expecting minimum 14 bytes\n"); + goto sim_error; + } + ef = (struct gsm1111_response_ef *)data; + /* if EF was selected, but type is not indicated */ + if (ntohs(ef->file_id) != sim->file) { + LOGP(DSIM, LOGL_NOTICE, "EF ID %04x != %04x\n", + ntohs(ef->file_id), sim->file); + goto sim_error; + } + /* check for record */ + if (length >= 15 && ef->length >= 2 && ef->structure != 0x00) { + /* get length of record */ + ef_len = ntohs(ef->file_size); + if (ef_len < data[14]) { + LOGP(DSIM, LOGL_NOTICE, "total length is " + "smaller (%d) than record size (%d)\n", + ef_len, data[14]); + goto request_error; + } + ef_len = data[14]; + LOGP(DSIM, LOGL_NOTICE, "selected record (len %d " + "structure %d)\n", ef_len, ef->structure); + } else { + /* get length of file */ + ef_len = ntohs(ef->file_size); + LOGP(DSIM, LOGL_NOTICE, "selected file (len %d)\n", + ef_len); + } + /* do file command */ + sim->job_state = SIM_JST_WAIT_FILE; + switch (sh->job_type) { + case SIM_JOB_READ_BINARY: + // FIXME: do chunks when greater or equal 256 bytes */ + gsm1111_tx_read_binary(ms, 0, ef_len); + break; + case SIM_JOB_UPDATE_BINARY: + // FIXME: do chunks when greater or equal 256 bytes */ + if (ef_len < payload_len) { + LOGP(DSIM, LOGL_NOTICE, "selected file is " + "smaller (%d) than data to update " + "(%d)\n", ef_len, payload_len); + goto request_error; + } + gsm1111_tx_update_binary(ms, 0, payload, payload_len); + break; + case SIM_JOB_READ_RECORD: + gsm1111_tx_read_record(ms, sh->rec_no, sh->rec_mode, + ef_len); + break; + case SIM_JOB_UPDATE_RECORD: + if (ef_len != payload_len) { + LOGP(DSIM, LOGL_NOTICE, "selected file length " + "(%d) does not equal record to update " + "(%d)\n", ef_len, payload_len); + goto request_error; + } + gsm1111_tx_update_record(ms, sh->rec_no, sh->rec_mode, + payload, payload_len); + break; + case SIM_JOB_SEEK_RECORD: + gsm1111_tx_seek(ms, sh->seek_type_mode, data, length); + break; + case SIM_JOB_INCREASE: + if (length != 4) { + LOGP(DSIM, LOGL_ERROR, "expecting uint32_t as " + "value lenght, but got %d bytes\n", + length); + goto request_error; + } + gsm1111_tx_increase(ms, *((uint32_t *)data)); + break; + case SIM_JOB_INVALIDATE: + gsm1111_tx_invalidate(ms); + break; + case SIM_JOB_REHABILITATE: + gsm1111_tx_rehabilitate(ms); + break; + } + msgb_free(msg); + return 0; + /* step 3: after processing file command, job is done */ + case SIM_JST_WAIT_FILE: + /* reply job with data */ + gsm_sim_reply(ms, SIM_JOB_OK, data, length); + msgb_free(msg); + return 0; + /* step 1: after running GSM algorithm, request response */ + case SIM_JST_RUN_GSM_ALGO: + /* not enough data */ + if (sw2 < 12) { + LOGP(DSIM, LOGL_NOTICE, "expecting minimum 12 bytes\n"); + goto sim_error; + } + /* request response */ + sim->job_state = SIM_JST_RUN_GSM_ALGO_RESP; + gsm1111_tx_get_response(ms, sw2); + msgb_free(msg); + return 0; + /* step 2: after processing GSM command, job is done */ + case SIM_JST_RUN_GSM_ALGO_RESP: + /* reply job with data */ + gsm_sim_reply(ms, SIM_JOB_OK, data, length); + msgb_free(msg); + return 0; + case SIM_JST_PIN1_UNLOCK: + case SIM_JST_PIN1_CHANGE: + case SIM_JST_PIN1_DISABLE: + case SIM_JST_PIN1_ENABLE: + case SIM_JST_PIN1_UNBLOCK: + case SIM_JST_PIN2_UNLOCK: + case SIM_JST_PIN2_CHANGE: + case SIM_JST_PIN2_UNBLOCK: + /* reply job with data */ + gsm_sim_reply(ms, SIM_JOB_OK, data, length); + msgb_free(msg); + return 0; + } + + LOGP(DSIM, LOGL_ERROR, "unknown state %u, please fix!\n", + sim->job_state); + goto request_error; +} + +/* + * API + */ + +/* open access to sim */ +uint32_t sim_open(struct osmocom_ms *ms, + void (*cb)(struct osmocom_ms *ms, struct msgb *msg)) +{ + struct gsm_sim *sim = &ms->sim; + struct gsm_sim_handler *handler; + + /* create handler and attach */ + handler = talloc_zero(l23_ctx, struct gsm_sim_handler); + if (!handler) + return 0; + handler->handle = new_handle++; + handler->cb = cb; + llist_add_tail(&handler->entry, &sim->handlers); + + return handler->handle; +} + +/* close access to sim */ +void sim_close(struct osmocom_ms *ms, uint32_t handle) +{ + struct gsm_sim *sim = &ms->sim; + struct gsm_sim_handler *handler; + + handler = sim_get_handler(sim, handle); + if (!handle) + return; + + /* kill ourself */ + llist_del(&handler->entry); + talloc_free(handler); +} + +/* send job */ +void sim_job(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_sim *sim = &ms->sim; + + msgb_enqueue(&sim->jobs, msg); +} + +/* + * init + */ + +int gsm_sim_init(struct osmocom_ms *ms) +{ + struct gsm_sim *sim = &ms->sim; + + /* current path is undefined, forching MF */ + sim->path[0] = 0x0bad; + sim->path[1] = 0; + sim->file = 0; + + INIT_LLIST_HEAD(&sim->handlers); + INIT_LLIST_HEAD(&sim->jobs); + + LOGP(DSIM, LOGL_INFO, "init SIM client\n"); + + return 0; +} + +int gsm_sim_exit(struct osmocom_ms *ms) +{ + struct gsm_sim *sim = &ms->sim; + struct gsm_sim_handler *handler, *handler2; + struct msgb *msg; + + LOGP(DSIM, LOGL_INFO, "exit SIM client\n"); + + /* remove pending job msg */ + if (sim->job_msg) { + msgb_free(sim->job_msg); + sim->job_msg = NULL; + } + /* flush handlers */ + llist_for_each_entry_safe(handler, handler2, &sim->handlers, entry) + sim_close(ms, handler->handle); + /* flush jobs */ + while ((msg = msgb_dequeue(&sim->jobs))) + msgb_free(msg); + + return 0; +} + + + + diff --git a/src/host/layer23/src/common/sysinfo.c b/src/host/layer23/src/common/sysinfo.c new file mode 100644 index 00000000..2816c266 --- /dev/null +++ b/src/host/layer23/src/common/sysinfo.c @@ -0,0 +1,870 @@ +/* + * (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <arpa/inet.h> + +#include <osmocom/core/bitvec.h> + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/networks.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/sysinfo.h> + +#define MIN(a, b) ((a < b) ? a : b) + +/* + * dumping + */ + +// FIXME: move to libosmocore +char *gsm_print_arfcn(uint16_t arfcn) +{ + static char text[10]; + + sprintf(text, "%d", arfcn & 1023); + if ((arfcn & ARFCN_PCS)) + strcat(text, "(PCS)"); + else if (arfcn >= 512 && arfcn <= 885) + strcat(text, "(DCS)"); + + return text; +} + +/* check if the cell 'talks' about DCS (0) or PCS (1) */ +uint8_t gsm_refer_pcs(uint16_t arfcn, struct gsm48_sysinfo *s) +{ + /* If ARFCN is PCS band, the cell refers to PCS */ + if ((arfcn & ARFCN_PCS)) + return 1; + + /* If no SI1 is available, we assume DCS. Be sure to call this + * function only if SI 1 is available. */ + if (!s->si1) + return 0; + + /* If band indicator indicates PCS band, the cell refers to PCSThe */ + return s->band_ind; +} + +int gsm48_sysinfo_dump(struct gsm48_sysinfo *s, uint16_t arfcn, + void (*print)(void *, const char *, ...), void *priv, uint8_t *freq_map) +{ + char buffer[81]; + int i, j, k, index; + int refer_pcs = gsm_refer_pcs(arfcn, s); + + /* available sysinfos */ + print(priv, "ARFCN = %s channels 512+ refer to %s\n", + gsm_print_arfcn(arfcn), + (refer_pcs) ? "PCS (1900)" : "DCS (1800)"); + print(priv, "Available SYSTEM INFORMATIONS ="); + if (s->si1) + print(priv, " 1"); + if (s->si2) + print(priv, " 2"); + if (s->si2bis) + print(priv, " 2bis"); + if (s->si2ter) + print(priv, " 2ter"); + if (s->si3) + print(priv, " 3"); + if (s->si4) + print(priv, " 4"); + if (s->si5) + print(priv, " 5"); + if (s->si5bis) + print(priv, " 5bis"); + if (s->si5ter) + print(priv, " 5ter"); + if (s->si6) + print(priv, " 6"); + print(priv, "\n"); + print(priv, "\n"); + + /* frequency list */ + j = 0; k = 0; + for (i = 0; i < 1024; i++) { + if ((s->freq[i].mask & FREQ_TYPE_SERV)) { + if (!k) { + sprintf(buffer, "serv. cell : "); + j = strlen(buffer); + } + if (j >= 75) { + buffer[j - 1] = '\0'; + print(priv, "%s\n", buffer); + sprintf(buffer, " "); + j = strlen(buffer); + } + sprintf(buffer + j, "%d,", i); + j = strlen(buffer); + k++; + } + } + if (j) { + buffer[j - 1] = '\0'; + print(priv, "%s\n", buffer); + } + j = 0; k = 0; + for (i = 0; i < 1024; i++) { + if ((s->freq[i].mask & FREQ_TYPE_NCELL)) { + if (!k) { + sprintf(buffer, "SI2 (neigh.) BA=%d: ", + s->nb_ba_ind_si2); + j = strlen(buffer); + } + if (j >= 70) { + buffer[j - 1] = '\0'; + print(priv, "%s\n", buffer); + sprintf(buffer, " "); + j = strlen(buffer); + } + sprintf(buffer + j, "%d,", i); + j = strlen(buffer); + k++; + } + } + if (j) { + buffer[j - 1] = '\0'; + print(priv, "%s\n", buffer); + } + j = 0; k = 0; + for (i = 0; i < 1024; i++) { + if ((s->freq[i].mask & FREQ_TYPE_REP)) { + if (!k) { + sprintf(buffer, "SI5 (report) BA=%d: ", + s->nb_ba_ind_si5); + j = strlen(buffer); + } + if (j >= 70) { + buffer[j - 1] = '\0'; + print(priv, "%s\n", buffer); + sprintf(buffer, " "); + j = strlen(buffer); + } + sprintf(buffer + j, "%d,", i); + j = strlen(buffer); + k++; + } + } + if (j) { + buffer[j - 1] = '\0'; + print(priv, "%s\n", buffer); + } + print(priv, "\n"); + + /* frequency map */ + for (i = 0; i < 1024; i += 64) { + sprintf(buffer, " %3d ", i); + for (j = 0; j < 64; j++) { + index = i+j; + if (refer_pcs && index >= 512 && index <= 885) + index = index-512+1024; + if ((s->freq[i+j].mask & FREQ_TYPE_SERV)) + buffer[j + 5] = 'S'; + else if ((s->freq[i+j].mask & FREQ_TYPE_NCELL) + && (s->freq[i+j].mask & FREQ_TYPE_REP)) + buffer[j + 5] = 'b'; + else if ((s->freq[i+j].mask & FREQ_TYPE_NCELL)) + buffer[j + 5] = 'n'; + else if ((s->freq[i+j].mask & FREQ_TYPE_REP)) + buffer[j + 5] = 'r'; + else if (!freq_map || (freq_map[index >> 3] + & (1 << (index & 7)))) + buffer[j + 5] = '.'; + else + buffer[j + 5] = ' '; + } + for (; j < 64; j++) + buffer[j + 5] = ' '; + sprintf(buffer + 69, " %d", i + 63); + print(priv, "%s\n", buffer); + } + print(priv, " 'S' = serv. cell 'n' = SI2 (neigh.) 'r' = SI5 (rep.) " + "'b' = SI2+SI5\n\n"); + + /* serving cell */ + print(priv, "Serving Cell:\n"); + print(priv, " BSIC = %d,%d MCC = %s MNC = %s LAC = 0x%04x Cell ID " + "= 0x%04x\n", s->bsic >> 3, s->bsic & 0x7, + gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc), s->lac, + s->cell_id); + print(priv, " Country = %s Network Name = %s\n", gsm_get_mcc(s->mcc), + gsm_get_mnc(s->mcc, s->mnc)); + print(priv, " MAX_RETRANS = %d TX_INTEGER = %d re-establish = %s\n", + s->max_retrans, s->tx_integer, + (s->reest_denied) ? "denied" : "allowed"); + print(priv, " Cell barred = %s barred classes =", + (s->cell_barr ? "yes" : "no")); + for (i = 0; i < 16; i++) { + if ((s->class_barr & (1 << i))) + print(priv, " C%d", i); + } + print(priv, "\n"); + if (s->sp) + print(priv, " CBQ = %d CRO = %d TEMP_OFFSET = %d " + "PENALTY_TIME = %d\n", s->sp_cbq, s->sp_cro, s->sp_to, + s->sp_pt); + if (s->nb_ncc_permitted_si2) { + print(priv, "NCC Permitted BCCH ="); + for (i = 0; i < 8; i++) + if ((s->nb_ncc_permitted_si2 & (1 << i))) + print(priv, " %d", i); + print(priv, "\n"); + } + if (s->nb_ncc_permitted_si6) { + print(priv, "NCC Permitted SACCH/TCH ="); + for (i = 0; i < 8; i++) + if ((s->nb_ncc_permitted_si6 & (1 << i))) + print(priv, " %d", i); + print(priv, "\n"); + } + print(priv, "\n"); + + /* neighbor cell */ + print(priv, "Neighbor Cell:\n"); + print(priv, " MAX_RETRANS = %d TX_INTEGER = %d re-establish = %s\n", + s->nb_max_retrans, s->nb_tx_integer, + (s->nb_reest_denied) ? "denied" : "allowed"); + print(priv, " Cell barred = %s barred classes =", + (s->nb_cell_barr ? "yes" : "no")); + for (i = 0; i < 16; i++) { + if ((s->nb_class_barr & (1 << i))) + print(priv, " C%d", i); + } + print(priv, "\n"); + print(priv, "\n"); + + /* cell selection */ + print(priv, "MX_TXPWR_MAX_CCCH = %d CRH = %d RXLEV_MIN = %d " + "NECI = %d ACS = %d\n", s->ms_txpwr_max_cch, + s->cell_resel_hyst_db, s->rxlev_acc_min_db, s->neci, s->acs); + + /* bcch options */ + print(priv, "BCCH link timeout = %d DTX = %d PWRC = %d\n", + s->bcch_radio_link_timeout, s->bcch_dtx, s->bcch_pwrc); + + /* sacch options */ + print(priv, "SACCH link timeout = %d DTX = %d PWRC = %d\n", + s->sacch_radio_link_timeout, s->sacch_dtx, s->sacch_pwrc); + + /* control channel */ + switch(s->ccch_conf) { + case 0: + case 2: + case 4: + case 6: + print(priv, "CCCH Config = %d CCCH", (s->ccch_conf >> 1) + 1); + break; + case 1: + print(priv, "CCCH Config = 1 CCCH + SDCCH"); + break; + default: + print(priv, "CCCH Config = reserved"); + } + print(priv, " BS-PA-MFMS = %d Attachment = %s\n", + s->pag_mf_periods, (s->att_allowed) ? "allowed" : "denied"); + print(priv, "BS-AG_BLKS_RES = %d ", s->bs_ag_blks_res); + if (s->t3212) + print(priv, "T3212 = %d sec.\n", s->t3212); + else + print(priv, "T3212 = disabled\n", s->t3212); + + /* channel description */ + if (s->h) + print(priv, "chan_nr = 0x%02x TSC = %d MAIO = %d HSN = %d\n", + s->chan_nr, s->tsc, s->maio, s->hsn); + else + print(priv, "chan_nr = 0x%02x TSC = %d ARFCN = %d\n", + s->chan_nr, s->tsc, s->arfcn); + print(priv, "\n"); + + return 0; +} + +/* + * decoding + */ + +int gsm48_decode_chan_h0(struct gsm48_chan_desc *cd, uint8_t *tsc, + uint16_t *arfcn) +{ + *tsc = cd->h0.tsc; + *arfcn = cd->h0.arfcn_low | (cd->h0.arfcn_high << 8); + + return 0; +} + +int gsm48_decode_chan_h1(struct gsm48_chan_desc *cd, uint8_t *tsc, + uint8_t *maio, uint8_t *hsn) +{ + *tsc = cd->h1.tsc; + *maio = cd->h1.maio_low | (cd->h1.maio_high << 2); + *hsn = cd->h1.hsn; + + return 0; +} + +/* decode "Cell Channel Description" (10.5.2.1b) and other frequency lists */ +static int decode_freq_list(struct gsm_sysinfo_freq *f, uint8_t *cd, + uint8_t len, uint8_t mask, uint8_t frqt) +{ +#if 0 + /* only Bit map 0 format for P-GSM */ + if ((cd[0] & 0xc0 & mask) != 0x00 && + (set->p_gsm && !set->e_gsm && !set->r_gsm && !set->dcs)) + return 0; +#endif + + return gsm48_decode_freq_list(f, cd, len, mask, frqt); +} + +/* decode "Cell Selection Parameters" (10.5.2.4) */ +static int gsm48_decode_cell_sel_param(struct gsm48_sysinfo *s, + struct gsm48_cell_sel_par *cs) +{ + s->ms_txpwr_max_cch = cs->ms_txpwr_max_ccch; + s->cell_resel_hyst_db = cs->cell_resel_hyst * 2; + s->rxlev_acc_min_db = cs->rxlev_acc_min - 110; + s->neci = cs->neci; + s->acs = cs->acs; + + return 0; +} + +/* decode "Cell Options (BCCH)" (10.5.2.3) */ +static int gsm48_decode_cellopt_bcch(struct gsm48_sysinfo *s, + struct gsm48_cell_options *co) +{ + s->bcch_radio_link_timeout = (co->radio_link_timeout + 1) * 4; + s->bcch_dtx = co->dtx; + s->bcch_pwrc = co->pwrc; + + return 0; +} + +/* decode "Cell Options (SACCH)" (10.5.2.3a) */ +static int gsm48_decode_cellopt_sacch(struct gsm48_sysinfo *s, + struct gsm48_cell_options *co) +{ + s->sacch_radio_link_timeout = (co->radio_link_timeout + 1) * 4; + s->sacch_dtx = co->dtx; + s->sacch_pwrc = co->pwrc; + + return 0; +} + +/* decode "Control Channel Description" (10.5.2.11) */ +static int gsm48_decode_ccd(struct gsm48_sysinfo *s, + struct gsm48_control_channel_descr *cc) +{ + s->ccch_conf = cc->ccch_conf; + s->bs_ag_blks_res = cc->bs_ag_blks_res; + s->att_allowed = cc->att; + s->pag_mf_periods = cc->bs_pa_mfrms + 2; + s->t3212 = cc->t3212 * 360; /* convert deci-hours to seconds */ + + return 0; +} + +/* decode "Mobile Allocation" (10.5.2.21) */ +int gsm48_decode_mobile_alloc(struct gsm_sysinfo_freq *freq, + uint8_t *ma, uint8_t len, uint16_t *hopping, uint8_t *hopp_len, int si4) +{ + int i, j = 0; + uint16_t f[len << 3]; + + /* not more than 64 hopping indexes allowed in IE */ + if (len > 8) + return -EINVAL; + + /* tabula rasa */ + *hopp_len = 0; + if (si4) { + for (i = 0; i < 1024; i++) + freq[i].mask &= ~FREQ_TYPE_HOPP; + } + + /* generating list of all frequencies (1..1023,0) */ + for (i = 1; i <= 1024; i++) { + if ((freq[i & 1023].mask & FREQ_TYPE_SERV)) { + LOGP(DRR, LOGL_INFO, "Serving cell ARFCN #%d: %d\n", + j, i & 1023); + f[j++] = i & 1023; + if (j == (len << 3)) + break; + } + } + + /* fill hopping table with frequency index given by IE + * and set hopping type bits + */ + for (i = 0; i < (len << 3); i++) { + /* if bit is set, this frequency index is used for hopping */ + if ((ma[len - 1 - (i >> 3)] & (1 << (i & 7)))) { + LOGP(DRR, LOGL_INFO, "Hopping ARFCN: %d (bit %d)\n", + i, f[i]); + /* index higher than entries in list ? */ + if (i >= j) { + LOGP(DRR, LOGL_NOTICE, "Mobile Allocation " + "hopping index %d exceeds maximum " + "number of cell frequencies. (%d)\n", + i + 1, j); + break; + } + hopping[(*hopp_len)++] = f[i]; + if (si4) + freq[f[i]].mask |= FREQ_TYPE_HOPP; + } + } + + return 0; +} + +/* Rach Control decode tables */ +static uint8_t gsm48_max_retrans[4] = { + 1, 2, 4, 7 +}; +static uint8_t gsm48_tx_integer[16] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 25, 32, 50 +}; + +/* decode "RACH Control Parameter" (10.5.2.29) */ +static int gsm48_decode_rach_ctl_param(struct gsm48_sysinfo *s, + struct gsm48_rach_control *rc) +{ + s->reest_denied = rc->re; + s->cell_barr = rc->cell_bar; + s->tx_integer = gsm48_tx_integer[rc->tx_integer]; + s->max_retrans = gsm48_max_retrans[rc->max_trans]; + s->class_barr = (rc->t2 << 8) | rc->t3; + + return 0; +} +static int gsm48_decode_rach_ctl_neigh(struct gsm48_sysinfo *s, + struct gsm48_rach_control *rc) +{ + s->nb_reest_denied = rc->re; + s->nb_cell_barr = rc->cell_bar; + s->nb_tx_integer = gsm48_tx_integer[rc->tx_integer]; + s->nb_max_retrans = gsm48_max_retrans[rc->max_trans]; + s->nb_class_barr = (rc->t2 << 8) | rc->t3; + + return 0; +} + +/* decode "SI 1 Rest Octets" (10.5.2.32) */ +static int gsm48_decode_si1_rest(struct gsm48_sysinfo *s, uint8_t *si, + uint8_t len) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data_len = len; + bv.data = si; + + /* Optional Selection Parameters */ + if (bitvec_get_bit_high(&bv) == H) { + s->nch = 1; + s->nch_position = bitvec_get_uint(&bv, 5); + } else + s->nch = 0; + if (bitvec_get_bit_high(&bv) == H) + s->band_ind = 1; + else + s->band_ind = 0; + + return 0; +} + +/* decode "SI 3 Rest Octets" (10.5.2.34) */ +static int gsm48_decode_si3_rest(struct gsm48_sysinfo *s, uint8_t *si, + uint8_t len) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data_len = len; + bv.data = si; + + /* Optional Selection Parameters */ + if (bitvec_get_bit_high(&bv) == H) { + s->sp = 1; + s->sp_cbq = bitvec_get_uint(&bv, 1); + s->sp_cro = bitvec_get_uint(&bv, 6); + s->sp_to = bitvec_get_uint(&bv, 3); + s->sp_pt = bitvec_get_uint(&bv, 5); + } else + s->sp = 0; + /* Optional Power Offset */ + if (bitvec_get_bit_high(&bv) == H) { + s->po = 1; + s->po_value = bitvec_get_uint(&bv, 2); + } else + s->po = 0; + /* System Onformation 2ter Indicator */ + if (bitvec_get_bit_high(&bv) == H) + s->si2ter_ind = 1; + else + s->si2ter_ind = 0; + /* Early Classark Sending Control */ + if (bitvec_get_bit_high(&bv) == H) + s->ecsm = 1; + else + s->ecsm = 0; + /* Scheduling if and where */ + if (bitvec_get_bit_high(&bv) == H) { + s->sched = 1; + s->sched_where = bitvec_get_uint(&bv, 3); + } else + s->sched = 0; + /* GPRS Indicator */ + if (bitvec_get_bit_high(&bv) == H) { + s->gprs = 1; + s->gprs_ra_colour = bitvec_get_uint(&bv, 3); + s->gprs_si13_pos = bitvec_get_uint(&bv, 1); + } else + s->gprs = 0; + + return 0; +} + +/* decode "SI 4 Rest Octets" (10.5.2.35) */ +static int gsm48_decode_si4_rest(struct gsm48_sysinfo *s, uint8_t *si, + uint8_t len) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data_len = len; + bv.data = si; + + /* Optional Selection Parameters */ + if (bitvec_get_bit_high(&bv) == H) { + s->sp = 1; + s->sp_cbq = bitvec_get_uint(&bv, 1); + s->sp_cro = bitvec_get_uint(&bv, 6); + s->sp_to = bitvec_get_uint(&bv, 3); + s->sp_pt = bitvec_get_uint(&bv, 5); + } else + s->sp = 0; + /* Optional Power Offset */ + if (bitvec_get_bit_high(&bv) == H) { + s->po = 1; + s->po_value = bitvec_get_uint(&bv, 3); + } else + s->po = 0; + /* GPRS Indicator */ + if (bitvec_get_bit_high(&bv) == H) { + s->gprs = 1; + s->gprs_ra_colour = bitvec_get_uint(&bv, 3); + s->gprs_si13_pos = bitvec_get_uint(&bv, 1); + } else + s->gprs = 0; + // todo: more rest octet bits + + return 0; +} + +/* decode "SI 6 Rest Octets" (10.5.2.35a) */ +static int gsm48_decode_si6_rest(struct gsm48_sysinfo *s, uint8_t *si, + uint8_t len) +{ + return 0; +} + +int gsm48_decode_sysinfo1(struct gsm48_sysinfo *s, + struct gsm48_system_information_type_1 *si, int len) +{ + int payload_len = len - sizeof(*si); + + memcpy(s->si1_msg, si, MIN(len, sizeof(s->si1_msg))); + + /* Cell Channel Description */ + decode_freq_list(s->freq, si->cell_channel_description, + sizeof(si->cell_channel_description), 0xce, FREQ_TYPE_SERV); + /* RACH Control Parameter */ + gsm48_decode_rach_ctl_param(s, &si->rach_control); + /* SI 1 Rest Octets */ + if (payload_len) + gsm48_decode_si1_rest(s, si->rest_octets, payload_len); + + s->si1 = 1; + + if (s->si4) { + LOGP(DRR, LOGL_NOTICE, "Now updating previously received " + "SYSTEM INFORMATION 4\n"); + gsm48_decode_sysinfo4(s, + (struct gsm48_system_information_type_4 *) s->si4_msg, + sizeof(s->si4_msg)); + } + + return 0; +} + +int gsm48_decode_sysinfo2(struct gsm48_sysinfo *s, + struct gsm48_system_information_type_2 *si, int len) +{ + memcpy(s->si2_msg, si, MIN(len, sizeof(s->si2_msg))); + + /* Neighbor Cell Description */ + s->nb_ext_ind_si2 = (si->bcch_frequency_list[0] >> 6) & 1; + s->nb_ba_ind_si2 = (si->bcch_frequency_list[0] >> 5) & 1; + decode_freq_list(s->freq, si->bcch_frequency_list, + sizeof(si->bcch_frequency_list), 0xce, FREQ_TYPE_NCELL_2); + /* NCC Permitted */ + s->nb_ncc_permitted_si2 = si->ncc_permitted; + /* RACH Control Parameter */ + gsm48_decode_rach_ctl_neigh(s, &si->rach_control); + + s->si2 = 1; + + return 0; +} + +int gsm48_decode_sysinfo2bis(struct gsm48_sysinfo *s, + struct gsm48_system_information_type_2bis *si, int len) +{ + memcpy(s->si2b_msg, si, MIN(len, sizeof(s->si2b_msg))); + + /* Neighbor Cell Description */ + s->nb_ext_ind_si2bis = (si->bcch_frequency_list[0] >> 6) & 1; + s->nb_ba_ind_si2bis = (si->bcch_frequency_list[0] >> 5) & 1; + decode_freq_list(s->freq, si->bcch_frequency_list, + sizeof(si->bcch_frequency_list), 0xce, FREQ_TYPE_NCELL_2bis); + /* RACH Control Parameter */ + gsm48_decode_rach_ctl_neigh(s, &si->rach_control); + + s->si2bis = 1; + + return 0; +} + +int gsm48_decode_sysinfo2ter(struct gsm48_sysinfo *s, + struct gsm48_system_information_type_2ter *si, int len) +{ + memcpy(s->si2t_msg, si, MIN(len, sizeof(s->si2t_msg))); + + /* Neighbor Cell Description 2 */ + s->nb_multi_rep_si2ter = (si->ext_bcch_frequency_list[0] >> 6) & 3; + s->nb_ba_ind_si2ter = (si->ext_bcch_frequency_list[0] >> 5) & 1; + decode_freq_list(s->freq, si->ext_bcch_frequency_list, + sizeof(si->ext_bcch_frequency_list), 0x8e, + FREQ_TYPE_NCELL_2ter); + + s->si2ter = 1; + + return 0; +} + +int gsm48_decode_sysinfo3(struct gsm48_sysinfo *s, + struct gsm48_system_information_type_3 *si, int len) +{ + int payload_len = len - sizeof(*si); + + memcpy(s->si3_msg, si, MIN(len, sizeof(s->si3_msg))); + + /* Cell Identity */ + s->cell_id = ntohs(si->cell_identity); + /* LAI */ + gsm48_decode_lai_hex(&si->lai, &s->mcc, &s->mnc, &s->lac); + /* Control Channel Description */ + gsm48_decode_ccd(s, &si->control_channel_desc); + /* Cell Options (BCCH) */ + gsm48_decode_cellopt_bcch(s, &si->cell_options); + /* Cell Selection Parameters */ + gsm48_decode_cell_sel_param(s, &si->cell_sel_par); + /* RACH Control Parameter */ + gsm48_decode_rach_ctl_param(s, &si->rach_control); + /* SI 3 Rest Octets */ + if (payload_len >= 4) + gsm48_decode_si3_rest(s, si->rest_octets, payload_len); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 3 (mcc %s mnc %s " + "lac 0x%04x)\n", gsm_print_mcc(s->mcc), + gsm_print_mnc(s->mnc), s->lac); + + s->si3 = 1; + + return 0; +} + +int gsm48_decode_sysinfo4(struct gsm48_sysinfo *s, + struct gsm48_system_information_type_4 *si, int len) +{ + int payload_len = len - sizeof(*si); + + uint8_t *data = si->data; + struct gsm48_chan_desc *cd; + + memcpy(s->si4_msg, si, MIN(len, sizeof(s->si4_msg))); + + /* LAI */ + gsm48_decode_lai_hex(&si->lai, &s->mcc, &s->mnc, &s->lac); + /* Cell Selection Parameters */ + gsm48_decode_cell_sel_param(s, &si->cell_sel_par); + /* RACH Control Parameter */ + gsm48_decode_rach_ctl_param(s, &si->rach_control); + + /* CBCH Channel Description */ + if (payload_len >= 1 && data[0] == GSM48_IE_CBCH_CHAN_DESC) { + if (payload_len < 4) { +short_read: + LOGP(DRR, LOGL_NOTICE, "Short read!\n"); + return -EIO; + } + cd = (struct gsm48_chan_desc *) (data + 1); + s->chan_nr = cd->chan_nr; + if (cd->h0.h) { + s->h = 1; + gsm48_decode_chan_h1(cd, &s->tsc, &s->maio, &s->hsn); + } else { + s->h = 0; + gsm48_decode_chan_h0(cd, &s->tsc, &s->arfcn); + } + payload_len -= 4; + data += 4; + } + /* CBCH Mobile Allocation */ + if (payload_len >= 1 && data[0] == GSM48_IE_CBCH_MOB_AL) { + if (payload_len < 1 || payload_len < 2 + data[1]) + goto short_read; + if (!s->si1) { + LOGP(DRR, LOGL_NOTICE, "Ignoring CBCH allocation of " + "SYSTEM INFORMATION 4 until SI 1 is " + "received.\n"); + gsm48_decode_mobile_alloc(s->freq, data + 2, data[1], + s->hopping, &s->hopp_len, 1); + } + payload_len -= 2 + data[1]; + data += 2 + data[1]; + } + /* SI 4 Rest Octets */ + if (payload_len > 0) + gsm48_decode_si4_rest(s, data, payload_len); + + s->si4 = 1; + + return 0; +} + +int gsm48_decode_sysinfo5(struct gsm48_sysinfo *s, + struct gsm48_system_information_type_5 *si, int len) +{ + memcpy(s->si5_msg, si, MIN(len, sizeof(s->si5_msg))); + + /* Neighbor Cell Description */ + s->nb_ext_ind_si5 = (si->bcch_frequency_list[0] >> 6) & 1; + s->nb_ba_ind_si5 = (si->bcch_frequency_list[0] >> 5) & 1; + decode_freq_list(s->freq, si->bcch_frequency_list, + sizeof(si->bcch_frequency_list), 0xce, FREQ_TYPE_REP_5); + + s->si5 = 1; + + return 0; +} + +int gsm48_decode_sysinfo5bis(struct gsm48_sysinfo *s, + struct gsm48_system_information_type_5bis *si, int len) +{ + memcpy(s->si5b_msg, si, MIN(len, sizeof(s->si5b_msg))); + + /* Neighbor Cell Description */ + s->nb_ext_ind_si5bis = (si->bcch_frequency_list[0] >> 6) & 1; + s->nb_ba_ind_si5bis = (si->bcch_frequency_list[0] >> 5) & 1; + decode_freq_list(s->freq, si->bcch_frequency_list, + sizeof(si->bcch_frequency_list), 0xce, FREQ_TYPE_REP_5bis); + + s->si5bis = 1; + + return 0; +} + +int gsm48_decode_sysinfo5ter(struct gsm48_sysinfo *s, + struct gsm48_system_information_type_5ter *si, int len) +{ + memcpy(s->si5t_msg, si, MIN(len, sizeof(s->si5t_msg))); + + /* Neighbor Cell Description */ + s->nb_multi_rep_si5ter = (si->bcch_frequency_list[0] >> 6) & 3; + s->nb_ba_ind_si5ter = (si->bcch_frequency_list[0] >> 5) & 1; + decode_freq_list(s->freq, si->bcch_frequency_list, + sizeof(si->bcch_frequency_list), 0x8e, FREQ_TYPE_REP_5ter); + + s->si5ter = 1; + + return 0; +} + +int gsm48_decode_sysinfo6(struct gsm48_sysinfo *s, + struct gsm48_system_information_type_6 *si, int len) +{ + int payload_len = len - sizeof(*si); + + memcpy(s->si6_msg, si, MIN(len, sizeof(s->si6_msg))); + + /* Cell Identity */ + if (s->si6 && s->cell_id != ntohs(si->cell_identity)) + LOGP(DRR, LOGL_INFO, "Cell ID on SI 6 differs from previous " + "read.\n"); + s->cell_id = ntohs(si->cell_identity); + /* LAI */ + gsm48_decode_lai_hex(&si->lai, &s->mcc, &s->mnc, &s->lac); + /* Cell Options (SACCH) */ + gsm48_decode_cellopt_sacch(s, &si->cell_options); + /* NCC Permitted */ + s->nb_ncc_permitted_si6 = si->ncc_permitted; + /* SI 6 Rest Octets */ + if (payload_len >= 4) + gsm48_decode_si6_rest(s, si->rest_octets, payload_len); + + s->si6 = 1; + + return 0; +} + +int gsm48_encode_lai_hex(struct gsm48_loc_area_id *lai, uint16_t mcc, + uint16_t mnc, uint16_t lac) +{ + lai->digits[0] = (mcc >> 8) | (mcc & 0xf0); + lai->digits[1] = (mcc & 0x0f) | (mnc << 4); + lai->digits[2] = (mnc >> 8) | (mnc & 0xf0); + lai->lac = htons(lac); + + return 0; +} + + int gsm48_decode_lai_hex(struct gsm48_loc_area_id *lai, uint16_t *mcc, + uint16_t *mnc, uint16_t *lac) +{ + *mcc = ((lai->digits[0] & 0x0f) << 8) + | (lai->digits[0] & 0xf0) + | (lai->digits[1] & 0x0f); + *mnc = ((lai->digits[2] & 0x0f) << 8) + | (lai->digits[2] & 0xf0) + | ((lai->digits[1] & 0xf0) >> 4); + *lac = ntohs(lai->lac); + + return 0; +} + diff --git a/src/host/layer23/src/misc/Makefile.am b/src/host/layer23/src/misc/Makefile.am new file mode 100644 index 00000000..d8fb3222 --- /dev/null +++ b/src/host/layer23/src/misc/Makefile.am @@ -0,0 +1,13 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBGPS_CFLAGS) +LDADD = ../common/liblayer23.a $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBGPS_LIBS) + +bin_PROGRAMS = bcch_scan ccch_scan echo_test cell_log cbch_sniff + +bcch_scan_SOURCES = ../common/main.c app_bcch_scan.c bcch_scan.c +ccch_scan_SOURCES = ../common/main.c app_ccch_scan.c rslms.c +echo_test_SOURCES = ../common/main.c app_echo_test.c +cell_log_LDADD = $(LDADD) -lm +cell_log_SOURCES = ../common/main.c app_cell_log.c cell_log.c \ + ../../../gsmmap/geo.c +cbch_sniff_SOURCES = ../common/main.c app_cbch_sniff.c diff --git a/src/host/layer23/src/misc/app_bcch_scan.c b/src/host/layer23/src/misc/app_bcch_scan.c new file mode 100644 index 00000000..7b21ed79 --- /dev/null +++ b/src/host/layer23/src/misc/app_bcch_scan.c @@ -0,0 +1,69 @@ +/* "Application" code of the layer2/3 stack */ + +/* (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/l23_app.h> +#include <osmocom/bb/misc/layer3.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/signal.h> + +#include <l1ctl_proto.h> + +static int signal_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct osmocom_ms *ms; + + if (subsys != SS_L1CTL) + return 0; + + switch (signal) { + case S_L1CTL_RESET: + ms = signal_data; + return fps_start(ms); + } + return 0; +} + +int l23_app_init(struct osmocom_ms *ms) +{ + /* don't do layer3_init() as we don't want an actualy L3 */ + fps_init(ms); + l1ctl_tx_reset_req(ms, L1CTL_RES_T_FULL); + return osmo_signal_register_handler(SS_L1CTL, &signal_cb, NULL); +} + +static struct l23_app_info info = { + .copyright = "Copyright (C) 2010 Harald Welte <laforge@gnumonks.org>\n", + .contribution = "Contributions by Holger Hans Peter Freyther\n", +}; + +struct l23_app_info *l23_app_info() +{ + return &info; +} diff --git a/src/host/layer23/src/misc/app_cbch_sniff.c b/src/host/layer23/src/misc/app_cbch_sniff.c new file mode 100644 index 00000000..2f45e483 --- /dev/null +++ b/src/host/layer23/src/misc/app_cbch_sniff.c @@ -0,0 +1,201 @@ +/* CBCH passive sniffer */ + +/* (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2010 by Alex Badea <vamposdecampos@gmail.com> + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/l23_app.h> +#include <osmocom/bb/misc/layer3.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/signal.h> +#include <osmocom/gsm/rsl.h> + +#include <l1ctl_proto.h> + +struct osmocom_ms *g_ms; +struct gsm48_sysinfo g_sysinfo = {}; + +static int try_cbch(struct osmocom_ms *ms, struct gsm48_sysinfo *s) +{ + if (!s->si1 || !s->si4) + return 0; + if (!s->chan_nr) { + LOGP(DRR, LOGL_INFO, "no CBCH chan_nr found\n"); + return 0; + } + + if (s->h) { + LOGP(DRR, LOGL_INFO, "chan_nr = 0x%02x TSC = %d MAIO = %d " + "HSN = %d hseq (%d): %s\n", + s->chan_nr, s->tsc, s->maio, s->hsn, + s->hopp_len, + osmo_hexdump((unsigned char *) s->hopping, s->hopp_len * 2)); + return l1ctl_tx_dm_est_req_h1(ms, + s->maio, s->hsn, s->hopping, s->hopp_len, + s->chan_nr, s->tsc, + GSM48_CMODE_SIGN, 0); + } else { + LOGP(DRR, LOGL_INFO, "chan_nr = 0x%02x TSC = %d ARFCN = %d\n", + s->chan_nr, s->tsc, s->arfcn); + return l1ctl_tx_dm_est_req_h0(ms, s->arfcn, + s->chan_nr, s->tsc, GSM48_CMODE_SIGN, 0); + } +} + + +/* receive BCCH at RR layer */ +static int bcch(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_system_information_type_header *sih = msgb_l3(msg); + struct gsm48_sysinfo *s = &g_sysinfo; + + if (msgb_l3len(msg) != 23) { + LOGP(DRR, LOGL_NOTICE, "Invalid BCCH message length\n"); + return -EINVAL; + } + switch (sih->system_information) { + case GSM48_MT_RR_SYSINFO_1: + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 1\n"); + gsm48_decode_sysinfo1(s, + (struct gsm48_system_information_type_1 *) sih, + msgb_l3len(msg)); + return try_cbch(ms, s); + case GSM48_MT_RR_SYSINFO_4: + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 4\n"); + gsm48_decode_sysinfo4(s, + (struct gsm48_system_information_type_4 *) sih, + msgb_l3len(msg)); + return try_cbch(ms, s); + default: + return 0; + } +} + +static int unit_data_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + struct tlv_parsed tv; + uint8_t ch_type, ch_subch, ch_ts; + + DEBUGP(DRSL, "RSLms UNIT DATA IND chan_nr=0x%02x link_id=0x%02x\n", + rllh->chan_nr, rllh->link_id); + + rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg)-sizeof(*rllh)); + if (!TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) { + DEBUGP(DRSL, "UNIT_DATA_IND without L3 INFO ?!?\n"); + return -EIO; + } + msg->l3h = (uint8_t *) TLVP_VAL(&tv, RSL_IE_L3_INFO); + + rsl_dec_chan_nr(rllh->chan_nr, &ch_type, &ch_subch, &ch_ts); + switch (ch_type) { + case RSL_CHAN_BCCH: + return bcch(ms, msg); + default: + return 0; + } +} + +static int rcv_rll(struct osmocom_ms *ms, struct msgb *msg) +{ + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + int msg_type = rllh->c.msg_type; + + if (msg_type == RSL_MT_UNIT_DATA_IND) { + unit_data_ind(ms, msg); + } else + LOGP(DRSL, LOGL_NOTICE, "RSLms message unhandled\n"); + + msgb_free(msg); + + return 0; +} + +static int rcv_rsl(struct msgb *msg, struct lapdm_entity *le, void *l3ctx) +{ + struct osmocom_ms *ms = l3ctx; + struct abis_rsl_common_hdr *rslh = msgb_l2(msg); + int rc = 0; + + switch (rslh->msg_discr & 0xfe) { + case ABIS_RSL_MDISC_RLL: + rc = rcv_rll(ms, msg); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "unknown RSLms msg_discr 0x%02x\n", + rslh->msg_discr); + msgb_free(msg); + rc = -EINVAL; + break; + } + + return rc; +} + +static int signal_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct osmocom_ms *ms; + + if (subsys != SS_L1CTL) + return 0; + + switch (signal) { + case S_L1CTL_RESET: + case S_L1CTL_FBSB_ERR: + ms = g_ms; + return l1ctl_tx_fbsb_req(ms, ms->test_arfcn, + L1CTL_FBSB_F_FB01SB, 100, 0, CCCH_MODE_COMBINED); + case S_L1CTL_FBSB_RESP: + return 0; + } + return 0; +} + +int l23_app_init(struct osmocom_ms *ms) +{ + /* don't do layer3_init() as we don't want an actualy L3 */ + + g_ms = ms; + lapdm_channel_set_l3(&ms->lapdm_channel, &rcv_rsl, ms); + + l1ctl_tx_reset_req(ms, L1CTL_RES_T_FULL); + /* FIXME: L1CTL_RES_T_FULL doesn't reset dedicated mode + * (if previously set), so we release it here. */ + l1ctl_tx_dm_rel_req(ms); + return osmo_signal_register_handler(SS_L1CTL, &signal_cb, NULL); +} + +static struct l23_app_info info = { + .copyright = "Copyright (C) 2010 Harald Welte <laforge@gnumonks.org>\n", + .contribution = "Contributions by Holger Hans Peter Freyther\n", +}; + +struct l23_app_info *l23_app_info() +{ + return &info; +} diff --git a/src/host/layer23/src/misc/app_ccch_scan.c b/src/host/layer23/src/misc/app_ccch_scan.c new file mode 100644 index 00000000..d301b7b7 --- /dev/null +++ b/src/host/layer23/src/misc/app_ccch_scan.c @@ -0,0 +1,509 @@ +/* CCCH passive sniffer */ +/* (C) 2010-2011 by Holger Hans Peter Freyther + * (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdint.h> +#include <errno.h> +#include <stdio.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/rsl.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/gsm/gsm48_ie.h> +#include <osmocom/gsm/gsm48.h> +#include <osmocom/core/signal.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/misc/rslms.h> +#include <osmocom/bb/misc/layer3.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/common/l23_app.h> + +#include <l1ctl_proto.h> + +static struct { + int has_si1; + int ccch_mode; + int ccch_enabled; + int rach_count; + struct gsm_sysinfo_freq cell_arfcns[1024]; +} app_state; + + +static void dump_bcch(struct osmocom_ms *ms, uint8_t tc, const uint8_t *data) +{ + struct gsm48_system_information_type_header *si_hdr; + si_hdr = (struct gsm48_system_information_type_header *) data; + + /* GSM 05.02 §6.3.1.3 Mapping of BCCH data */ + switch (si_hdr->system_information) { + case GSM48_MT_RR_SYSINFO_1: +#ifdef BCCH_TC_CHECK + if (tc != 0) + LOGP(DRR, LOGL_ERROR, "SI1 on the wrong TC: %d\n", tc); +#endif + if (!app_state.has_si1) { + struct gsm48_system_information_type_1 *si1 = + (struct gsm48_system_information_type_1 *)data; + + gsm48_decode_freq_list(app_state.cell_arfcns, + si1->cell_channel_description, + sizeof(si1->cell_channel_description), + 0xff, 0x01); + + app_state.has_si1 = 1; + LOGP(DRR, LOGL_ERROR, "SI1 received.\n"); + } + break; + case GSM48_MT_RR_SYSINFO_2: +#ifdef BCCH_TC_CHECK + if (tc != 1) + LOGP(DRR, LOGL_ERROR, "SI2 on the wrong TC: %d\n", tc); +#endif + break; + case GSM48_MT_RR_SYSINFO_3: +#ifdef BCCH_TC_CHECK + if (tc != 2 && tc != 6) + LOGP(DRR, LOGL_ERROR, "SI3 on the wrong TC: %d\n", tc); +#endif + if (app_state.ccch_mode == CCCH_MODE_NONE) { + struct gsm48_system_information_type_3 *si3 = + (struct gsm48_system_information_type_3 *)data; + + if (si3->control_channel_desc.ccch_conf == RSL_BCCH_CCCH_CONF_1_C) + app_state.ccch_mode = CCCH_MODE_COMBINED; + else + app_state.ccch_mode = CCCH_MODE_NON_COMBINED; + + l1ctl_tx_ccch_mode_req(ms, app_state.ccch_mode); + } + break; + case GSM48_MT_RR_SYSINFO_4: +#ifdef BCCH_TC_CHECK + if (tc != 3 && tc != 7) + LOGP(DRR, LOGL_ERROR, "SI4 on the wrong TC: %d\n", tc); +#endif + break; + case GSM48_MT_RR_SYSINFO_5: + break; + case GSM48_MT_RR_SYSINFO_6: + break; + case GSM48_MT_RR_SYSINFO_7: +#ifdef BCCH_TC_CHECK + if (tc != 7) + LOGP(DRR, LOGL_ERROR, "SI7 on the wrong TC: %d\n", tc); +#endif + break; + case GSM48_MT_RR_SYSINFO_8: +#ifdef BCCH_TC_CHECK + if (tc != 3) + LOGP(DRR, LOGL_ERROR, "SI8 on the wrong TC: %d\n", tc); +#endif + break; + case GSM48_MT_RR_SYSINFO_9: +#ifdef BCCH_TC_CHECK + if (tc != 4) + LOGP(DRR, LOGL_ERROR, "SI9 on the wrong TC: %d\n", tc); +#endif + break; + case GSM48_MT_RR_SYSINFO_13: +#ifdef BCCH_TC_CHECK + if (tc != 4 && tc != 0) + LOGP(DRR, LOGL_ERROR, "SI13 on the wrong TC: %d\n", tc); +#endif + break; + case GSM48_MT_RR_SYSINFO_16: +#ifdef BCCH_TC_CHECK + if (tc != 6) + LOGP(DRR, LOGL_ERROR, "SI16 on the wrong TC: %d\n", tc); +#endif + break; + case GSM48_MT_RR_SYSINFO_17: +#ifdef BCCH_TC_CHECK + if (tc != 2) + LOGP(DRR, LOGL_ERROR, "SI17 on the wrong TC: %d\n", tc); +#endif + break; + case GSM48_MT_RR_SYSINFO_2bis: +#ifdef BCCH_TC_CHECK + if (tc != 5) + LOGP(DRR, LOGL_ERROR, "SI2bis on the wrong TC: %d\n", tc); +#endif + break; + case GSM48_MT_RR_SYSINFO_2ter: +#ifdef BCCH_TC_CHECK + if (tc != 5 && tc != 4) + LOGP(DRR, LOGL_ERROR, "SI2ter on the wrong TC: %d\n", tc); +#endif + break; + case GSM48_MT_RR_SYSINFO_5bis: + break; + case GSM48_MT_RR_SYSINFO_5ter: + break; + default: + LOGP(DRR, LOGL_ERROR, "Unknown SI: %d\n", + si_hdr->system_information); + break; + }; +} + + +/** + * This method used to send a l1ctl_tx_dm_est_req_h0 or + * a l1ctl_tx_dm_est_req_h1 to the layer1 to follow this + * assignment. The code has been removed. + */ +static int gsm48_rx_imm_ass(struct msgb *msg, struct osmocom_ms *ms) +{ + struct gsm48_imm_ass *ia = msgb_l3(msg); + uint8_t ch_type, ch_subch, ch_ts; + + /* Discard packet TBF assignement */ + if (ia->page_mode & 0xf0) + return 0; + + /* FIXME: compare RA and GSM time with when we sent RACH req */ + + rsl_dec_chan_nr(ia->chan_desc.chan_nr, &ch_type, &ch_subch, &ch_ts); + + if (!ia->chan_desc.h0.h) { + /* Non-hopping */ + uint16_t arfcn; + + arfcn = ia->chan_desc.h0.arfcn_low | (ia->chan_desc.h0.arfcn_high << 8); + + LOGP(DRR, LOGL_NOTICE, "GSM48 IMM ASS (ra=0x%02x, chan_nr=0x%02x, " + "ARFCN=%u, TS=%u, SS=%u, TSC=%u) ", ia->req_ref.ra, + ia->chan_desc.chan_nr, arfcn, ch_ts, ch_subch, + ia->chan_desc.h0.tsc); + + } else { + /* Hopping */ + uint8_t maio, hsn, ma_len; + uint16_t ma[64], arfcn; + int i, j, k; + + hsn = ia->chan_desc.h1.hsn; + maio = ia->chan_desc.h1.maio_low | (ia->chan_desc.h1.maio_high << 2); + + LOGP(DRR, LOGL_NOTICE, "GSM48 IMM ASS (ra=0x%02x, chan_nr=0x%02x, " + "HSN=%u, MAIO=%u, TS=%u, SS=%u, TSC=%u) ", ia->req_ref.ra, + ia->chan_desc.chan_nr, hsn, maio, ch_ts, ch_subch, + ia->chan_desc.h1.tsc); + + /* decode mobile allocation */ + ma_len = 0; + for (i=1, j=0; i<=1024; i++) { + arfcn = i & 1023; + if (app_state.cell_arfcns[arfcn].mask & 0x01) { + k = ia->mob_alloc_len - (j>>3) - 1; + if (ia->mob_alloc[k] & (1 << (j&7))) { + ma[ma_len++] = arfcn; + } + j++; + } + } + } + + LOGPC(DRR, LOGL_NOTICE, "\n"); + return 0; +} + +static const char *pag_print_mode(int mode) +{ + switch (mode) { + case 0: + return "Normal paging"; + case 1: + return "Extended paging"; + case 2: + return "Paging reorganization"; + case 3: + return "Same as before"; + default: + return "invalid"; + } +} + +static char *chan_need(int need) +{ + switch (need) { + case 0: + return "any"; + case 1: + return "sdch"; + case 2: + return "tch/f"; + case 3: + return "tch/h"; + default: + return "invalid"; + } +} + +static char *mi_type_to_string(int type) +{ + switch (type) { + case GSM_MI_TYPE_NONE: + return "none"; + case GSM_MI_TYPE_IMSI: + return "imsi"; + case GSM_MI_TYPE_IMEI: + return "imei"; + case GSM_MI_TYPE_IMEISV: + return "imeisv"; + case GSM_MI_TYPE_TMSI: + return "tmsi"; + default: + return "invalid"; + } +} + +/** + * This can contain two MIs. The size checking is a bit of a mess. + */ +static int gsm48_rx_paging_p1(struct msgb *msg, struct osmocom_ms *ms) +{ + struct gsm48_paging1 *pag; + int len1, len2, mi_type, tag; + char mi_string[GSM48_MI_SIZE]; + + /* is there enough room for the header + LV? */ + if (msgb_l3len(msg) < sizeof(*pag) + 2) { + LOGP(DRR, LOGL_ERROR, "PagingRequest is too short.\n"); + return -1; + } + + pag = msgb_l3(msg); + len1 = pag->data[0]; + mi_type = pag->data[1] & GSM_MI_TYPE_MASK; + + if (msgb_l3len(msg) < sizeof(*pag) + 2 + len1) { + LOGP(DRR, LOGL_ERROR, "PagingRequest with wrong MI\n"); + return -1; + } + + if (mi_type != GSM_MI_TYPE_NONE) { + gsm48_mi_to_string(mi_string, sizeof(mi_string), &pag->data[1], len1); + LOGP(DRR, LOGL_NOTICE, "Paging1: %s chan %s to %s M(%s) \n", + pag_print_mode(pag->pag_mode), + chan_need(pag->cneed1), + mi_type_to_string(mi_type), + mi_string); + } + + /* check if we have a MI type in here */ + if (msgb_l3len(msg) < sizeof(*pag) + 2 + len1 + 3) + return 0; + + tag = pag->data[2 + len1 + 0]; + len2 = pag->data[2 + len1 + 1]; + mi_type = pag->data[2 + len1 + 2] & GSM_MI_TYPE_MASK; + if (tag == GSM48_IE_MOBILE_ID && mi_type != GSM_MI_TYPE_NONE) { + if (msgb_l3len(msg) < sizeof(*pag) + 2 + len1 + 3 + len2) { + LOGP(DRR, LOGL_ERROR, "Optional MI does not fit here.\n"); + return -1; + } + + gsm48_mi_to_string(mi_string, sizeof(mi_string), &pag->data[2 + len1 + 2], len2); + LOGP(DRR, LOGL_NOTICE, "Paging2: %s chan %s to %s M(%s) \n", + pag_print_mode(pag->pag_mode), + chan_need(pag->cneed2), + mi_type_to_string(mi_type), + mi_string); + } + return 0; +} + +static int gsm48_rx_paging_p2(struct msgb *msg, struct osmocom_ms *ms) +{ + struct gsm48_paging2 *pag; + int tag, len, mi_type; + char mi_string[GSM48_MI_SIZE]; + + if (msgb_l3len(msg) < sizeof(*pag)) { + LOGP(DRR, LOGL_ERROR, "Paging2 message is too small.\n"); + return -1; + } + + pag = msgb_l3(msg); + LOGP(DRR, LOGL_NOTICE, "Paging1: %s chan %s to TMSI M(0x%x) \n", + pag_print_mode(pag->pag_mode), + chan_need(pag->cneed1), pag->tmsi1); + LOGP(DRR, LOGL_NOTICE, "Paging2: %s chan %s to TMSI M(0x%x) \n", + pag_print_mode(pag->pag_mode), + chan_need(pag->cneed2), pag->tmsi2); + + /* no optional element */ + if (msgb_l3len(msg) < sizeof(*pag) + 3) + return 0; + + tag = pag->data[0]; + len = pag->data[1]; + mi_type = pag->data[2] & GSM_MI_TYPE_MASK; + + if (tag != GSM48_IE_MOBILE_ID) + return 0; + + if (msgb_l3len(msg) < sizeof(*pag) + 3 + len) { + LOGP(DRR, LOGL_ERROR, "Optional MI does not fit in here\n"); + return -1; + } + + gsm48_mi_to_string(mi_string, sizeof(mi_string), &pag->data[2], len); + LOGP(DRR, LOGL_NOTICE, "Paging3: %s chan %s to %s M(%s) \n", + pag_print_mode(pag->pag_mode), + "n/a ", + mi_type_to_string(mi_type), + mi_string); + + return 0; +} + +static int gsm48_rx_paging_p3(struct msgb *msg, struct osmocom_ms *ms) +{ + struct gsm48_paging3 *pag; + + if (msgb_l3len(msg) < sizeof(*pag)) { + LOGP(DRR, LOGL_ERROR, "Paging3 message is too small.\n"); + return -1; + } + + pag = msgb_l3(msg); + LOGP(DRR, LOGL_NOTICE, "Paging1: %s chan %s to TMSI M(0x%x) \n", + pag_print_mode(pag->pag_mode), + chan_need(pag->cneed1), pag->tmsi1); + LOGP(DRR, LOGL_NOTICE, "Paging2: %s chan %s to TMSI M(0x%x) \n", + pag_print_mode(pag->pag_mode), + chan_need(pag->cneed2), pag->tmsi2); + LOGP(DRR, LOGL_NOTICE, "Paging3: %s chan %s to TMSI M(0x%x) \n", + pag_print_mode(pag->pag_mode), + "n/a ", pag->tmsi3); + LOGP(DRR, LOGL_NOTICE, "Paging4: %s chan %s to TMSI M(0x%x) \n", + pag_print_mode(pag->pag_mode), + "n/a ", pag->tmsi4); + + return 0; +} + +int gsm48_rx_ccch(struct msgb *msg, struct osmocom_ms *ms) +{ + struct gsm48_system_information_type_header *sih = msgb_l3(msg); + int rc = 0; + + if (sih->rr_protocol_discriminator != GSM48_PDISC_RR) + LOGP(DRR, LOGL_ERROR, "PCH pdisc != RR\n"); + + switch (sih->system_information) { + case GSM48_MT_RR_PAG_REQ_1: + gsm48_rx_paging_p1(msg, ms); + break; + case GSM48_MT_RR_PAG_REQ_2: + gsm48_rx_paging_p2(msg, ms); + break; + case GSM48_MT_RR_PAG_REQ_3: + gsm48_rx_paging_p3(msg, ms); + break; + case GSM48_MT_RR_IMM_ASS: + gsm48_rx_imm_ass(msg, ms); + break; + case GSM48_MT_RR_NOTIF_NCH: + /* notification for voice call groups and such */ + break; + case 0x07: + /* wireshark know that this is SI2 quater and for 3G interop */ + break; + default: + LOGP(DRR, LOGL_NOTICE, "unknown PCH/AGCH type 0x%02x\n", + sih->system_information); + rc = -EINVAL; + } + + return rc; +} + +int gsm48_rx_bcch(struct msgb *msg, struct osmocom_ms *ms) +{ + /* FIXME: we have lost the gsm frame time until here, need to store it + * in some msgb context */ + //dump_bcch(dl->time.tc, ccch->data); + dump_bcch(ms, 0, msg->l3h); + + /* Req channel logic */ + if (app_state.ccch_enabled && (app_state.rach_count < 2)) { + l1ctl_tx_rach_req(ms, app_state.rach_count, 0, + app_state.ccch_mode == CCCH_MODE_COMBINED); + app_state.rach_count++; + } + + return 0; +} + +void layer3_app_reset(void) +{ + /* Reset state */ + app_state.has_si1 = 0; + app_state.ccch_mode = CCCH_MODE_NONE; + app_state.ccch_enabled = 0; + app_state.rach_count = 0; + + memset(&app_state.cell_arfcns, 0x00, sizeof(app_state.cell_arfcns)); +} + +static int signal_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct osmocom_ms *ms; + + if (subsys != SS_L1CTL) + return 0; + + switch (signal) { + case S_L1CTL_RESET: + ms = signal_data; + layer3_app_reset(); + return l1ctl_tx_fbsb_req(ms, ms->test_arfcn, + L1CTL_FBSB_F_FB01SB, 100, 0, + CCCH_MODE_NONE); + break; + } + return 0; +} + + +int l23_app_init(struct osmocom_ms *ms) +{ + osmo_signal_register_handler(SS_L1CTL, &signal_cb, NULL); + l1ctl_tx_reset_req(ms, L1CTL_RES_T_FULL); + return layer3_init(ms); +} + +static struct l23_app_info info = { + .copyright = "Copyright (C) 2010 Harald Welte <laforge@gnumonks.org>\n", + .contribution = "Contributions by Holger Hans Peter Freyther\n", +}; + +struct l23_app_info *l23_app_info() +{ + return &info; +} diff --git a/src/host/layer23/src/misc/app_cell_log.c b/src/host/layer23/src/misc/app_cell_log.c new file mode 100644 index 00000000..27290be7 --- /dev/null +++ b/src/host/layer23/src/misc/app_cell_log.c @@ -0,0 +1,195 @@ +/* "Application" code of the layer2/3 stack */ + +/* (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <signal.h> +#include <stdlib.h> +#include <time.h> +#include <getopt.h> + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/common/l23_app.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/gps.h> +#include <osmocom/bb/misc/cell_log.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> + +#include <l1ctl_proto.h> + +extern struct log_target *stderr_target; +extern void *l23_ctx; + +char *logname = "/var/log/osmocom.log"; +int RACH_MAX = 2; + +int _scan_work(struct osmocom_ms *ms) +{ + return 0; +} + +int _scan_exit(struct osmocom_ms *ms) +{ + /* in case there is a lockup during exit */ + signal(SIGINT, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + + scan_exit(); + + return 0; +} + +int l23_app_init(struct osmocom_ms *ms) +{ + int rc; + + srand(time(NULL)); + +// log_parse_category_mask(stderr_target, "DL1C:DRSL:DRR:DGPS:DSUM"); + log_parse_category_mask(stderr_target, "DSUM"); + log_set_log_level(stderr_target, LOGL_INFO); + + l23_app_work = _scan_work; + l23_app_exit = _scan_exit; + + rc = scan_init(ms); + if (rc) + return rc; + + l1ctl_tx_reset_req(ms, L1CTL_RES_T_FULL); + printf("Mobile initialized, please start phone now!\n"); + return 0; +} + +static int l23_cfg_supported() +{ + return L23_OPT_TAP | L23_OPT_DBG; +} + +static int l23_getopt_options(struct option **options) +{ + static struct option opts [] = { + {"logfile", 1, 0, 'l'}, + {"rach", 1, 0, 'r'}, + {"no-rach", 1, 0, 'n'}, +#ifdef _HAVE_GPSD + {"gpsd-host", 1, 0, 'g'}, + {"gpsd-port", 1, 0, 'p'}, +#endif + {"gps", 1, 0, 'g'}, + {"baud", 1, 0, 'b'} + }; + + *options = opts; + return ARRAY_SIZE(opts); +} + +static int l23_cfg_print_help() +{ + printf("\nApplication specific\n"); + printf(" -l --logfile LOGFILE Logfile for the cell log.\n"); + printf(" -r --rach RACH Nr. of RACH bursts to send.\n"); + printf(" -n --no-rach Send no rach bursts.\n"); + printf(" -g --gpsd-host HOST 127.0.0.1. gpsd host.\n"); + printf(" -p --port PORT 2947. gpsd port\n"); + printf(" -f --gps DEVICE /dev/ttyACM0. GPS serial device.\n"); + printf(" -b --baud BAUDRAT The baud rate of the GPS device\n"); + + return 0; +} + +static int l23_cfg_handle(int c, const char *optarg) +{ + switch (c) { + case 'l': + logname = talloc_strdup(l23_ctx, optarg); + break; + case 'r': + RACH_MAX = atoi(optarg); + break; + case 'n': + RACH_MAX = 0; + break; + case 'g': +#ifdef _HAVE_GPSD + snprintf(g.gpsd_host, ARRAY_SIZE(g.gpsd_host), "%s", optarg); + /* force string terminator */ + g.gpsd_host[ARRAY_SIZE(g.gpsd_host) - 1] = '\0'; + if (g.gps_type != GPS_TYPE_UNDEF) + goto cmd_line_error; + g.gps_type = GPS_TYPE_GPSD; + LOGP(DGPS, LOGL_INFO, "Using gpsd host %s\n", g.gpsd_host); +#else + printf("Gpsd support not compiled.\n"); + exit(1); +#endif + break; + case 'p': +#ifdef _HAVE_GPSD + snprintf(g.gpsd_port, ARRAY_SIZE(g.gpsd_port), "%s", optarg); + /* force string terminator */ + g.gpsd_port[ARRAY_SIZE(g.gpsd_port) - 1] = '\0'; + g.gps_type = GPS_TYPE_GPSD; + LOGP(DGPS, LOGL_INFO, "Using gpsd port %s\n", g.gpsd_port); +#else + printf("Gpsd support not compiled.\n"); + exit(1); +#endif + break; + case 'f': + snprintf(g.device, ARRAY_SIZE(g.device), "%s", optarg); + /* force string terminator */ + g.device[ARRAY_SIZE(g.device) - 1] = '\0'; + if (g.gps_type != GPS_TYPE_UNDEF) + goto cmd_line_error; + g.gps_type = GPS_TYPE_SERIAL; + LOGP(DGPS, LOGL_INFO, "Using GPS serial device %s\n", g.device); + break; + case 'b': + g.baud = atoi(optarg); + g.gps_type = GPS_TYPE_SERIAL; + LOGP(DGPS, LOGL_INFO, "Setting GPS baudrate to %u\n", g.baud); + break; + } + return 0; + +cmd_line_error: + printf("\nYou can't specify both gpsd and serial gps!!\n\n"); + exit(1); +} + +static struct l23_app_info info = { + .copyright = "Copyright (C) 2010 Andreas Eversberg\n", + .getopt_string = "g:p:l:r:nf:b:", + .cfg_supported = l23_cfg_supported, + .cfg_getopt_opt = l23_getopt_options, + .cfg_handle_opt = l23_cfg_handle, + .cfg_print_help = l23_cfg_print_help, +}; + +struct l23_app_info *l23_app_info() +{ + return &info; +} diff --git a/src/host/layer23/src/misc/app_echo_test.c b/src/host/layer23/src/misc/app_echo_test.c new file mode 100644 index 00000000..3a0ee0f4 --- /dev/null +++ b/src/host/layer23/src/misc/app_echo_test.c @@ -0,0 +1,65 @@ +/* TEST code, regularly transmit ECHO REQ packet to L1 */ + +/* (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/l23_app.h> +#include <osmocom/bb/misc/layer3.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> + + +static struct { + struct osmo_timer_list timer; +} test_data; + +static void test_tmr_cb(void *data) +{ + struct osmocom_ms *ms = data; + + l1ctl_tx_echo_req(ms, 62); + osmo_timer_schedule(&test_data.timer, 1, 0); +} + +int l23_app_init(struct osmocom_ms *ms) +{ + test_data.timer.cb = &test_tmr_cb; + test_data.timer.data = ms; + + osmo_timer_schedule(&test_data.timer, 1, 0); + + return 0; +} + +static struct l23_app_info info = { + .copyright = "Copyright (C) 2010 Harald Welte <laforge@gnumonks.org>\n", + .contribution = "Contributions by Holger Hans Peter Freyther\n", +}; + +struct l23_app_info *l23_app_info() +{ + return &info; +} diff --git a/src/host/layer23/src/misc/bcch_scan.c b/src/host/layer23/src/misc/bcch_scan.c new file mode 100644 index 00000000..4636c9ab --- /dev/null +++ b/src/host/layer23/src/misc/bcch_scan.c @@ -0,0 +1,317 @@ +/* BCCH Scanning code for OsmocomBB */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> + +#include <l1ctl_proto.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/signal.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/gsm/rsl.h> + +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/logging.h> + +/* somewhere in 05.08 */ +#define MAX_CELLS_IN_BA 32 + +/* Information about a single cell / BCCH */ +struct cell_info { + struct llist_head list; + + uint16_t band_arfcn; + uint8_t bsic; + uint8_t rxlev; + + struct { + uint16_t mcc; /* Mobile Country Code */ + uint16_t mnc; /* Mobile Network Code */ + uint16_t lac; /* Location Area Code */ + uint16_t rac; /* Routing Area Code */ + uint16_t cid; /* Cell ID */ + } id; + uint16_t ba_arfcn[MAX_CELLS_IN_BA]; + uint8_t ba_arfcn_num; + + struct { + int32_t fn_delta; /* delta to current L1 fn */ + int16_t qbit_delta; + int16_t afc_delta; + } l1_sync; +}; + +#define AFS_F_PM_DONE 0x01 +#define AFS_F_TESTED 0x02 +#define AFS_F_BCCH 0x04 +struct arfcn_state { + uint8_t rxlev; + uint8_t flags; +}; + +enum bscan_state { + BSCAN_S_NONE, + BSCAN_S_WAIT_DATA, + BSCAN_S_DONE, +}; + +enum fps_state { + FPS_S_NONE, + FPS_S_PM_GSM900, + FPS_S_PM_EGSM900, + FPS_S_PM_GSM1800, + FPS_S_BINFO, +}; + +struct full_power_scan { + /* Full Power Scan */ + enum fps_state fps_state; + struct arfcn_state arfcn_state[1024]; + + struct osmocom_ms *ms; + + /* BCCH info part */ + enum bscan_state state; + struct llist_head cell_list; + struct cell_info *cur_cell; + uint16_t cur_arfcn; + struct osmo_timer_list timer; +}; + +static struct full_power_scan fps; + +static int get_next_arfcn(struct full_power_scan *fps) +{ + unsigned int i; + uint8_t best_rxlev = 0; + int best_arfcn = -1; + + for (i = 0; i < ARRAY_SIZE(fps->arfcn_state); i++) { + struct arfcn_state *af = &fps->arfcn_state[i]; + /* skip ARFCN's where we don't have a PM */ + if (!(af->flags & AFS_F_PM_DONE)) + continue; + /* skip ARFCN's that we already tested */ + if (af->flags & AFS_F_TESTED) + continue; + /* if current arfcn_state is better than best so far, + * select it */ + if (af->rxlev > best_rxlev) { + best_rxlev = af->rxlev; + best_arfcn = i; + } + } + printf("arfcn=%d rxlev=%u\n", best_arfcn, best_rxlev); + return best_arfcn; +} + +static struct cell_info *cell_info_alloc(void) +{ + struct cell_info *ci = talloc_zero(NULL, struct cell_info); + return ci; +} + +static void cell_info_free(struct cell_info *ci) +{ + talloc_free(ci); +} + +/* start to scan for one ARFCN */ +static int _cinfo_start_arfcn(unsigned int band_arfcn) +{ + int rc; + + /* ask L1 to try to tune to new ARFCN */ + /* FIXME: decode band */ + rc = l1ctl_tx_fbsb_req(fps.ms, band_arfcn, + L1CTL_FBSB_F_FB01SB, 100, 0, CCCH_MODE_COMBINED); + if (rc < 0) + return rc; + + /* allocate new cell info structure */ + fps.cur_cell = cell_info_alloc(); + fps.cur_arfcn = band_arfcn; + fps.cur_cell->band_arfcn = band_arfcn; + /* FIXME: start timer in case we never get a sync */ + fps.state = BSCAN_S_WAIT_DATA; + osmo_timer_schedule(&fps.timer, 2, 0); + + return 0; +} + + +static void cinfo_next_cell(void *data) +{ + int rc; + + /* we've been waiting for BCCH info */ + fps.arfcn_state[fps.cur_arfcn].flags |= AFS_F_TESTED; + /* if there is a BCCH, we need to add the collected BCCH + * information to our list */ + + if (fps.arfcn_state[fps.cur_arfcn].flags & AFS_F_BCCH) + llist_add(&fps.cur_cell->list, &fps.cell_list); + else + cell_info_free(fps.cur_cell); + + rc = get_next_arfcn(&fps); + if (rc < 0) { + fps.state = BSCAN_S_DONE; + return; + } + /* start syncing to the next ARFCN */ + _cinfo_start_arfcn(rc); +} + +static void cinfo_timer_cb(void *data) +{ + switch (fps.state) { + case BSCAN_S_WAIT_DATA: + cinfo_next_cell(data); + break; + } +} + +/* Update cell_info for current cell with received BCCH info */ +static int rx_bcch_info(const uint8_t *data) +{ + struct cell_info *ci = fps.cur_cell; + struct gsm48_system_information_type_header *si_hdr; + si_hdr = (struct gsm48_system_information_type_header *) data;; + + /* we definitely have a BCCH on this channel */ + fps.arfcn_state[ci->band_arfcn].flags |= AFS_F_BCCH; + + switch (si_hdr->system_information) { + case GSM48_MT_RR_SYSINFO_1: + /* FIXME: CA, RACH control */ + break; + case GSM48_MT_RR_SYSINFO_2: + /* FIXME: BA, NCC, RACH control */ + break; + case GSM48_MT_RR_SYSINFO_3: + /* FIXME: cell_id, LAI */ + break; + case GSM48_MT_RR_SYSINFO_4: + /* FIXME: LAI */ + break; + } + return 0; +} + +/* Update L1/SCH information (AFC/QBIT/FN offset, BSIC) */ +static int rx_sch_info() +{ + /* FIXME */ +} + +static int bscan_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct cell_info *ci = fps.cur_cell; + struct osmocom_ms *ms; + struct osmobb_meas_res *mr; + uint16_t arfcn; + int rc; + + if (subsys != SS_L1CTL) + return 0; + + switch (signal) { + case S_L1CTL_PM_RES: + mr = signal_data; + /* check if PM result is for same MS */ + if (fps.ms != mr->ms) + return 0; + arfcn = mr->band_arfcn & 0x3ff; + /* update RxLev and notice that PM was done */ + fps.arfcn_state[arfcn].rxlev = mr->rx_lev; + fps.arfcn_state[arfcn].flags |= AFS_F_PM_DONE; + break; + case S_L1CTL_PM_DONE: + ms = signal_data; + switch (fps.fps_state) { + case FPS_S_PM_GSM900: + fps.fps_state = FPS_S_PM_EGSM900; + return l1ctl_tx_pm_req_range(ms, 955, 1023); + case FPS_S_PM_EGSM900: + fps.fps_state = FPS_S_PM_GSM1800; + return l1ctl_tx_pm_req_range(ms, 512, 885); + case FPS_S_PM_GSM1800: + /* power measurement has finished, we can start + * to actually iterate over the ARFCN's and try + * to sync to BCCHs */ + fps.fps_state = FPS_S_BINFO; + rc = get_next_arfcn(&fps); + if (rc < 0) { + fps.state = BSCAN_S_DONE; + return 0; + } + _cinfo_start_arfcn(rc); + break; + } + break; + case S_L1CTL_FBSB_RESP: + /* We actually got a FCCH/SCH burst */ +#if 0 + fps.arfcn_state[ci->band_arfcn].flags |= AFS_F_BCCH; + /* fallthrough */ +#else + break; +#endif + case S_L1CTL_FBSB_ERR: + /* We timed out, move on */ + if (fps.state == BSCAN_S_WAIT_DATA) + cinfo_next_cell(NULL); + break; + } + return 0; +} + +/* start the full power scan */ +int fps_start(struct osmocom_ms *ms) +{ + memset(&fps, 0, sizeof(fps)); + fps.ms = ms; + + fps.timer.cb = cinfo_timer_cb; + fps.timer.data = &fps; + + /* Start by scanning the good old GSM900 band */ + fps.fps_state = FPS_S_PM_GSM900; + return l1ctl_tx_pm_req_range(ms, 0, 124); +} + +int fps_init(void) +{ + return osmo_signal_register_handler(SS_L1CTL, &bscan_sig_cb, NULL); +} diff --git a/src/host/layer23/src/misc/cell_log.c b/src/host/layer23/src/misc/cell_log.c new file mode 100644 index 00000000..aa964f48 --- /dev/null +++ b/src/host/layer23/src/misc/cell_log.c @@ -0,0 +1,819 @@ +/* Cell Scanning code for OsmocomBB */ + +/* (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include <errno.h> + +#include <l1ctl_proto.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/signal.h> +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/rsl.h> + +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/networks.h> +#include <osmocom/bb/common/gps.h> +#include <osmocom/bb/misc/cell_log.h> +#include "../../../gsmmap/geo.h" + +#define READ_WAIT 2, 0 +#define RACH_WAIT 0, 900000 +#define MIN_RXLEV -106 +#define MAX_DIST 2000 + +enum { + SCAN_STATE_PM, + SCAN_STATE_SYNC, + SCAN_STATE_READ, + SCAN_STATE_RACH, +}; + +/* ranges of bands */ +static uint16_t band_range[][2] = {{0, 124}, {512, 885}, {955, 1023}, {0, 0}}; + +#define INFO_FLG_PM 1 +#define INFO_FLG_SYNC 2 +#define INFO_FLG_SI1 4 +#define INFO_FLG_SI2 8 +#define INFO_FLG_SI2bis 16 +#define INFO_FLG_SI2ter 32 +#define INFO_FLG_SI3 64 +#define INFO_FLG_SI4 128 + +static struct osmocom_ms *ms; +static struct osmo_timer_list timer; + +static struct pm_info { + uint16_t flags; + int8_t rxlev; +} pm[1024]; + +static int started = 0; +static int state; +static int8_t min_rxlev = MIN_RXLEV; +static int sync_count; +static int pm_index, pm_gps_valid; +static double pm_gps_x, pm_gps_y, pm_gps_z; +static int arfcn; +static int rach_count; +static FILE *logfp = NULL; +extern char *logname; +extern int RACH_MAX; + + +static struct gsm48_sysinfo sysinfo; + +static struct log_si { + uint16_t flags; + uint8_t bsic; + int8_t rxlev; + uint16_t mcc, mnc, lac, cellid; + uint8_t ta; + double latitude, longitude; +} log_si; + +struct rach_ref { + uint8_t valid; + uint8_t cr; + uint8_t t1, t2, t3; +} rach_ref; + +#define LOGFILE(fmt, args...) \ + fprintf(logfp, fmt, ## args); +#define LOGFLUSH() \ + fflush(logfp); + +static void start_sync(void); +static void start_rach(void); +static void start_pm(void); + +static void log_gps(void) +{ + if (!g.enable || !g.valid) + return; + LOGFILE("position %.8f %.8f\n", g.longitude, g.latitude); +} + +static void log_time(void) +{ + time_t now; + + if (g.enable && g.valid) + now = g.gmt; + else + time(&now); + LOGFILE("time %lu\n", now); +} + +static void log_frame(char *tag, uint8_t *data) +{ + int i; + + LOGFILE("%s", tag); + for (i = 0; i < 23; i++) + LOGFILE(" %02x", *data++); + LOGFILE("\n"); +} + +static void log_pm(void) +{ + int count = 0, i; + + LOGFILE("[power]\n"); + log_time(); + log_gps(); + for (i = 0; i <= 1023; i++) { + if ((pm[i].flags & INFO_FLG_PM)) { + if (!count) + LOGFILE("arfcn %d", i); + LOGFILE(" %d", pm[i].rxlev); + count++; + if (count == 12) { + LOGFILE("\n"); + count = 0; + } + } else { + if (count) { + LOGFILE("\n"); + count = 0; + } + } + } + if (count) + LOGFILE("\n"); + + LOGFILE("\n"); + LOGFLUSH(); +} + +static void log_sysinfo(void) +{ + struct rx_meas_stat *meas = &ms->meas; + struct gsm48_sysinfo *s = &sysinfo; + int8_t rxlev; + char ta_str[32] = ""; + + if (log_si.ta != 0xff) + sprintf(ta_str, " TA=%d", log_si.ta); + + LOGP(DSUM, LOGL_INFO, "Cell: ARFCN=%d MCC=%s MNC=%s (%s, %s)%s\n", + arfcn, gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc), + gsm_get_mcc(s->mcc), gsm_get_mnc(s->mcc, s->mnc), ta_str); + + LOGFILE("[sysinfo]\n"); + LOGFILE("arfcn %d\n", s->arfcn); + log_time(); + log_gps(); + LOGFILE("bsic %d,%d\n", s->bsic >> 3, s->bsic & 7); + rxlev = meas->rxlev / meas->frames - 110; + LOGFILE("rxlev %d\n", rxlev); + if (s->si1) + log_frame("si1", s->si1_msg); + if (s->si2) + log_frame("si2", s->si2_msg); + if (s->si2bis) + log_frame("si2bis", s->si2b_msg); + if (s->si2ter) + log_frame("si2ter", s->si2t_msg); + if (s->si3) + log_frame("si3", s->si3_msg); + if (s->si4) + log_frame("si4", s->si4_msg); + if (log_si.ta != 0xff) + LOGFILE("ta %d\n", log_si.ta); + + LOGFILE("\n"); + LOGFLUSH(); +} + +static void timeout_cb(void *arg) +{ + switch (state) { + case SCAN_STATE_READ: + LOGP(DRR, LOGL_INFO, "Timeout reading BCCH\n"); + start_sync(); + break; + case SCAN_STATE_RACH: + LOGP(DRR, LOGL_INFO, "Timeout on RACH\n"); + rach_count++; + start_rach(); + break; + } +} + +static void stop_timer(void) +{ + if (osmo_timer_pending(&timer)) + osmo_timer_del(&timer); +} + +static void start_timer(int sec, int micro) +{ + stop_timer(); + timer.cb = timeout_cb; + timer.data = ms; + osmo_timer_schedule(&timer, sec, micro); +} + +static void start_rach(void) +{ + struct gsm48_sysinfo *s = &sysinfo; + uint8_t chan_req_val, chan_req_mask; + struct msgb *nmsg; + struct abis_rsl_cchan_hdr *ncch; + + if (rach_count == RACH_MAX) { + log_sysinfo(); + start_sync(); + return; + } + + state = SCAN_STATE_RACH; + + if (s->neci) { + chan_req_mask = 0x0f; + chan_req_val = 0x01; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x " + "(OTHER with NECI)\n", chan_req_val); + } else { + chan_req_mask = 0x1f; + chan_req_val = 0xe0; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (OTHER no NECI)\n", + chan_req_val); + } + + rach_ref.valid = 0; + rach_ref.cr = random(); + rach_ref.cr &= chan_req_mask; + rach_ref.cr |= chan_req_val; + + nmsg = msgb_alloc_headroom(RSL_ALLOC_SIZE+RSL_ALLOC_HEADROOM, + RSL_ALLOC_HEADROOM, "GSM 04.06 RSL"); + if (!nmsg) + return; + nmsg->l2h = nmsg->data; + ncch = (struct abis_rsl_cchan_hdr *) msgb_put(nmsg, sizeof(*ncch) + + 4 + 2 + 2); + rsl_init_cchan_hdr(ncch, RSL_MT_CHAN_RQD); + ncch->chan_nr = RSL_CHAN_RACH; + ncch->data[0] = RSL_IE_REQ_REFERENCE; + ncch->data[1] = rach_ref.cr; + ncch->data[2] = (s->ccch_conf == 1) << 7; + ncch->data[3] = 0; + ncch->data[4] = RSL_IE_ACCESS_DELAY; + ncch->data[5] = 0; /* no delay */ + ncch->data[6] = RSL_IE_MS_POWER; + ncch->data[7] = 0; /* full power */ + + start_timer(RACH_WAIT); + + lapdm_rslms_recvmsg(nmsg, &ms->lapdm_channel); +} + +static void start_sync(void) +{ + int rxlev = -128; + int i, dist = 0; + char dist_str[32] = ""; + + arfcn = 0xffff; + for (i = 0; i <= 1023; i++) { + if ((pm[i].flags & INFO_FLG_PM) + && !(pm[i].flags & INFO_FLG_SYNC)) { + if (pm[i].rxlev > rxlev) { + rxlev = pm[i].rxlev; + arfcn = i; + } + } + } + /* if GPS becomes valid, like after exitting a tunnel */ + if (!pm_gps_valid && g.valid) { + pm_gps_valid = 1; + geo2space(&pm_gps_x, &pm_gps_y, &pm_gps_z, g.longitude, + g.latitude); + } + if (pm_gps_valid && g.valid) { + double x, y, z; + + geo2space(&x, &y, &z, g.longitude, g.latitude); + dist = distinspace(pm_gps_x, pm_gps_y, pm_gps_z, x, y, z); + sprintf(dist_str, " dist %d", (int)dist); + } + if (dist > MAX_DIST || arfcn == 0xffff || rxlev < min_rxlev) { + memset(pm, 0, sizeof(pm)); + pm_index = 0; + sync_count = 0; + start_pm(); + return; + } + pm[arfcn].flags |= INFO_FLG_SYNC; + LOGP(DSUM, LOGL_INFO, "Sync ARFCN %d (rxlev %d, %d syncs " + "left)%s\n", arfcn, pm[arfcn].rxlev, sync_count--, dist_str); + memset(&sysinfo, 0, sizeof(sysinfo)); + sysinfo.arfcn = arfcn; + state = SCAN_STATE_SYNC; + l1ctl_tx_reset_req(ms, L1CTL_RES_T_FULL); + l1ctl_tx_fbsb_req(ms, arfcn, L1CTL_FBSB_F_FB01SB, 100, 0, + CCCH_MODE_NONE); +} + +static void start_pm(void) +{ + uint16_t from, to; + + state = SCAN_STATE_PM; + from = band_range[pm_index][0]; + to = band_range[pm_index][1]; + + if (from == 0 && to == 0) { + LOGP(DSUM, LOGL_INFO, "Measurement done\n"); + pm_gps_valid = g.enable && g.valid; + if (pm_gps_valid) + geo2space(&pm_gps_x, &pm_gps_y, &pm_gps_z, + g.longitude, g.latitude); + log_pm(); + start_sync(); + return; + } + LOGP(DSUM, LOGL_INFO, "Measure from %d to %d\n", from, to); + l1ctl_tx_reset_req(ms, L1CTL_RES_T_FULL); + l1ctl_tx_pm_req_range(ms, from, to); +} + +static int signal_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct osmobb_meas_res *mr; + struct osmobb_fbsb_res *fr; + uint16_t index; + + if (subsys != SS_L1CTL) + return 0; + + switch (signal) { + case S_L1CTL_PM_RES: + mr = signal_data; + index = mr->band_arfcn & 0x3ff; + pm[index].flags |= INFO_FLG_PM; + pm[index].rxlev = mr->rx_lev - 110; + if (pm[index].rxlev >= min_rxlev) + sync_count++; +// printf("rxlev %d = %d (sync_count %d)\n", index, pm[index].rxlev, sync_count); + break; + case S_L1CTL_PM_DONE: + pm_index++; + start_pm(); + break; + case S_L1CTL_FBSB_RESP: + fr = signal_data; + sysinfo.bsic = fr->bsic; + state = SCAN_STATE_READ; + memset(&ms->meas, 0, sizeof(ms->meas)); + memset(&log_si, 0, sizeof(log_si)); + log_si.flags |= INFO_FLG_SYNC; + log_si.ta = 0xff; /* invalid */ + start_timer(READ_WAIT); + LOGP(DRR, LOGL_INFO, "Synchronized, start reading\n"); + break; + case S_L1CTL_FBSB_ERR: + LOGP(DRR, LOGL_INFO, "Sync failed\n"); + start_sync(); + break; + case S_L1CTL_RESET: + if (started) + break; + started = 1; + memset(pm, 0, sizeof(pm)); + pm_index = 0; + sync_count = 0; + start_pm(); + } + return 0; +} + +static int ta_result(uint8_t ta) +{ + stop_timer(); + + if (ta == 0xff) + LOGP(DSUM, LOGL_INFO, "Got assignment reject\n"); + else { + LOGP(DSUM, LOGL_DEBUG, "Got assignment TA = %d\n", ta); + log_si.ta = ta; + } + + log_sysinfo(); + start_sync(); + + return 0; +} + +/* match request reference agains request */ +static int match_ra(struct osmocom_ms *ms, struct gsm48_req_ref *ref) +{ + uint8_t ia_t1, ia_t2, ia_t3; + + /* filter confirmed RACH requests only */ + if (rach_ref.valid && ref->ra == rach_ref.cr) { + ia_t1 = ref->t1; + ia_t2 = ref->t2; + ia_t3 = (ref->t3_high << 3) | ref->t3_low; + if (ia_t1 == rach_ref.t1 && ia_t2 == rach_ref.t2 + && ia_t3 == rach_ref.t3) { + LOGP(DRR, LOGL_INFO, "request %02x matches " + "(fn=%d,%d,%d)\n", ref->ra, ia_t1, ia_t2, + ia_t3); + return 1; + } else + LOGP(DRR, LOGL_INFO, "request %02x matches but not " + "frame number (IMM.ASS fn=%d,%d,%d != RACH " + "fn=%d,%d,%d)\n", ref->ra, ia_t1, ia_t2, ia_t3, + rach_ref.t1, rach_ref.t2, rach_ref.t3); + } + + return 0; +} + +/* 9.1.18 IMMEDIATE ASSIGNMENT is received */ +static int imm_ass(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_imm_ass *ia = msgb_l3(msg); + + LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT:\n"); + + if (state != SCAN_STATE_RACH) { + LOGP(DRR, LOGL_INFO, "Not for us, no request.\n"); + return 0; + } + + /* request ref */ + if (match_ra(ms, &ia->req_ref)) { + return ta_result(ia->timing_advance); + } + LOGP(DRR, LOGL_INFO, "Request, but not for us.\n"); + + return 0; +} + +/* 9.1.19 IMMEDIATE ASSIGNMENT EXTENDED is received */ +static int imm_ass_ext(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_imm_ass_ext *ia = msgb_l3(msg); + + LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT EXTENDED:\n"); + + if (state != SCAN_STATE_RACH) { + LOGP(DRR, LOGL_INFO, "Not for us, no request.\n"); + return 0; + } + + /* request ref 1 */ + if (match_ra(ms, &ia->req_ref1)) { + return ta_result(ia->timing_advance1); + } + /* request ref 2 */ + if (match_ra(ms, &ia->req_ref2)) { + return ta_result(ia->timing_advance2); + } + LOGP(DRR, LOGL_INFO, "Request, but not for us.\n"); + + return 0; +} + +/* 9.1.20 IMMEDIATE ASSIGNMENT REJECT is received */ +static int imm_ass_rej(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_imm_ass_rej *ia = msgb_l3(msg); + int i; + struct gsm48_req_ref *req_ref; + + LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT REJECT:\n"); + + if (state != SCAN_STATE_RACH) { + LOGP(DRR, LOGL_INFO, "Not for us, no request.\n"); + return 0; + } + + for (i = 0; i < 4; i++) { + /* request reference */ + req_ref = (struct gsm48_req_ref *) + (((uint8_t *)&ia->req_ref1) + i * 4); + LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT REJECT " + "(ref 0x%02x)\n", req_ref->ra); + if (match_ra(ms, req_ref)) { + return ta_result(0xff); + } + } + + return 0; +} + +/* receive CCCH at RR layer */ +static int pch_agch(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_system_information_type_header *sih = msgb_l3(msg); + + switch (sih->system_information) { + case GSM48_MT_RR_PAG_REQ_1: + case GSM48_MT_RR_PAG_REQ_2: + case GSM48_MT_RR_PAG_REQ_3: + return 0; + case GSM48_MT_RR_IMM_ASS: + return imm_ass(ms, msg); + case GSM48_MT_RR_IMM_ASS_EXT: + return imm_ass_ext(ms, msg); + case GSM48_MT_RR_IMM_ASS_REJ: + return imm_ass_rej(ms, msg); + default: + return -EINVAL; + } +} + +/* check if sysinfo is complete, change to RACH state */ +static int new_sysinfo(void) +{ + struct gsm48_sysinfo *s = &sysinfo; + + /* restart timer */ + start_timer(READ_WAIT); + + /* mandatory */ + if (!s->si1 || !s->si2 || !s->si3 || !s->si4) { + LOGP(DRR, LOGL_INFO, "not all mandatory SI received\n"); + return 0; + } + + /* extended band */ + if (s->nb_ext_ind_si2 && !s->si2bis) { + LOGP(DRR, LOGL_INFO, "extended ba, but si2bis not received\n"); + return 0; + } + + /* 2ter */ + if (s->si2ter_ind && !s->si2ter) { + LOGP(DRR, LOGL_INFO, "si2ter_ind, but si2ter not received\n"); + return 0; + } + + LOGP(DRR, LOGL_INFO, "Sysinfo complete\n"); + + stop_timer(); + + rach_count = 0; + start_rach(); + + return 0; +} + +/* receive BCCH at RR layer */ +static int bcch(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_sysinfo *s = &sysinfo; + struct gsm48_system_information_type_header *sih = msgb_l3(msg); + uint8_t ccch_mode; + + if (msgb_l3len(msg) != 23) { + LOGP(DRR, LOGL_NOTICE, "Invalid BCCH message length\n"); + return -EINVAL; + } + switch (sih->system_information) { + case GSM48_MT_RR_SYSINFO_1: + if (!memcmp(sih, s->si1_msg, sizeof(s->si1_msg))) + return 0; + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 1\n"); + gsm48_decode_sysinfo1(s, + (struct gsm48_system_information_type_1 *) sih, + msgb_l3len(msg)); + return new_sysinfo(); + case GSM48_MT_RR_SYSINFO_2: + if (!memcmp(sih, s->si2_msg, sizeof(s->si2_msg))) + return 0; + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 2\n"); + gsm48_decode_sysinfo2(s, + (struct gsm48_system_information_type_2 *) sih, + msgb_l3len(msg)); + return new_sysinfo(); + case GSM48_MT_RR_SYSINFO_2bis: + if (!memcmp(sih, s->si2b_msg, sizeof(s->si2b_msg))) + return 0; + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 2bis\n"); + gsm48_decode_sysinfo2bis(s, + (struct gsm48_system_information_type_2bis *) sih, + msgb_l3len(msg)); + return new_sysinfo(); + case GSM48_MT_RR_SYSINFO_2ter: + if (!memcmp(sih, s->si2t_msg, sizeof(s->si2t_msg))) + return 0; + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 2ter\n"); + gsm48_decode_sysinfo2ter(s, + (struct gsm48_system_information_type_2ter *) sih, + msgb_l3len(msg)); + return new_sysinfo(); + case GSM48_MT_RR_SYSINFO_3: + if (!memcmp(sih, s->si3_msg, sizeof(s->si3_msg))) + return 0; + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 3\n"); + gsm48_decode_sysinfo3(s, + (struct gsm48_system_information_type_3 *) sih, + msgb_l3len(msg)); + ccch_mode = (s->ccch_conf == 1) ? CCCH_MODE_COMBINED : + CCCH_MODE_NON_COMBINED; + LOGP(DRR, LOGL_INFO, "Changing CCCH_MODE to %d\n", ccch_mode); + l1ctl_tx_ccch_mode_req(ms, ccch_mode); + return new_sysinfo(); + case GSM48_MT_RR_SYSINFO_4: + if (!memcmp(sih, s->si4_msg, sizeof(s->si4_msg))) + return 0; + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 4\n"); + gsm48_decode_sysinfo4(s, + (struct gsm48_system_information_type_4 *) sih, + msgb_l3len(msg)); + return new_sysinfo(); + default: + return -EINVAL; + } +} + +static int unit_data_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + struct tlv_parsed tv; + uint8_t ch_type, ch_subch, ch_ts; + + DEBUGP(DRSL, "RSLms UNIT DATA IND chan_nr=0x%02x link_id=0x%02x\n", + rllh->chan_nr, rllh->link_id); + + rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg)-sizeof(*rllh)); + if (!TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) { + DEBUGP(DRSL, "UNIT_DATA_IND without L3 INFO ?!?\n"); + return -EIO; + } + msg->l3h = (uint8_t *) TLVP_VAL(&tv, RSL_IE_L3_INFO); + + if (state != SCAN_STATE_READ && state != SCAN_STATE_RACH) { + return -EINVAL; + } + + rsl_dec_chan_nr(rllh->chan_nr, &ch_type, &ch_subch, &ch_ts); + switch (ch_type) { + case RSL_CHAN_PCH_AGCH: + return pch_agch(ms, msg); + case RSL_CHAN_BCCH: + return bcch(ms, msg); +#if 0 + case RSL_CHAN_Bm_ACCHs: + case RSL_CHAN_Lm_ACCHs: + case RSL_CHAN_SDCCH4_ACCH: + case RSL_CHAN_SDCCH8_ACCH: + return rx_acch(ms, msg); +#endif + default: + LOGP(DRSL, LOGL_NOTICE, "RSL with chan_nr 0x%02x unknown.\n", + rllh->chan_nr); + return -EINVAL; + } +} + +static int rcv_rll(struct osmocom_ms *ms, struct msgb *msg) +{ + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + int msg_type = rllh->c.msg_type; + + if (msg_type == RSL_MT_UNIT_DATA_IND) { + unit_data_ind(ms, msg); + } else + LOGP(DRSL, LOGL_NOTICE, "RSLms message unhandled\n"); + + msgb_free(msg); + + return 0; +} + +int chan_conf(struct osmocom_ms *ms, struct msgb *msg) +{ + struct abis_rsl_cchan_hdr *ch = msgb_l2(msg); + struct gsm48_req_ref *ref = (struct gsm48_req_ref *) (ch->data + 1); + + if (msgb_l2len(msg) < sizeof(*ch) + sizeof(*ref)) { + LOGP(DRR, LOGL_ERROR, "CHAN_CNF too slort\n"); + return -EINVAL; + } + + rach_ref.valid = 1; + rach_ref.t1 = ref->t1; + rach_ref.t2 = ref->t2; + rach_ref.t3 = ref->t3_low | (ref->t3_high << 3); + + return 0; +} + +static int rcv_cch(struct osmocom_ms *ms, struct msgb *msg) +{ + struct abis_rsl_cchan_hdr *ch = msgb_l2(msg); + int msg_type = ch->c.msg_type; + int rc; + + LOGP(DRSL, LOGL_INFO, "Received '%s' from layer1\n", + rsl_msg_name(msg_type)); + + if (state == SCAN_STATE_RACH && msg_type == RSL_MT_CHAN_CONF) { + rc = chan_conf(ms, msg); + msgb_free(msg); + return rc; + } + + LOGP(DRSL, LOGL_NOTICE, "RSLms message unhandled\n"); + msgb_free(msg); + return 0; +} + +static int rcv_rsl(struct msgb *msg, struct lapdm_entity *le, void *l3ctx) +{ + struct osmocom_ms *ms = l3ctx; + struct abis_rsl_common_hdr *rslh = msgb_l2(msg); + int rc = 0; + + switch (rslh->msg_discr & 0xfe) { + case ABIS_RSL_MDISC_RLL: + rc = rcv_rll(ms, msg); + break; + case ABIS_RSL_MDISC_COM_CHAN: + rc = rcv_cch(ms, msg); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "unknown RSLms msg_discr 0x%02x\n", + rslh->msg_discr); + msgb_free(msg); + rc = -EINVAL; + break; + } + + return rc; +} + +int scan_init(struct osmocom_ms *_ms) +{ + ms = _ms; + osmo_signal_register_handler(SS_L1CTL, &signal_cb, NULL); + memset(&timer, 0, sizeof(timer)); + lapdm_channel_set_l3(&ms->lapdm_channel, &rcv_rsl, ms); + g.enable = 1; + osmo_gps_init(); + if (osmo_gps_open()) + g.enable = 0; + + if (!strcmp(logname, "-")) + logfp = stdout; + else + logfp = fopen(logname, "a"); + if (!logfp) { + fprintf(stderr, "Failed to open logfile '%s'\n", logname); + scan_exit(); + return -errno; + } + LOGP(DSUM, LOGL_INFO, "Scanner initialized\n"); + + return 0; +} + +int scan_exit(void) +{ + LOGP(DSUM, LOGL_INFO, "Scanner exit\n"); + if (g.valid) + osmo_gps_close(); + if (logfp) + fclose(logfp); + osmo_signal_unregister_handler(SS_L1CTL, &signal_cb, NULL); + stop_timer(); + + return 0; +} diff --git a/src/host/layer23/src/misc/rslms.c b/src/host/layer23/src/misc/rslms.c new file mode 100644 index 00000000..455e6631 --- /dev/null +++ b/src/host/layer23/src/misc/rslms.c @@ -0,0 +1,151 @@ +/* RSLms - GSM 08.58 like protocol between L2 and L3 of GSM Um interface */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdint.h> +#include <errno.h> +#include <stdio.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/rsl.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/gsm/lapdm.h> +#include <osmocom/bb/misc/rslms.h> +#include <osmocom/bb/misc/layer3.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/l1ctl.h> + +/* Send a 'simple' RLL request to L2 */ +int rslms_tx_rll_req(struct osmocom_ms *ms, uint8_t msg_type, + uint8_t chan_nr, uint8_t link_id) +{ + struct msgb *msg; + + msg = rsl_rll_simple(msg_type, chan_nr, link_id, 1); + + return lapdm_rslms_recvmsg(msg, &ms->lapdm_channel); +} + +/* Send a RLL request (including L3 info) to L2 */ +int rslms_tx_rll_req_l3(struct osmocom_ms *ms, uint8_t msg_type, + uint8_t chan_nr, uint8_t link_id, struct msgb *msg) +{ + rsl_rll_push_l3(msg, msg_type, chan_nr, link_id, 1); + + return lapdm_rslms_recvmsg(msg, &ms->lapdm_channel); +} + +static int rslms_rx_udata_ind(struct msgb *msg, struct osmocom_ms *ms) +{ + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + struct tlv_parsed tv; + int rc = 0; + + DEBUGP(DRSL, "RSLms UNIT DATA IND chan_nr=0x%02x link_id=0x%02x\n", + rllh->chan_nr, rllh->link_id); + + rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg)-sizeof(*rllh)); + if (!TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) { + DEBUGP(DRSL, "UNIT_DATA_IND without L3 INFO ?!?\n"); + return -EIO; + } + msg->l3h = (uint8_t *) TLVP_VAL(&tv, RSL_IE_L3_INFO); + + if (rllh->chan_nr == RSL_CHAN_PCH_AGCH) { + rc = gsm48_rx_ccch(msg, ms); + } else if (rllh->chan_nr == RSL_CHAN_BCCH) { + rc = gsm48_rx_bcch(msg, ms); + } + + return rc; +} + +static int rslms_rx_rll(struct msgb *msg, struct osmocom_ms *ms) +{ + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + int rc = 0; + + switch (rllh->c.msg_type) { + case RSL_MT_DATA_IND: + DEBUGP(DRSL, "RSLms DATA IND\n"); + /* FIXME: implement this */ + break; + case RSL_MT_UNIT_DATA_IND: + rc = rslms_rx_udata_ind(msg, ms); + break; + case RSL_MT_EST_IND: + DEBUGP(DRSL, "RSLms EST IND\n"); + /* FIXME: implement this */ + break; + case RSL_MT_EST_CONF: + DEBUGP(DRSL, "RSLms EST CONF\n"); + /* FIXME: implement this */ + break; + case RSL_MT_REL_CONF: + DEBUGP(DRSL, "RSLms REL CONF\n"); + /* FIXME: implement this */ + break; + case RSL_MT_ERROR_IND: + DEBUGP(DRSL, "RSLms ERR IND\n"); + /* FIXME: implement this */ + break; + default: + LOGP(DRSL, LOGL_NOTICE, "unknown RSLms message type " + "0x%02x\n", rllh->c.msg_type); + rc = -EINVAL; + break; + } + msgb_free(msg); + return rc; +} + +/* input function that L2 calls when sending messages up to L3 */ +static int layer3_from_layer2(struct msgb *msg, struct lapdm_entity *le, void *ctx) +{ + struct osmocom_ms *ms = ctx; + struct abis_rsl_common_hdr *rslh = msgb_l2(msg); + int rc = 0; + + switch (rslh->msg_discr & 0xfe) { + case ABIS_RSL_MDISC_RLL: + rc = rslms_rx_rll(msg, ms); + break; + default: + /* FIXME: implement this */ + LOGP(DRSL, LOGL_NOTICE, "unknown RSLms msg_discr 0x%02x\n", + rslh->msg_discr); + msgb_free(msg); + rc = -EINVAL; + break; + } + + return rc; +} + +int layer3_init(struct osmocom_ms *ms) +{ + lapdm_channel_set_l3(&ms->lapdm_channel, &layer3_from_layer2, ms); + + return 0; +} diff --git a/src/host/layer23/src/mobile/Makefile.am b/src/host/layer23/src/mobile/Makefile.am new file mode 100644 index 00000000..8920c54a --- /dev/null +++ b/src/host/layer23/src/mobile/Makefile.am @@ -0,0 +1,15 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBGPS_CFLAGS) +LDADD = ../common/liblayer23.a $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBGPS_LIBS) + +noinst_LIBRARIES = libmobile.a +libmobile_a_SOURCES = gsm322.c gsm480_ss.c gsm411_sms.c gsm48_cc.c gsm48_mm.c \ + gsm48_rr.c mnccms.c settings.c subscriber.c support.c \ + transaction.c vty_interface.c voice.c mncc_sock.c + +bin_PROGRAMS = mobile + +mobile_SOURCES = main.c app_mobile.c +mobile_LDADD = libmobile.a $(LDADD) + + diff --git a/src/host/layer23/src/mobile/app_mobile.c b/src/host/layer23/src/mobile/app_mobile.c new file mode 100644 index 00000000..dc57c315 --- /dev/null +++ b/src/host/layer23/src/mobile/app_mobile.c @@ -0,0 +1,407 @@ +/* "Application" code of the layer2/3 stack */ + +/* (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by Harald Welte <laforge@gnumonks.org> + * (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <errno.h> +#include <signal.h> + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/l1l2_interface.h> +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/gps.h> +#include <osmocom/bb/mobile/gsm48_rr.h> +#include <osmocom/bb/mobile/gsm480_ss.h> +#include <osmocom/bb/mobile/gsm411_sms.h> +#include <osmocom/bb/mobile/vty.h> +#include <osmocom/bb/mobile/app_mobile.h> +#include <osmocom/bb/mobile/mncc.h> +#include <osmocom/bb/mobile/voice.h> +#include <osmocom/vty/telnet_interface.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/signal.h> + +#include <l1ctl_proto.h> + +extern void *l23_ctx; +extern struct llist_head ms_list; +extern int vty_reading; + +int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg); +int mncc_recv_dummy(struct osmocom_ms *ms, int msg_type, void *arg); +int (*mncc_recv_app)(struct osmocom_ms *ms, int, void *); +static int quit; + +/* handle ms instance */ +int mobile_work(struct osmocom_ms *ms) +{ + int work = 0, w; + + do { + w = 0; + w |= gsm48_rsl_dequeue(ms); + w |= gsm48_rr_dequeue(ms); + w |= gsm48_mmxx_dequeue(ms); + w |= gsm48_mmr_dequeue(ms); + w |= gsm48_mmevent_dequeue(ms); + w |= gsm322_plmn_dequeue(ms); + w |= gsm322_cs_dequeue(ms); + w |= gsm_sim_job_dequeue(ms); + w |= mncc_dequeue(ms); + if (w) + work = 1; + } while (w); + return work; +} + +/* run ms instance, if layer1 is available */ +int mobile_signal_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct osmocom_ms *ms; + struct gsm_settings *set; + struct msgb *nmsg; + + if (subsys != SS_L1CTL) + return 0; + + switch (signal) { + case S_L1CTL_RESET: + ms = signal_data; + set = &ms->settings; + + if (ms->started) + break; + + /* insert test card, if enabled */ + switch (set->sim_type) { + case GSM_SIM_TYPE_READER: + /* trigger sim card reader process */ + gsm_subscr_simcard(ms); + break; + case GSM_SIM_TYPE_TEST: + gsm_subscr_testcard(ms, set->test_rplmn_mcc, + set->test_rplmn_mnc, set->test_lac, + set->test_tmsi); + break; + default: + /* no SIM, trigger PLMN selection process */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_SWITCH_ON); + if (!nmsg) + return -ENOMEM; + gsm322_plmn_sendmsg(ms, nmsg); + nmsg = gsm322_msgb_alloc(GSM322_EVENT_SWITCH_ON); + if (!nmsg) + return -ENOMEM; + gsm322_cs_sendmsg(ms, nmsg); + } + + ms->started = 1; + } + return 0; +} + +/* power-off ms instance */ +int mobile_exit(struct osmocom_ms *ms, int force) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + if (!force && ms->started) { + struct msgb *nmsg; + + ms->shutdown = 1; /* going down */ + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_IMSI_DETACH); + if (!nmsg) + return -ENOMEM; + gsm48_mmevent_msg(mm->ms, nmsg); + + return -EBUSY; + } + + gsm322_exit(ms); + gsm48_mm_exit(ms); + gsm48_rr_exit(ms); + gsm_subscr_exit(ms); + gsm48_cc_exit(ms); + gsm480_ss_exit(ms); + gsm411_sms_exit(ms); + gsm_sim_exit(ms); + lapdm_channel_exit(&ms->lapdm_channel); + + ms->shutdown = 2; /* being down */ + vty_notify(ms, NULL); + vty_notify(ms, "Power off!\n"); + printf("Power off! (MS %s)\n", ms->name); + + return 0; +} + +/* power-on ms instance */ +int mobile_init(struct osmocom_ms *ms) +{ + int rc; + + gsm_settings_arfcn(ms); + + lapdm_channel_init(&ms->lapdm_channel, LAPDM_MODE_MS); + ms->lapdm_channel.lapdm_dcch.datalink[DL_SAPI3].dl.t200_sec = + T200_DCCH_SHARED; + ms->lapdm_channel.lapdm_dcch.datalink[DL_SAPI3].dl.t200_usec = 0; + ms->lapdm_channel.lapdm_acch.datalink[DL_SAPI3].dl.t200_sec = + T200_ACCH; + ms->lapdm_channel.lapdm_acch.datalink[DL_SAPI3].dl.t200_usec = 0; + lapdm_channel_set_l1(&ms->lapdm_channel, l1ctl_ph_prim_cb, ms); + + gsm_sim_init(ms); + gsm48_cc_init(ms); + gsm480_ss_init(ms); + gsm411_sms_init(ms); + gsm_voice_init(ms); + gsm_subscr_init(ms); + gsm48_rr_init(ms); + gsm48_mm_init(ms); + INIT_LLIST_HEAD(&ms->trans_list); + gsm322_init(ms); + + rc = layer2_open(ms, ms->settings.layer2_socket_path); + if (rc < 0) { + fprintf(stderr, "Failed during layer2_open()\n"); + ms->l2_wq.bfd.fd = -1; + mobile_exit(ms, 1); + return rc; + } + +#if 0 + rc = sap_open(ms, ms->settings.sap_socket_path); + if (rc < 0) { + fprintf(stderr, "Failed during sap_open(), no SIM reader\n"); + ms->sap_wq.bfd.fd = -1; + mobile_exit(ms, 1); + return rc; + } +#endif + + gsm_random_imei(&ms->settings); + + ms->shutdown = 0; + ms->started = 0; + + if (!strcmp(ms->settings.imei, "000000000000000")) { + printf("***\nWarning: Mobile '%s' has default IMEI: %s\n", + ms->name, ms->settings.imei); + printf("This could relate your identitiy to other users with " + "default IMEI.\n***\n"); + } + + l1ctl_tx_reset_req(ms, L1CTL_RES_T_FULL); + printf("Mobile '%s' initialized, please start phone now!\n", ms->name); + return 0; +} + +/* create ms instance */ +struct osmocom_ms *mobile_new(char *name) +{ + static struct osmocom_ms *ms; + + ms = talloc_zero(l23_ctx, struct osmocom_ms); + if (!ms) { + fprintf(stderr, "Failed to allocate MS\n"); + exit(1); + } + llist_add_tail(&ms->entity, &ms_list); + + strcpy(ms->name, name); + + ms->l2_wq.bfd.fd = -1; + ms->sap_wq.bfd.fd = -1; + + gsm_support_init(ms); + gsm_settings_init(ms); + + ms->shutdown = 2; /* being down */ + + if (mncc_recv_app) { + char name[32]; + + sprintf(name, "/tmp/ms_mncc_%s", ms->name); + + ms->mncc_entity.mncc_recv = mncc_recv_app; + ms->mncc_entity.sock_state = mncc_sock_init(ms, name, l23_ctx); + + } else if (ms->settings.ch_cap == GSM_CAP_SDCCH) + ms->mncc_entity.mncc_recv = mncc_recv_dummy; + else + ms->mncc_entity.mncc_recv = mncc_recv_mobile; + + + return ms; +} + +/* destroy ms instance */ +int mobile_delete(struct osmocom_ms *ms, int force) +{ + int rc; + + ms->deleting = 1; + + if (ms->shutdown == 0 || (ms->shutdown == 1 && force)) { + rc = mobile_exit(ms, force); + if (rc < 0) + return rc; + } + + if (mncc_recv_app) { + mncc_sock_exit(ms->mncc_entity.sock_state); + ms->mncc_entity.sock_state = NULL; + } + + return 0; +} + +/* handle global shutdown */ +int global_signal_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct osmocom_ms *ms, *ms2; + + if (subsys != SS_GLOBAL) + return 0; + + switch (signal) { + case S_GLOBAL_SHUTDOWN: + llist_for_each_entry_safe(ms, ms2, &ms_list, entity) + mobile_delete(ms, quit); + + /* if second signal is received, force to exit */ + quit = 1; + break; + } + return 0; +} + +/* global work handler */ +int l23_app_work(int *_quit) +{ + struct osmocom_ms *ms, *ms2; + int work = 0; + + llist_for_each_entry_safe(ms, ms2, &ms_list, entity) { + if (ms->shutdown != 2) + work |= mobile_work(ms); + if (ms->shutdown == 2) { + if (ms->l2_wq.bfd.fd > -1) { + layer2_close(ms); + ms->l2_wq.bfd.fd = -1; + } + + if (ms->sap_wq.bfd.fd > -1) { + sap_close(ms); + ms->sap_wq.bfd.fd = -1; + } + + if (ms->deleting) { + gsm_settings_exit(ms); + llist_del(&ms->entity); + talloc_free(ms); + work = 1; + } + } + } + + /* return, if a shutdown was scheduled (quit = 1) */ + *_quit = quit; + return work; +} + +/* global exit */ +int l23_app_exit(void) +{ + osmo_signal_unregister_handler(SS_L1CTL, &gsm322_l1_signal, NULL); + osmo_signal_unregister_handler(SS_L1CTL, &mobile_signal_cb, NULL); + osmo_signal_unregister_handler(SS_GLOBAL, &global_signal_cb, NULL); + + osmo_gps_close(); + + telnet_exit(); + + return 0; +} + +static struct vty_app_info vty_info = { + .name = "OsmocomBB", + .version = PACKAGE_VERSION, + .go_parent_cb = ms_vty_go_parent, +}; + +/* global init */ +int l23_app_init(int (*mncc_recv)(struct osmocom_ms *ms, int, void *), + const char *config_file, const char *vty_ip, uint16_t vty_port) +{ + struct telnet_connection dummy_conn; + int rc = 0; + + mncc_recv_app = mncc_recv; + + osmo_gps_init(); + + vty_init(&vty_info); + ms_vty_init(); + dummy_conn.priv = NULL; + vty_reading = 1; + if (config_file != NULL) { + rc = vty_read_config_file(config_file, &dummy_conn); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file:" + " '%s'\n", config_file); + fprintf(stderr, "Please check or create config file" + " using: 'touch %s'\n", config_file); + return rc; + } + } + vty_reading = 0; + telnet_init_dynif(l23_ctx, NULL, vty_ip, vty_port); + if (rc < 0) + return rc; + printf("VTY available on port %u.\n", vty_port); + + osmo_signal_register_handler(SS_GLOBAL, &global_signal_cb, NULL); + osmo_signal_register_handler(SS_L1CTL, &mobile_signal_cb, NULL); + osmo_signal_register_handler(SS_L1CTL, &gsm322_l1_signal, NULL); + + if (llist_empty(&ms_list)) { + struct osmocom_ms *ms; + + printf("No Mobile Station defined, creating: MS '1'\n"); + ms = mobile_new("1"); + if (ms) + mobile_init(ms); + } + + quit = 0; + + return 0; +} + diff --git a/src/host/layer23/src/mobile/gsm322.c b/src/host/layer23/src/mobile/gsm322.c new file mode 100644 index 00000000..ce5e1e1d --- /dev/null +++ b/src/host/layer23/src/mobile/gsm322.c @@ -0,0 +1,5170 @@ +/* + * (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#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/gsm/gsm_utils.h> +#include <osmocom/core/signal.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/networks.h> +#include <osmocom/bb/mobile/vty.h> +#include <osmocom/bb/mobile/app_mobile.h> + +#include <l1ctl_proto.h> + +const char *ba_version = "osmocom BA V1\n"; + +extern void *l23_ctx; + +static void gsm322_cs_timeout(void *arg); +static int gsm322_cs_select(struct osmocom_ms *ms, int index, uint16_t mcc, + uint16_t mnc, int any); +static int gsm322_m_switch_on(struct osmocom_ms *ms, struct msgb *msg); +static void gsm322_any_timeout(void *arg); +static int gsm322_nb_scan(struct osmocom_ms *ms); +static int gsm322_nb_synced(struct gsm322_cellsel *cs, int yes); +static int gsm322_nb_read(struct gsm322_cellsel *cs, int yes); +static int gsm322_c_camp_any_cell(struct osmocom_ms *ms, struct msgb *msg); +static int gsm322_nb_start(struct osmocom_ms *ms, int synced); +static void gsm322_cs_loss(void *arg); +static int gsm322_nb_meas_ind(struct osmocom_ms *ms, uint16_t arfcn, + uint8_t rx_lev); + +#define SYNC_RETRIES 1 +#define SYNC_RETRIES_SERVING 2 + +/* time for trying to sync and read BCCH of neighbour cell again + * NOTE: This value is not defined by TS, i think. */ +#define GSM58_TRY_AGAIN 30 + +/* time for reading BCCH of neighbour cell again */ +#define GSM58_READ_AGAIN 300 + +/* number of neighbour cells to monitor */ +#define GSM58_NB_NUMBER 6 + +/* Timeout for reading BCCH of neighbour cells */ +#define GSM322_NB_TIMEOUT 2 + +/* number of neighbour cells to measure for average */ +#define RLA_C_NUM 4 + +/* wait before doing neighbour cell reselecton due to a better cell again */ +#define GSM58_RESEL_THRESHOLD 15 + +//#define 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 prefered 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 informations 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 neigbour + * 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) +{ + if ((arfcn & ARFCN_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; + + 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[5]; + 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; + } + + /* penatly 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, substract 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_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); +} + +/* this is called whenever the serving cell is unselectied */ +static void gsm322_unselect_cell(struct gsm322_cellsel *cs) +{ + if (!cs->selected) + return; + + LOGP(DCS, LOGL_INFO, "Unselecting serving cell.\n"); + + cs->selected = 0; + if (cs->si) + cs->si->si5 = 0; /* unset SI5* */ + cs->si = NULL; + memset(&cs->sel_si, 0, sizeof(cs->sel_si)); + cs->sel_mcc = cs->sel_mnc = cs->sel_lac = cs->sel_id = 0; +} + +/* print to DCS logging */ +static void print_dcs(void *priv, const char *fmt, ...) +{ + static char buffer[256] = ""; + int in = strlen(buffer); + va_list args; + + va_start(args, fmt); + vsnprintf(buffer + in, sizeof(buffer) - in - 1, fmt, args); + buffer[sizeof(buffer) - in - 1] = '\0'; + va_end(args); + + if (buffer[0] && buffer[strlen(buffer) - 1] == '\n') { + LOGP(DCS, LOGL_INFO, "%s", buffer); + buffer[0] = '\0'; + } +} + +/* del forbidden LA */ +int gsm322_del_forbidden_la(struct osmocom_ms *ms, uint16_t mcc, + uint16_t mnc, uint16_t lac) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_la_list *la; + + llist_for_each_entry(la, &plmn->forbidden_la, entry) { + if (la->mcc == mcc && la->mnc == mnc && la->lac == lac) { + LOGP(DPLMN, LOGL_INFO, "Delete from list of forbidden " + "LAs (mcc=%s, mnc=%s, lac=%04x)\n", + gsm_print_mcc(mcc), gsm_print_mnc(mnc), lac); + llist_del(&la->entry); + talloc_free(la); + return 0; + } + } + + return -EINVAL; +} + +/* add forbidden LA */ +int gsm322_add_forbidden_la(struct osmocom_ms *ms, uint16_t mcc, + uint16_t mnc, uint16_t lac, uint8_t cause) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_la_list *la; + + LOGP(DPLMN, LOGL_INFO, "Add to list of forbidden LAs " + "(mcc=%s, mnc=%s, lac=%04x)\n", gsm_print_mcc(mcc), + gsm_print_mnc(mnc), lac); + la = talloc_zero(l23_ctx, struct gsm322_la_list); + if (!la) + return -ENOMEM; + la->mcc = mcc; + la->mnc = mnc; + la->lac = lac; + la->cause = cause; + llist_add_tail(&la->entry, &plmn->forbidden_la); + + return 0; +} + +/* search forbidden LA */ +int gsm322_is_forbidden_la(struct osmocom_ms *ms, uint16_t mcc, uint16_t mnc, + uint16_t lac) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_la_list *la; + + llist_for_each_entry(la, &plmn->forbidden_la, entry) { + if (la->mcc == mcc && la->mnc == mnc && la->lac == lac) + return 1; + } + + return 0; +} + +/* search for PLMN in all BA lists */ +static struct gsm322_ba_list *gsm322_find_ba_list(struct gsm322_cellsel *cs, + uint16_t mcc, uint16_t mnc) +{ + struct gsm322_ba_list *ba, *ba_found = NULL; + + /* search for BA list */ + llist_for_each_entry(ba, &cs->ba_list, entry) { + if (ba->mcc == mcc + && ba->mnc == mnc) { + ba_found = ba; + break; + } + } + + return ba_found; +} + +/* search available PLMN */ +int gsm322_is_plmn_avail_and_allow(struct gsm322_cellsel *cs, uint16_t mcc, + uint16_t mnc) +{ + int i; + + for (i = 0; i <= 1023+299; i++) { + if ((cs->list[i].flags & GSM322_CS_FLAG_TEMP_AA) + && cs->list[i].sysinfo + && cs->list[i].sysinfo->mcc == mcc + && cs->list[i].sysinfo->mnc == mnc) + return 1; + } + + return 0; +} + +/* search available HPLMN */ +int gsm322_is_hplmn_avail(struct gsm322_cellsel *cs, char *imsi) +{ + int i; + + for (i = 0; i <= 1023+299; i++) { + if ((cs->list[i].flags & GSM322_CS_FLAG_SYSINFO) + && cs->list[i].sysinfo + && gsm_match_mnc(cs->list[i].sysinfo->mcc, + cs->list[i].sysinfo->mnc, imsi)) + return 1; + } + + return 0; +} + +static const struct value_string gsm322_nb_state_names[] = { + { GSM322_NB_NEW, "new" }, + { GSM322_NB_NOT_SUP, "not sup" }, + { GSM322_NB_RLA_C, "RLA_C" }, + { GSM322_NB_NO_SYNC, "no sync" }, + { GSM322_NB_NO_BCCH, "no BCCH" }, + { GSM322_NB_SYSINFO, "SYSINFO" }, + { 0, NULL } +}; + +const char *get_nb_state_name(int value) +{ + return get_value_string(gsm322_nb_state_names, value); +} + + +/* + * timer + */ + +/*plmn search timer event */ +static void plmn_timer_timeout(void *arg) +{ + struct gsm322_plmn *plmn = arg; + struct msgb *nmsg; + + LOGP(DPLMN, LOGL_INFO, "HPLMN search timer has fired.\n"); + + /* indicate PLMN selection T timeout */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_HPLMN_SEARCH); + if (!nmsg) + return; + gsm322_plmn_sendmsg(plmn->ms, nmsg); +} + +/* start plmn search timer */ +static void start_plmn_timer(struct gsm322_plmn *plmn, int secs) +{ + LOGP(DPLMN, LOGL_INFO, "Starting HPLMN search timer with %d minutes.\n", + secs / 60); + plmn->timer.cb = plmn_timer_timeout; + plmn->timer.data = plmn; + osmo_timer_schedule(&plmn->timer, secs, 0); +} + +/* stop plmn search timer */ +static void stop_plmn_timer(struct gsm322_plmn *plmn) +{ + if (osmo_timer_pending(&plmn->timer)) { + LOGP(DPLMN, LOGL_INFO, "Stopping pending timer.\n"); + osmo_timer_del(&plmn->timer); + } +} + +/* start cell selection timer */ +void start_cs_timer(struct gsm322_cellsel *cs, int sec, int micro) +{ + LOGP(DCS, LOGL_DEBUG, "Starting CS timer with %d seconds.\n", sec); + cs->timer.cb = gsm322_cs_timeout; + cs->timer.data = cs; + osmo_timer_schedule(&cs->timer, sec, micro); +} + +/* stop cell selection timer */ +static void stop_cs_timer(struct gsm322_cellsel *cs) +{ + if (osmo_timer_pending(&cs->timer)) { + LOGP(DCS, LOGL_DEBUG, "stopping pending CS timer.\n"); + osmo_timer_del(&cs->timer); + } +} + +/* the following timer is used to search again for allowable cell, after + * loss of coverage. (loss of any allowed PLMN) */ + +/* start any cell selection timer */ +void start_any_timer(struct gsm322_cellsel *cs, int sec, int micro) +{ + LOGP(DCS, LOGL_DEBUG, "Starting 'any cell selection' timer with %d " + "seconds.\n", sec); + cs->any_timer.cb = gsm322_any_timeout; + cs->any_timer.data = cs; + osmo_timer_schedule(&cs->any_timer, sec, micro); +} + +/* stop cell selection timer */ +static void stop_any_timer(struct gsm322_cellsel *cs) +{ + if (osmo_timer_pending(&cs->any_timer)) { + LOGP(DCS, LOGL_DEBUG, "stopping pending 'any cell selection' " + "timer.\n"); + osmo_timer_del(&cs->any_timer); + } +} + +/* + * state change + */ + +static const struct value_string gsm322_a_state_names[] = { + { GSM322_A0_NULL, "A0 null"}, + { GSM322_A1_TRYING_RPLMN, "A1 trying RPLMN"}, + { GSM322_A2_ON_PLMN, "A2 on PLMN"}, + { GSM322_A3_TRYING_PLMN, "A3 trying PLMN"}, + { GSM322_A4_WAIT_FOR_PLMN, "A4 wait for PLMN to appear"}, + { GSM322_A5_HPLMN_SEARCH, "A5 HPLMN search"}, + { GSM322_A6_NO_SIM, "A6 no SIM inserted"}, + { 0, NULL } +}; + +const char *get_a_state_name(int value) +{ + return get_value_string(gsm322_a_state_names, value); +} + +static const struct value_string gsm322_m_state_names[] = { + { GSM322_M0_NULL, "M0 null"}, + { GSM322_M1_TRYING_RPLMN, "M1 trying RPLMN"}, + { GSM322_M2_ON_PLMN, "M2 on PLMN"}, + { GSM322_M3_NOT_ON_PLMN, "M3 not on PLMN"}, + { GSM322_M4_TRYING_PLMN, "M4 trying PLMN"}, + { GSM322_M5_NO_SIM, "M5 no SIM inserted"}, + { 0, NULL } +}; + +const char *get_m_state_name(int value) +{ + return get_value_string(gsm322_m_state_names, value); +} + +static const struct value_string gsm322_cs_state_names[] = { + { GSM322_C0_NULL, "C0 null"}, + { GSM322_C1_NORMAL_CELL_SEL, "C1 normal cell selection"}, + { GSM322_C2_STORED_CELL_SEL, "C2 stored cell selection"}, + { GSM322_C3_CAMPED_NORMALLY, "C3 camped normally"}, + { GSM322_C4_NORMAL_CELL_RESEL, "C4 normal cell re-selection"}, + { GSM322_C5_CHOOSE_CELL, "C5 choose cell"}, + { GSM322_C6_ANY_CELL_SEL, "C6 any cell selection"}, + { GSM322_C7_CAMPED_ANY_CELL, "C7 camped on any cell"}, + { GSM322_C8_ANY_CELL_RESEL, "C8 any cell re-selection"}, + { GSM322_C9_CHOOSE_ANY_CELL, "C9 choose any cell"}, + { GSM322_CONNECTED_MODE_1, "connected mode 1"}, + { GSM322_CONNECTED_MODE_2, "connected mode 2"}, + { GSM322_PLMN_SEARCH, "PLMN search"}, + { GSM322_HPLMN_SEARCH, "HPLMN search"}, + { GSM322_ANY_SEARCH, "ANY search"}, + { 0, NULL } +}; + +const char *get_cs_state_name(int value) +{ + return get_value_string(gsm322_cs_state_names, value); +} + +/* new automatic PLMN search state */ +static void new_a_state(struct gsm322_plmn *plmn, int state) +{ + if (plmn->ms->settings.plmn_mode != PLMN_MODE_AUTO) { + LOGP(DPLMN, LOGL_FATAL, "not in auto mode, please fix!\n"); + return; + } + + stop_plmn_timer(plmn); + + LOGP(DPLMN, LOGL_INFO, "new state '%s' -> '%s'\n", + get_a_state_name(plmn->state), get_a_state_name(state)); + + plmn->state = state; +} + +/* new manual PLMN search state */ +static void new_m_state(struct gsm322_plmn *plmn, int state) +{ + if (plmn->ms->settings.plmn_mode != PLMN_MODE_MANUAL) { + LOGP(DPLMN, LOGL_FATAL, "not in manual mode, please fix!\n"); + return; + } + + LOGP(DPLMN, LOGL_INFO, "new state '%s' -> '%s'\n", + get_m_state_name(plmn->state), get_m_state_name(state)); + + plmn->state = state; +} + +/* new Cell selection state */ +static void new_c_state(struct gsm322_cellsel *cs, int state) +{ + LOGP(DCS, LOGL_INFO, "new state '%s' -> '%s'\n", + get_cs_state_name(cs->state), get_cs_state_name(state)); + + /* stop cell selection timer, if running */ + stop_cs_timer(cs); + + /* stop scanning of power measurement */ + if (cs->powerscan) { + LOGP(DCS, LOGL_INFO, "changing state while power scanning\n"); + l1ctl_tx_reset_req(cs->ms, L1CTL_RES_T_FULL); + cs->powerscan = 0; + } + + cs->state = state; +} + +/* + * list of PLMNs + */ + +/* 4.4.3 create sorted list of PLMN + * + * the source of entries are + * + * - HPLMN + * - entries found in the SIM's PLMN Selector list + * - scanned PLMNs above -85 dB (random order) + * - scanned PLMNs below or equal -85 (by received level) + * + * NOTE: + * + * The list only includes networks found at last scan. + * + * The list always contains HPLMN if available, even if not used by PLMN + * search process at some conditions. + * + * The list contains all PLMNs even if not allowed, so entries have to be + * removed when selecting from the list. (In case we use manual cell selection, + * we need to provide non-allowed networks also.) + */ +static int gsm322_sort_list(struct osmocom_ms *ms) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm_sub_plmn_list *sim_entry; + struct gsm_sub_plmn_na *na_entry; + struct llist_head temp_list; + struct gsm322_plmn_list *temp, *found; + struct llist_head *lh, *lh2; + int i, entries, move; + int8_t search = 0; + + /* flush list */ + llist_for_each_safe(lh, lh2, &plmn->sorted_plmn) { + llist_del(lh); + talloc_free(lh); + } + + /* Create a temporary list of all networks */ + INIT_LLIST_HEAD(&temp_list); + for (i = 0; i <= 1023+299; i++) { + if (!(cs->list[i].flags & GSM322_CS_FLAG_TEMP_AA) + || !cs->list[i].sysinfo) + continue; + + /* search if network has multiple cells */ + found = NULL; + llist_for_each_entry(temp, &temp_list, entry) { + if (temp->mcc == cs->list[i].sysinfo->mcc + && temp->mnc == cs->list[i].sysinfo->mnc) { + found = temp; + break; + } + } + /* update or create */ + if (found) { + if (cs->list[i].rxlev > found->rxlev) + found->rxlev = cs->list[i].rxlev; + } else { + temp = talloc_zero(l23_ctx, struct gsm322_plmn_list); + if (!temp) + return -ENOMEM; + temp->mcc = cs->list[i].sysinfo->mcc; + temp->mnc = cs->list[i].sysinfo->mnc; + temp->rxlev = cs->list[i].rxlev; + llist_add_tail(&temp->entry, &temp_list); + } + } + + /* move Home PLMN, if in list, else add it */ + if (subscr->sim_valid) { + found = NULL; + llist_for_each_entry(temp, &temp_list, entry) { + if (gsm_match_mnc(temp->mcc, temp->mnc, subscr->imsi)) { + found = temp; + break; + } + } + if (found) { + /* move */ + llist_del(&found->entry); + llist_add_tail(&found->entry, &plmn->sorted_plmn); + } + } + + /* move entries if in SIM's PLMN Selector list */ + llist_for_each_entry(sim_entry, &subscr->plmn_list, entry) { + found = NULL; + llist_for_each_entry(temp, &temp_list, entry) { + if (temp->mcc == sim_entry->mcc + && temp->mnc == sim_entry->mnc) { + found = temp; + break; + } + } + if (found) { + llist_del(&found->entry); + llist_add_tail(&found->entry, &plmn->sorted_plmn); + } + } + + /* move PLMN above -85 dBm in random order */ + entries = 0; + llist_for_each_entry(temp, &temp_list, entry) { + if (rxlev2dbm(temp->rxlev) > -85) + entries++; + } + while(entries) { + move = 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 ohter PLMN in decreasing order */ + while(1) { + found = NULL; + llist_for_each_entry(temp, &temp_list, entry) { + if (!found + || temp->rxlev > search) { + search = temp->rxlev; + found = temp; + } + } + if (!found) + break; + llist_del(&found->entry); + llist_add_tail(&found->entry, &plmn->sorted_plmn); + } + + /* mark forbidden PLMNs, if in list of forbidden networks */ + i = 0; + llist_for_each_entry(temp, &plmn->sorted_plmn, entry) { + llist_for_each_entry(na_entry, &subscr->plmn_na, entry) { + if (temp->mcc == na_entry->mcc + && temp->mnc == na_entry->mnc) { + temp->cause = na_entry->cause; + break; + } + } + LOGP(DPLMN, LOGL_INFO, "Creating Sorted PLMN list. " + "(%02d: mcc %s mnc %s allowed %s rx-lev %s)\n", + i, gsm_print_mcc(temp->mcc), + gsm_print_mnc(temp->mnc), (temp->cause) ? "no ":"yes", + gsm_print_rxlev(temp->rxlev)); + i++; + } + + gsm322_dump_sorted_plmn(ms); + + return 0; +} + +/* + * handler for automatic search + */ + +/* go On PLMN state */ +static int gsm322_a_go_on_plmn(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm_subscriber *subscr = &ms->subscr; + + new_a_state(plmn, GSM322_A2_ON_PLMN); + + /* start timer, if on VPLMN of home country OR special case */ + if (!gsm_match_mnc(plmn->mcc, plmn->mnc, subscr->imsi) + && (subscr->always_search_hplmn + || gsm_match_mcc(plmn->mcc, subscr->imsi)) + && subscr->sim_valid && subscr->t6m_hplmn) + start_plmn_timer(plmn, subscr->t6m_hplmn * 360); + else + stop_plmn_timer(plmn); + + return 0; +} + +/* go to Wait for PLMNs to appear state */ +static int gsm322_a_go_wait_for_plmns(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct msgb *nmsg; + struct gsm322_msg *ngm; + + new_a_state(plmn, GSM322_A4_WAIT_FOR_PLMN); + + /* we must forward this, otherwhise "Any cell selection" + * will not start automatically. + */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN); + if (!nmsg) + return -ENOMEM; + ngm = (struct gsm322_msg *) nmsg->data; + ngm->limited = 1; + gsm322_cs_sendmsg(ms, nmsg); + + return 0; +} + +/* no (more) PLMN in list */ +static int gsm322_a_no_more_plmn(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm322_cellsel *cs = &ms->cellsel; + struct msgb *nmsg; + int found; + + /* any allowable PLMN available? */ + found = gsm322_cs_select(ms, -1, 0, 0, 0); + + /* if no PLMN in list: + * this means that we are at a point where we camp on any cell or + * no cell ist available. */ + if (found < 0) { + if (subscr->plmn_valid) { + LOGP(DPLMN, LOGL_INFO, "Not any PLMN allowable. " + "Do limited search with RPLMN.\n"); + plmn->mcc = subscr->plmn_mcc; + plmn->mnc = subscr->plmn_mnc; + } else + if (subscr->sim_valid) { + LOGP(DPLMN, LOGL_INFO, "Not any PLMN allowable. " + "Do limited search with HPLMN.\n"); + plmn->mcc = subscr->mcc; + plmn->mnc = subscr->mnc; + } else { + LOGP(DPLMN, LOGL_INFO, "Not any PLMN allowable. " + "Do limited search with no PLMN.\n"); + plmn->mcc = 0; + plmn->mnc = 0; + } + + return gsm322_a_go_wait_for_plmns(ms, msg); + } + + /* select first PLMN in list */ + plmn->mcc = cs->list[found].sysinfo->mcc; + plmn->mnc = cs->list[found].sysinfo->mnc; + + LOGP(DPLMN, LOGL_INFO, "PLMN available after searching PLMN list " + "(mcc=%s mnc=%s %s, %s)\n", + gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc), + gsm_get_mcc(plmn->mcc), gsm_get_mnc(plmn->mcc, plmn->mnc)); + + /* indicate New PLMN */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN); + if (!nmsg) + return -ENOMEM; + gsm322_cs_sendmsg(ms, nmsg); + + /* go On PLMN */ + return gsm322_a_go_on_plmn(ms, msg); +} + +/* select first PLMN in list */ +static int gsm322_a_sel_first_plmn(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm_subscriber *subscr = &ms->subscr; + struct msgb *nmsg; + struct gsm322_plmn_list *plmn_entry; + struct gsm322_plmn_list *plmn_first = NULL; + int i; + + /* generate list */ + gsm322_sort_list(ms); + + /* select first entry */ + i = 0; + llist_for_each_entry(plmn_entry, &plmn->sorted_plmn, entry) { + /* if last selected PLMN was HPLMN, we skip that */ + if (gsm_match_mnc(plmn_entry->mcc, plmn_entry->mnc, + subscr->imsi) + && plmn_entry->mcc == plmn->mcc + && plmn_entry->mnc == plmn->mnc) { + LOGP(DPLMN, LOGL_INFO, "Skip HPLMN, because it was " + "previously selected.\n"); + i++; + continue; + } + /* select first allowed network */ + if (!plmn_entry->cause) { + plmn_first = plmn_entry; + break; + } + LOGP(DPLMN, LOGL_INFO, "Skip PLMN (%02d: mcc=%s, mnc=%s), " + "because it is not allowed (cause %d).\n", i, + gsm_print_mcc(plmn_entry->mcc), + gsm_print_mnc(plmn_entry->mnc), + plmn_entry->cause); + i++; + } + plmn->plmn_curr = i; + + /* if no PLMN in list */ + if (!plmn_first) { + LOGP(DPLMN, LOGL_INFO, "No PLMN in list.\n"); + gsm322_a_no_more_plmn(ms, msg); + + return 0; + } + + LOGP(DPLMN, LOGL_INFO, "Selecting PLMN from list. (%02d: mcc=%s " + "mnc=%s %s, %s)\n", plmn->plmn_curr, + gsm_print_mcc(plmn_first->mcc), gsm_print_mnc(plmn_first->mnc), + gsm_get_mcc(plmn_first->mcc), + gsm_get_mnc(plmn_first->mcc, plmn_first->mnc)); + + /* set current network */ + plmn->mcc = plmn_first->mcc; + plmn->mnc = plmn_first->mnc; + + new_a_state(plmn, GSM322_A3_TRYING_PLMN); + + /* indicate New PLMN */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN); + if (!nmsg) + return -ENOMEM; + gsm322_cs_sendmsg(ms, nmsg); + + return 0; +} + +/* select next PLMN in list */ +static int gsm322_a_sel_next_plmn(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct msgb *nmsg; + struct gsm322_plmn_list *plmn_entry; + struct gsm322_plmn_list *plmn_next = NULL; + int i, ii; + + /* select next entry from list */ + i = 0; + ii = plmn->plmn_curr + 1; + llist_for_each_entry(plmn_entry, &plmn->sorted_plmn, entry) { + /* skip previously selected networks */ + if (i < ii) { + i++; + continue; + } + /* select next allowed network */ + if (!plmn_entry->cause) { + plmn_next = plmn_entry; + break; + } + LOGP(DPLMN, LOGL_INFO, "Skip PLMN (%02d: mcc=%s, mnc=%s), " + "because it is not allowed (cause %d).\n", i, + gsm_print_mcc(plmn_entry->mcc), + gsm_print_mnc(plmn_entry->mnc), + plmn_entry->cause); + i++; + } + plmn->plmn_curr = i; + + /* if no more PLMN in list */ + if (!plmn_next) { + LOGP(DPLMN, LOGL_INFO, "No more PLMN in list.\n"); + gsm322_a_no_more_plmn(ms, msg); + return 0; + } + + /* set next network */ + plmn->mcc = plmn_next->mcc; + plmn->mnc = plmn_next->mnc; + + LOGP(DPLMN, LOGL_INFO, "Selecting PLMN from list. (%02d: mcc=%s " + "mnc=%s %s, %s)\n", plmn->plmn_curr, + gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc), + gsm_get_mcc(plmn->mcc), gsm_get_mnc(plmn->mcc, plmn->mnc)); + + new_a_state(plmn, GSM322_A3_TRYING_PLMN); + + /* indicate New PLMN */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN); + if (!nmsg) + return -ENOMEM; + gsm322_cs_sendmsg(ms, nmsg); + + return 0; +} + +/* User re-selection event */ +static int gsm322_a_user_resel(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm322_plmn_list *plmn_entry; + struct gsm322_plmn_list *plmn_found = NULL; + struct msgb *nmsg; + + if (!subscr->sim_valid) { + return 0; + } + + /* try again later, if not idle */ + if (cs->state == GSM322_CONNECTED_MODE_1 + || cs->state == GSM322_CONNECTED_MODE_2) { + LOGP(DPLMN, LOGL_INFO, "Not idle, rejecting.\n"); + + return 0; + } + + /* search current PLMN in list */ + llist_for_each_entry(plmn_entry, &plmn->sorted_plmn, entry) { + if (plmn_entry->mcc == plmn->mcc + && plmn_entry->mnc == plmn->mnc) { + plmn_found = plmn_entry; + break; + } + } + + /* abort if list is empty */ + if (!plmn_found) { + LOGP(DPLMN, LOGL_INFO, "Selected PLMN not in list, strange!\n"); + return 0; + } + + LOGP(DPLMN, LOGL_INFO, "Movin selected PLMN to the bottom of the list " + "and restarting PLMN search process.\n"); + + /* move entry to end of list */ + llist_del(&plmn_found->entry); + llist_add_tail(&plmn_found->entry, &plmn->sorted_plmn); + + /* tell MM that we selected a PLMN */ + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_USER_PLMN_SEL); + if (!nmsg) + return -ENOMEM; + gsm48_mmevent_msg(ms, nmsg); + + /* select first PLMN in list */ + return gsm322_a_sel_first_plmn(ms, msg); +} + +/* PLMN becomes available */ +static int gsm322_a_plmn_avail(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm322_msg *gm = (struct gsm322_msg *) msg->data; + + if (subscr->plmn_valid && plmn->mcc == gm->mcc + && plmn->mnc == gm->mnc) { + struct msgb *nmsg; + + new_m_state(plmn, GSM322_A1_TRYING_RPLMN); + + LOGP(DPLMN, LOGL_INFO, "Last selected PLMN becomes available " + "again.\n"); + + /* indicate New PLMN */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN); + if (!nmsg) + return -ENOMEM; + gsm322_cs_sendmsg(ms, nmsg); + + return 0; + + } else { + /* select first PLMN in list */ + LOGP(DPLMN, LOGL_INFO, "Some PLMN became available, start PLMN " + "search process.\n"); + return gsm322_a_sel_first_plmn(ms, msg); + } +} + +/* loss of radio coverage */ +static int gsm322_a_loss_of_radio(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + int found; + + /* any allowable PLMN available */ + found = gsm322_cs_select(ms, -1, 0, 0, 0); + + /* if PLMN in list */ + if (found >= 0) { + LOGP(DPLMN, LOGL_INFO, "PLMN available (mcc=%s mnc=%s " + "%s, %s)\n", gsm_print_mcc( + cs->list[found].sysinfo->mcc), + gsm_print_mnc(cs->list[found].sysinfo->mnc), + gsm_get_mcc(cs->list[found].sysinfo->mcc), + gsm_get_mnc(cs->list[found].sysinfo->mcc, + cs->list[found].sysinfo->mnc)); + return gsm322_a_sel_first_plmn(ms, msg); + } + + LOGP(DPLMN, LOGL_INFO, "PLMN not available after loss of coverage.\n"); + + return gsm322_a_go_wait_for_plmns(ms, msg); +} + +/* MS is switched on OR SIM is inserted OR removed */ +static int gsm322_a_switch_on(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm322_plmn *plmn = &ms->plmn; + struct msgb *nmsg; + + if (!subscr->sim_valid) { + LOGP(DSUM, LOGL_INFO, "SIM is removed\n"); + LOGP(DPLMN, LOGL_INFO, "SIM is removed\n"); + new_a_state(plmn, GSM322_A6_NO_SIM); + + return 0; + } + + /* if there is a registered PLMN */ + if (subscr->plmn_valid) { + /* select the registered PLMN */ + plmn->mcc = subscr->plmn_mcc; + plmn->mnc = subscr->plmn_mnc; + + LOGP(DSUM, LOGL_INFO, "Start search of last registered PLMN " + "(mcc=%s mnc=%s %s, %s)\n", gsm_print_mcc(plmn->mcc), + gsm_print_mnc(plmn->mnc), gsm_get_mcc(plmn->mcc), + gsm_get_mnc(plmn->mcc, plmn->mnc)); + LOGP(DPLMN, LOGL_INFO, "Use RPLMN (mcc=%s mnc=%s " + "%s, %s)\n", gsm_print_mcc(plmn->mcc), + gsm_print_mnc(plmn->mnc), gsm_get_mcc(plmn->mcc), + gsm_get_mnc(plmn->mcc, plmn->mnc)); + + new_a_state(plmn, GSM322_A1_TRYING_RPLMN); + + /* indicate New PLMN */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN); + if (!nmsg) + return -ENOMEM; + gsm322_cs_sendmsg(ms, nmsg); + + return 0; + } + + plmn->mcc = plmn->mnc = 0; + + /* initiate search at cell selection */ + LOGP(DSUM, LOGL_INFO, "Search for network\n"); + LOGP(DPLMN, LOGL_INFO, "Switch on, no RPLMN, start PLMN search " + "first.\n"); + + nmsg = gsm322_msgb_alloc(GSM322_EVENT_PLMN_SEARCH_START); + if (!nmsg) + return -ENOMEM; + gsm322_cs_sendmsg(ms, nmsg); + + return 0; +} + +/* MS is switched off */ +static int gsm322_a_switch_off(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + + new_a_state(plmn, GSM322_A0_NULL); + + return 0; +} + +static int gsm322_a_sim_insert(struct osmocom_ms *ms, struct msgb *msg) +{ + LOGP(DPLMN, LOGL_INFO, "SIM already inserted when switched on.\n"); + return 0; +} + +/* SIM is removed */ +static int gsm322_a_sim_removed(struct osmocom_ms *ms, struct msgb *msg) +{ + struct msgb *nmsg; + + /* indicate SIM remove to cell selection process */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_SIM_REMOVE); + if (!nmsg) + return -ENOMEM; + gsm322_cs_sendmsg(ms, nmsg); + + /* flush list of PLMNs */ + gsm_subscr_del_forbidden_plmn(&ms->subscr, 0, 0); + + return gsm322_a_switch_on(ms, msg); +} + +/* location update response: "Roaming not allowed" */ +static int gsm322_a_roaming_na(struct osmocom_ms *ms, struct msgb *msg) +{ + /* store in list of forbidden LAs is done in gsm48* */ + + return gsm322_a_sel_first_plmn(ms, msg); +} + +/* On VPLMN of home country and timeout occurs */ +static int gsm322_a_hplmn_search_start(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_cellsel *cs = &ms->cellsel; + struct msgb *nmsg; + + /* try again later, if not idle and not camping */ + if (cs->state != GSM322_C3_CAMPED_NORMALLY) { + LOGP(DPLMN, LOGL_INFO, "Not camping normally, wait some more." + "\n"); + start_plmn_timer(plmn, 60); + + return 0; + } + + new_a_state(plmn, GSM322_A5_HPLMN_SEARCH); + + /* initiate search at cell selection */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_HPLMN_SEARCH); + if (!nmsg) + return -ENOMEM; + gsm322_cs_sendmsg(ms, nmsg); + + return 0; +} + +/* manual mode selected */ +static int gsm322_a_sel_manual(struct osmocom_ms *ms, struct msgb *msg) +{ + struct msgb *nmsg; + + /* restart state machine */ + gsm322_a_switch_off(ms, msg); + ms->settings.plmn_mode = PLMN_MODE_MANUAL; + gsm322_m_switch_on(ms, msg); + + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_USER_PLMN_SEL); + if (!nmsg) + return -ENOMEM; + gsm48_mmevent_msg(ms, nmsg); + + return 0; +} + +/* + * handler for manual search + */ + +/* display PLMNs and to Not on PLMN */ +static int gsm322_m_display_plmns(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_msg *gm = (struct gsm322_msg *) msg->data; + int msg_type = gm->msg_type; + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm_sub_plmn_list *temp; + struct msgb *nmsg; + struct gsm322_msg *ngm; + + /* generate list */ + gsm322_sort_list(ms); + + vty_notify(ms, NULL); + switch (msg_type) { + case GSM322_EVENT_REG_FAILED: + vty_notify(ms, "Failed to register to network %s, %s " + "(%s, %s)\n", + gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc), + gsm_get_mcc(plmn->mcc), + gsm_get_mnc(plmn->mcc, plmn->mnc)); + break; + case GSM322_EVENT_NO_CELL_FOUND: + vty_notify(ms, "No cell found for network %s, %s " + "(%s, %s)\n", + gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc), + gsm_get_mcc(plmn->mcc), + gsm_get_mnc(plmn->mcc, plmn->mnc)); + break; + case GSM322_EVENT_ROAMING_NA: + vty_notify(ms, "Roaming not allowed to network %s, %s " + "(%s, %s)\n", + gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc), + gsm_get_mcc(plmn->mcc), + gsm_get_mnc(plmn->mcc, plmn->mnc)); + break; + } + + if (llist_empty(&plmn->sorted_plmn)) + vty_notify(ms, "Search network!\n"); + else { + vty_notify(ms, "Search or select from network:\n"); + llist_for_each_entry(temp, &plmn->sorted_plmn, entry) + vty_notify(ms, " Network %s, %s (%s, %s)\n", + gsm_print_mcc(temp->mcc), + gsm_print_mnc(temp->mnc), + gsm_get_mcc(temp->mcc), + gsm_get_mnc(temp->mcc, temp->mnc)); + } + + /* go Not on PLMN state */ + new_m_state(plmn, GSM322_M3_NOT_ON_PLMN); + + /* we must forward this, otherwhise "Any cell selection" + * will not start automatically. + * this way we get back to the last PLMN, in case we gained + * our coverage back. + */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN); + if (!nmsg) + return -ENOMEM; + ngm = (struct gsm322_msg *) nmsg->data; + ngm->limited = 1; + gsm322_cs_sendmsg(ms, nmsg); + + return 0; +} + +/* user starts reselection */ +static int gsm322_m_user_resel(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm_subscriber *subscr = &ms->subscr; + struct msgb *nmsg; + + /* unselect PLMN. after search, the process will wait until a PLMN is + * selected by the user. this prevents from switching back to the + * last selected PLMN and destroying the list of scanned networks. + */ + plmn->mcc = plmn->mnc = 0; + + if (!subscr->sim_valid) { + return 0; + } + + /* try again later, if not idle */ + if (cs->state == GSM322_CONNECTED_MODE_1 + || cs->state == GSM322_CONNECTED_MODE_2) { + LOGP(DPLMN, LOGL_INFO, "Not idle, rejecting.\n"); + + return 0; + } + + /* initiate search at cell selection */ + LOGP(DPLMN, LOGL_INFO, "User re-select, start PLMN search first.\n"); + + /* tell MM that we selected a PLMN */ + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_USER_PLMN_SEL); + if (!nmsg) + return -ENOMEM; + gsm48_mmevent_msg(ms, nmsg); + + /* triffer PLMN search */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_PLMN_SEARCH_START); + if (!nmsg) + return -ENOMEM; + gsm322_cs_sendmsg(ms, nmsg); + + return 0; +} + +/* MS is switched on OR SIM is inserted OR removed */ +static int gsm322_m_switch_on(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm322_plmn *plmn = &ms->plmn; + struct msgb *nmsg; + + if (!subscr->sim_valid) { + LOGP(DSUM, LOGL_INFO, "SIM is removed\n"); + LOGP(DPLMN, LOGL_INFO, "Switch on without SIM.\n"); + new_m_state(plmn, GSM322_M5_NO_SIM); + + return 0; + } + + /* if there is a registered PLMN */ + if (subscr->plmn_valid) { + struct msgb *nmsg; + + /* select the registered PLMN */ + plmn->mcc = subscr->plmn_mcc; + plmn->mnc = subscr->plmn_mnc; + + LOGP(DSUM, LOGL_INFO, "Start search of last registered PLMN " + "(mcc=%s mnc=%s %s, %s)\n", gsm_print_mcc(plmn->mcc), + gsm_print_mnc(plmn->mnc), gsm_get_mcc(plmn->mcc), + gsm_get_mnc(plmn->mcc, plmn->mnc)); + LOGP(DPLMN, LOGL_INFO, "Use RPLMN (mcc=%s mnc=%s " + "%s, %s)\n", gsm_print_mcc(plmn->mcc), + gsm_print_mnc(plmn->mnc), gsm_get_mcc(plmn->mcc), + gsm_get_mnc(plmn->mcc, plmn->mnc)); + + new_m_state(plmn, GSM322_M1_TRYING_RPLMN); + + /* indicate New PLMN */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN); + if (!nmsg) + return -ENOMEM; + gsm322_cs_sendmsg(ms, nmsg); + + return 0; + } + + plmn->mcc = plmn->mnc = 0; + + /* initiate search at cell selection */ + LOGP(DSUM, LOGL_INFO, "Search for network\n"); + LOGP(DPLMN, LOGL_INFO, "Switch on, start PLMN search first.\n"); + + nmsg = gsm322_msgb_alloc(GSM322_EVENT_PLMN_SEARCH_START); + if (!nmsg) + return -ENOMEM; + gsm322_cs_sendmsg(ms, nmsg); + + return 0; +} + +/* MS is switched off */ +static int gsm322_m_switch_off(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + + stop_plmn_timer(plmn); + + new_m_state(plmn, GSM322_M0_NULL); + + return 0; +} + +static int gsm322_m_sim_insert(struct osmocom_ms *ms, struct msgb *msg) +{ + LOGP(DPLMN, LOGL_INFO, "SIM already inserted when switched on.\n"); + return 0; +} + +/* SIM is removed */ +static int gsm322_m_sim_removed(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct msgb *nmsg; + + stop_plmn_timer(plmn); + + /* indicate SIM remove to cell selection process */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_SIM_REMOVE); + if (!nmsg) + return -ENOMEM; + gsm322_cs_sendmsg(ms, nmsg); + + /* flush list of PLMNs */ + gsm_subscr_del_forbidden_plmn(&ms->subscr, 0, 0); + + return gsm322_m_switch_on(ms, msg); +} + +/* go to On PLMN state */ +static int gsm322_m_go_on_plmn(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm_subscriber *subscr = &ms->subscr; + + /* set last registered PLMN */ + subscr->plmn_valid = 1; + subscr->plmn_mcc = plmn->mcc; + subscr->plmn_mnc = plmn->mnc; + + new_m_state(plmn, GSM322_M2_ON_PLMN); + + return 0; +} + +/* previously selected PLMN becomes available again */ +static int gsm322_m_plmn_avail(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct msgb *nmsg; + + new_m_state(plmn, GSM322_M1_TRYING_RPLMN); + + LOGP(DPLMN, LOGL_INFO, "Last selected PLMN becomes available again.\n"); + + /* indicate New PLMN */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN); + if (!nmsg) + return -ENOMEM; + gsm322_cs_sendmsg(ms, nmsg); + + return 0; +} + +/* the user has selected given PLMN */ +static int gsm322_m_choose_plmn(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm322_msg *gm = (struct gsm322_msg *) msg->data; + struct msgb *nmsg; + + /* use user selection */ + plmn->mcc = gm->mcc; + plmn->mnc = gm->mnc; + + LOGP(DPLMN, LOGL_INFO, "User selects PLMN. (mcc=%s mnc=%s " + "%s, %s)\n", gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc), + gsm_get_mcc(plmn->mcc), gsm_get_mnc(plmn->mcc, plmn->mnc)); + + /* if selected PLMN is in list of forbidden PLMNs */ + gsm_subscr_del_forbidden_plmn(subscr, plmn->mcc, plmn->mnc); + + new_m_state(plmn, GSM322_M4_TRYING_PLMN); + + /* tell MM that we selected a PLMN */ + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_USER_PLMN_SEL); + if (!nmsg) + return -ENOMEM; + gsm48_mmevent_msg(ms, nmsg); + + /* indicate New PLMN */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_NEW_PLMN); + if (!nmsg) + return -ENOMEM; + gsm322_cs_sendmsg(ms, nmsg); + + return 0; +} + +/* auto mode selected */ +static int gsm322_m_sel_auto(struct osmocom_ms *ms, struct msgb *msg) +{ + struct msgb *nmsg; + + /* restart state machine */ + gsm322_m_switch_off(ms, msg); + ms->settings.plmn_mode = PLMN_MODE_AUTO; + gsm322_a_switch_on(ms, msg); + + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_USER_PLMN_SEL); + if (!nmsg) + return -ENOMEM; + gsm48_mmevent_msg(ms, nmsg); + + return 0; +} + +/* if no cell is found in other states than in *_TRYING_* states */ +static int gsm322_am_no_cell_found(struct osmocom_ms *ms, struct msgb *msg) +{ + struct msgb *nmsg; + + /* Tell cell selection process to handle "no cell found". */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_NO_CELL_FOUND); + if (!nmsg) + return -ENOMEM; + gsm322_cs_sendmsg(ms, nmsg); + + return 0; +} + +/* + * cell scanning process + */ + +/* select a suitable and allowable cell */ +static int gsm322_cs_select(struct osmocom_ms *ms, int index, uint16_t mcc, + uint16_t mnc, int any) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm_settings *set = &ms->settings; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_sysinfo *s; + int start, end, i, found = -1, power = 0; + uint8_t flags, mask; + uint16_t acc_class; + int16_t c1; + enum gsm_band band; + int class; + + /* set our access class depending on the cell selection type */ + if (any) { + acc_class = subscr->acc_class | 0x0400; /* add emergency */ + LOGP(DCS, LOGL_DEBUG, "Select using access class with " + "Emergency class.\n"); + } else { + acc_class = subscr->acc_class; + LOGP(DCS, LOGL_DEBUG, "Select using access class \n"); + } + + /* flags to match */ + mask = GSM322_CS_FLAG_SUPPORT | GSM322_CS_FLAG_POWER + | GSM322_CS_FLAG_SIGNAL | GSM322_CS_FLAG_SYSINFO; + if (cs->state == GSM322_C2_STORED_CELL_SEL + || cs->state == GSM322_C5_CHOOSE_CELL) + mask |= GSM322_CS_FLAG_BA; + flags = mask; /* all masked flags are requied */ + + /* 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 informations for us */ + if (!s || (cs->list[i].flags & mask) != flags) { + continue; + } + + /* check C1 criteria not fullfilled */ + // TODO: class 3 DCS mobile + band = gsm_arfcn2band(index2arfcn(i)); + class = class_of_band(ms, band); + c1 = calculate_c1(DCS, rxlev2dbm(cs->list[i].rxlev), + s->rxlev_acc_min_db, + ms_pwr_dbm(band, s->ms_txpwr_max_cch), + ms_class_gmsk_dbm(band, class)); + if (!set->stick && c1 < 0) { + LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: C1 criterion " + "not met. (C1 = %d)\n", + gsm_print_arfcn(index2arfcn(i)), c1); + continue; + } + + /* if cell is barred and we don't override */ + if (!subscr->acc_barr + && (cs->list[i].flags & GSM322_CS_FLAG_BARRED)) { + LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is " + "barred.\n", gsm_print_arfcn(index2arfcn(i))); + continue; + } + + /* if we have no access to the cell and we don't override */ + if (!subscr->acc_barr + && !(acc_class & (s->class_barr ^ 0xffff))) { + LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Class is " + "barred for our access. (access=%04x " + "barred=%04x)\n", + gsm_print_arfcn(index2arfcn(i)), + acc_class, s->class_barr); + continue; + } + + /* store temporary available and allowable flag */ + cs->list[i].flags |= GSM322_CS_FLAG_TEMP_AA; + + /* if cell is in list of forbidden LAs */ + if ((cs->list[i].flags & GSM322_CS_FLAG_FORBIDD)) { + if (!any) { + LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is " + "in list of forbidden LAs. (mcc=%s " + "mnc=%s lai=%04x)\n", + gsm_print_arfcn(index2arfcn(i)), + gsm_print_mcc(s->mcc), + gsm_print_mnc(s->mnc), s->lac); + continue; + } + LOGP(DCS, LOGL_INFO, "Accept ARFCN %s: Cell is in " + "list of forbidden LAs, but we search for any " + "cell. (mcc=%s mnc=%s lai=%04x)\n", + gsm_print_arfcn(index2arfcn(i)), + gsm_print_mcc(s->mcc), + gsm_print_mnc(s->mnc), s->lac); + cs->list[i].flags &= ~GSM322_CS_FLAG_TEMP_AA; + } + + /* if cell is in list of forbidden PLMNs */ + if (gsm_subscr_is_forbidden_plmn(subscr, s->mcc, s->mnc)) { + if (!any) { + LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is " + "in list of forbidden PLMNs. (mcc=%s " + "mnc=%s)\n", + gsm_print_arfcn(index2arfcn(i)), + gsm_print_mcc(s->mcc), + gsm_print_mnc(s->mnc)); + continue; + } + LOGP(DCS, LOGL_INFO, "Accept ARFCN %s: Cell is in list " + "of forbidden PLMNs, but we search for any " + "cell. (mcc=%s mnc=%s)\n", + gsm_print_arfcn(index2arfcn(i)), + gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc)); + cs->list[i].flags &= ~GSM322_CS_FLAG_TEMP_AA; + } + + /* if we search a specific PLMN, but it does not match */ + if (!any && mcc && (mcc != s->mcc + || mnc != s->mnc)) { + LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: PLMN of cell " + "does not match target PLMN. (mcc=%s " + "mnc=%s)\n", gsm_print_arfcn(index2arfcn(i)), + gsm_print_mcc(s->mcc), + gsm_print_mnc(s->mnc)); + continue; + } + + LOGP(DCS, LOGL_INFO, "Cell ARFCN %s: Cell found, (rxlev=%s " + "mcc=%s mnc=%s lac=%04x %s, %s)\n", + gsm_print_arfcn(index2arfcn(i)), + gsm_print_rxlev(cs->list[i].rxlev), + gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc), s->lac, + gsm_get_mcc(s->mcc), gsm_get_mnc(s->mcc, s->mnc)); + + /* find highest power cell */ + if (found < 0 || cs->list[i].rxlev > power) { + power = cs->list[i].rxlev; + found = i; + } + } + + if (found >= 0) + LOGP(DCS, LOGL_INFO, "Cell ARFCN %s selected.\n", + gsm_print_arfcn(index2arfcn(found))); + + return found; +} + +/* re-select a suitable and allowable cell */ +static int gsm322_cs_reselect(struct osmocom_ms *ms, uint16_t mcc, + uint16_t mnc, int any) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_sysinfo *s = cs->si; + int i = cs->arfci; + uint16_t acc_class; + + /* set our access class depending on the cell selection type */ + if (any) { + acc_class = subscr->acc_class | 0x0400; /* add emergency */ + LOGP(DCS, LOGL_DEBUG, "Select using access class with " + "Emergency class.\n"); + } else { + acc_class = subscr->acc_class; + LOGP(DCS, LOGL_DEBUG, "Select using access class \n"); + } + + /* if cell is barred and we don't override */ + if (!subscr->acc_barr + && (cs->list[i].flags & GSM322_CS_FLAG_BARRED)) { + LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is barred.\n", + gsm_print_arfcn(index2arfcn(i))); + return -1; + } + + /* if cell is in list of forbidden LAs */ + if (!any && (cs->list[i].flags & GSM322_CS_FLAG_FORBIDD)) { + LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is in list of " + "forbidden LAs. (mcc=%s mnc=%s lai=%04x)\n", + gsm_print_arfcn(index2arfcn(i)), gsm_print_mcc(s->mcc), + gsm_print_mnc(s->mnc), s->lac); + return -1; + } + + /* if cell is in list of forbidden PLMNs */ + if (!any && gsm_subscr_is_forbidden_plmn(subscr, s->mcc, s->mnc)) { + LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Cell is in " + "list of forbidden PLMNs. (mcc=%s mnc=%s)\n", + gsm_print_arfcn(index2arfcn(i)), + gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc)); + return -1; + } + + /* if we have no access to the cell and we don't override */ + if (!subscr->acc_barr + && !(acc_class & (s->class_barr ^ 0xffff))) { + LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: Class is barred for our " + "access. (access=%04x barred=%04x)\n", + gsm_print_arfcn(index2arfcn(i)), acc_class, + s->class_barr); + return -1; + } + + /* if we search a specific PLMN, but it does not match */ + if (!any && mcc && (mcc != s->mcc + || mnc != s->mnc)) { + LOGP(DCS, LOGL_INFO, "Skip ARFCN %s: PLMN of cell " + "does not match target PLMN. (mcc=%s mnc=%s)\n", + gsm_print_arfcn(index2arfcn(i)), gsm_print_mcc(s->mcc), + gsm_print_mnc(s->mnc)); + return -1; + } + + LOGP(DCS, LOGL_INFO, "Cell ARFCN %s: Neighbour cell accepted, " + "(rxlev=%s mcc=%s mnc=%s lac=%04x %s, %s)\n", + gsm_print_arfcn(index2arfcn(i)), + gsm_print_rxlev(cs->list[i].rxlev), + gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc), s->lac, + gsm_get_mcc(s->mcc), gsm_get_mnc(s->mcc, s->mnc)); + + return i; +} + +/* this processes the end of frequency scanning or cell searches */ +static int gsm322_search_end(struct osmocom_ms *ms) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm322_plmn *plmn = &ms->plmn; + struct msgb *nmsg; + struct gsm322_msg *ngm; + int msg_type = -1; /* no message to be sent */ + int tune_back = 0, mcc = 0, mnc = 0; + int found; + + switch (cs->state) { + case GSM322_ANY_SEARCH: + /* special case for 'any cell' search */ + LOGP(DCS, LOGL_INFO, "Any cell search finished.\n"); + + /* create AA flag */ + found = gsm322_cs_select(ms, -1, 0, 0, 0); + + /* if no cell is found, or if we don't wait for any available + * and allowable PLMN to appear, we just continue to camp */ + if (ms->settings.plmn_mode != PLMN_MODE_AUTO + || plmn->state != GSM322_A4_WAIT_FOR_PLMN + || found < 0) { + tune_back = 1; + gsm322_c_camp_any_cell(ms, NULL); + break; + } + + /* indicate available PLMN, include selected PLMN, if found */ + msg_type = GSM322_EVENT_PLMN_AVAIL; + if (gsm322_is_plmn_avail_and_allow(cs, plmn->mcc, plmn->mnc)) { + /* set what PLMN becomes available */ + mcc = plmn->mcc; + mnc = plmn->mnc; + } + + new_c_state(cs, GSM322_C0_NULL); + + break; + + case GSM322_PLMN_SEARCH: + /* special case for PLMN search */ + msg_type = GSM322_EVENT_PLMN_SEARCH_END; + LOGP(DCS, LOGL_INFO, "PLMN search finished.\n"); + + /* create AA flag */ + gsm322_cs_select(ms, -1, 0, 0, 0); + + new_c_state(cs, GSM322_C0_NULL); + + break; + + case GSM322_HPLMN_SEARCH: + /* special case for HPLMN search */ + msg_type = GSM322_EVENT_NO_CELL_FOUND; + LOGP(DCS, LOGL_INFO, "HPLMN search finished, no cell.\n"); + + new_c_state(cs, GSM322_C3_CAMPED_NORMALLY); + + tune_back = 1; + + break; + + default: + /* we start normal cell selection if this fails */ + if (cs->state == GSM322_C2_STORED_CELL_SEL + || cs->state == GSM322_C5_CHOOSE_CELL) { + /* tell CS to start over */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_NO_CELL_FOUND); + if (!nmsg) + return -ENOMEM; + gsm322_c_event(ms, nmsg); + msgb_free(nmsg); + + break; + } + + /* on other cell selection, indicate "no cell found" */ + /* NOTE: PLMN search process handles it. + * If not handled there, CS process gets indicated. + * If we would continue to process CS, then we might get + * our list of scanned cells disturbed. + */ + LOGP(DCS, LOGL_INFO, "Cell search finished without result.\n"); + msg_type = GSM322_EVENT_NO_CELL_FOUND; + + /* stay in null-state until any cell selectio is triggered or + * new plmn is indicated. + */ + new_c_state(cs, GSM322_C0_NULL); + } + + if (msg_type > -1) { + /* send result to PLMN process, to trigger next CS event */ + nmsg = gsm322_msgb_alloc(msg_type); + if (!nmsg) + return -ENOMEM; + ngm = (struct gsm322_msg *) nmsg->data; + ngm->mcc = mcc; + ngm->mnc = mcc; + 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(l23_ctx, + struct gsm48_sysinfo); + if (!cs->list[cs->arfci].sysinfo) + exit(-ENOMEM); + cs->list[cs->arfci].flags |= GSM322_CS_FLAG_SYSINFO; + memcpy(cs->list[cs->arfci].sysinfo, &cs->sel_si, + sizeof(struct gsm48_sysinfo)); + cs->si = cs->list[cs->arfci].sysinfo; + cs->sel_mcc = cs->si->mcc; + cs->sel_mnc = cs->si->mnc; + cs->sel_lac = cs->si->lac; + cs->sel_id = cs->si->cell_id; + LOGP(DCS, LOGL_INFO, "Tuning back to frequency %s after full " + "search.\n", gsm_print_arfcn(cs->arfcn)); + cs->sync_retries = SYNC_RETRIES; + gsm322_sync_to_cell(cs, NULL, 0); + } + + return 0; +} + + +/* tune to first/next unscanned frequency and search for PLMN */ +static int gsm322_cs_scan(struct osmocom_ms *ms) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + int i; + int j, band = 0; + uint8_t mask, flags; + uint32_t weight = 0, test = cs->scan_state; + + /* search for strongest unscanned cell */ + mask = GSM322_CS_FLAG_SUPPORT | GSM322_CS_FLAG_POWER + | GSM322_CS_FLAG_SIGNAL; + if (cs->state == GSM322_C2_STORED_CELL_SEL + || cs->state == GSM322_C5_CHOOSE_CELL) + mask |= GSM322_CS_FLAG_BA; + flags = mask; /* all masked flags are requied */ + 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 informations, 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(l23_ctx, + struct gsm48_sysinfo); + if (!cs->list[cs->arfci].sysinfo) + exit(-ENOMEM); + cs->si = cs->list[cs->arfci].sysinfo; + cs->sync_retries = 0; + gsm322_sync_to_cell(cs, NULL, 0); + + /* increase scan counter for each maximum scan range */ + if (!ms->settings.skip_max_per_band && gsm_sup_smax[band].max) { + LOGP(DCS, LOGL_DEBUG, "%d frequencies left in band %d..%d\n", + gsm_sup_smax[band].max - gsm_sup_smax[band].temp, + gsm_sup_smax[band].start, gsm_sup_smax[band].end); + gsm_sup_smax[band].temp++; + } + + return 0; +} + +/* check if cell is now suitable and allowable */ +static int gsm322_cs_store(struct osmocom_ms *ms) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = cs->si; + struct gsm322_plmn *plmn = &ms->plmn; + struct msgb *nmsg; + struct gsm322_msg *ngm; + int found, any = 0; + + if (cs->state != GSM322_C2_STORED_CELL_SEL + && cs->state != GSM322_C1_NORMAL_CELL_SEL + && cs->state != GSM322_C6_ANY_CELL_SEL + && cs->state != GSM322_C4_NORMAL_CELL_RESEL + && cs->state != GSM322_C8_ANY_CELL_RESEL + && cs->state != GSM322_C5_CHOOSE_CELL + && cs->state != GSM322_C9_CHOOSE_ANY_CELL + && cs->state != GSM322_ANY_SEARCH + && cs->state != GSM322_PLMN_SEARCH + && cs->state != GSM322_HPLMN_SEARCH) { + LOGP(DCS, LOGL_FATAL, "This must only happen during cell " + "(re-)selection, please fix!\n"); + return -EINVAL; + } + + /* store sysinfo */ + cs->list[cs->arfci].flags |= GSM322_CS_FLAG_SYSINFO; + if (s->cell_barr && !(s->sp && s->sp_cbq)) + cs->list[cs->arfci].flags |= GSM322_CS_FLAG_BARRED; + else + cs->list[cs->arfci].flags &= ~GSM322_CS_FLAG_BARRED; + + /* store selected network */ + if (s->mcc) { + if (gsm322_is_forbidden_la(ms, s->mcc, s->mnc, s->lac)) + cs->list[cs->arfci].flags |= GSM322_CS_FLAG_FORBIDD; + else + cs->list[cs->arfci].flags &= ~GSM322_CS_FLAG_FORBIDD; + } + + LOGP(DCS, LOGL_DEBUG, "Scan frequency %s: Cell found. (rxlev %s " + "mcc %s mnc %s lac %04x)\n", gsm_print_arfcn(cs->arfcn), + gsm_print_rxlev(cs->list[cs->arfci].rxlev), + gsm_print_mcc(s->mcc), gsm_print_mnc(s->mnc), s->lac); + + /* selected PLMN (auto) becomes available during "any search" */ + if (ms->settings.plmn_mode == PLMN_MODE_AUTO + && (cs->state == GSM322_ANY_SEARCH + || cs->state == GSM322_C6_ANY_CELL_SEL + || cs->state == GSM322_C8_ANY_CELL_RESEL + || cs->state == GSM322_C9_CHOOSE_ANY_CELL) + && plmn->state == GSM322_A4_WAIT_FOR_PLMN + && s->mcc == plmn->mcc && s->mnc == plmn->mnc) { + LOGP(DCS, LOGL_INFO, "Candidate network to become available " + "again\n"); + found = gsm322_cs_select(ms, cs->arfci, s->mcc, s->mnc, 0); + if (found >= 0) { + LOGP(DCS, LOGL_INFO, "Selected PLMN in \"A4_WAIT_F" + "OR_PLMN\" state becomes available.\n"); +indicate_plmn_avail: + /* PLMN becomes available */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_PLMN_AVAIL); + if (!nmsg) + return -ENOMEM; + /* set what PLMN becomes available */ + ngm = (struct gsm322_msg *) nmsg->data; + ngm->mcc = plmn->mcc; + ngm->mnc = plmn->mcc; + gsm322_plmn_sendmsg(ms, nmsg); + + new_c_state(cs, GSM322_C0_NULL); + + return 0; + } + } + + /* selected PLMN (manual) becomes available during "any search" */ + if (ms->settings.plmn_mode == PLMN_MODE_MANUAL + && (cs->state == GSM322_ANY_SEARCH + || cs->state == GSM322_C6_ANY_CELL_SEL + || cs->state == GSM322_C8_ANY_CELL_RESEL + || cs->state == GSM322_C9_CHOOSE_ANY_CELL) + && plmn->state == GSM322_M3_NOT_ON_PLMN + && s->mcc == plmn->mcc && s->mnc == plmn->mnc) { + LOGP(DCS, LOGL_INFO, "Candidate network to become available " + "again\n"); + found = gsm322_cs_select(ms, cs->arfci, s->mcc, s->mnc, 0); + if (found >= 0) { + LOGP(DCS, LOGL_INFO, "Current selected PLMN in \"M3_N" + "OT_ON_PLMN\" state becomes available.\n"); + goto indicate_plmn_avail; + } + } + + /* special case for PLMN search */ + if (cs->state == GSM322_PLMN_SEARCH + || cs->state == GSM322_ANY_SEARCH) + /* tune to next cell */ + return gsm322_cs_scan(ms); + + /* special case for HPLMN search */ + if (cs->state == GSM322_HPLMN_SEARCH) { + struct gsm_subscriber *subscr = &ms->subscr; + struct msgb *nmsg; + + if (!gsm322_is_hplmn_avail(cs, subscr->imsi)) + /* tune to next cell */ + return gsm322_cs_scan(ms); + + nmsg = gsm322_msgb_alloc(GSM322_EVENT_CELL_FOUND); + LOGP(DCS, LOGL_INFO, "HPLMN search finished, cell found.\n"); + if (!nmsg) + return -ENOMEM; + gsm322_plmn_sendmsg(ms, nmsg); + + return 0; + } + + /* just see, if we search for any cell */ + if (cs->state == GSM322_C6_ANY_CELL_SEL + || cs->state == GSM322_C8_ANY_CELL_RESEL + || cs->state == GSM322_C9_CHOOSE_ANY_CELL) + any = 1; + + if (cs->state == GSM322_C4_NORMAL_CELL_RESEL + || cs->state == GSM322_C8_ANY_CELL_RESEL) + found = gsm322_cs_reselect(ms, cs->mcc, cs->mnc, any); + else + found = gsm322_cs_select(ms, -1, cs->mcc, cs->mnc, any); + + /* if not found */ + if (found < 0) { + LOGP(DCS, LOGL_INFO, "Cell not suitable and allowable.\n"); + /* tune to next cell */ + if (cs->state == GSM322_C4_NORMAL_CELL_RESEL + || cs->state == GSM322_C8_ANY_CELL_RESEL) + return gsm322_nb_scan(ms); + else + return gsm322_cs_scan(ms); + } + + LOGP(DCS, LOGL_INFO, "Tune to frequency %d.\n", found); + /* tune */ + cs->arfci = found; + cs->arfcn = index2arfcn(cs->arfci); + cs->si = cs->list[cs->arfci].sysinfo; + cs->sync_retries = SYNC_RETRIES; + gsm322_sync_to_cell(cs, NULL, 0); + + /* set selected cell */ + cs->selected = 1; + cs->sel_arfcn = cs->arfcn; + memcpy(&cs->sel_si, cs->si, sizeof(cs->sel_si)); + cs->sel_mcc = cs->si->mcc; + cs->sel_mnc = cs->si->mnc; + cs->sel_lac = cs->si->lac; + cs->sel_id = cs->si->cell_id; + if (ms->rrlayer.monitor) { + vty_notify(ms, "MON: %scell selected ARFCN=%s MCC=%s MNC=%s " + "LAC=0x%04x cellid=0x%04x (%s %s)\n", + (any) ? "any " : "", gsm_print_arfcn(cs->sel_arfcn), + gsm_print_mcc(cs->sel_mcc), gsm_print_mnc(cs->sel_mnc), + cs->sel_lac, cs->sel_id, + gsm_get_mcc(cs->sel_mcc), + gsm_get_mnc(cs->sel_mcc, cs->sel_mnc)); + } + + /* tell CS process about available cell */ + LOGP(DCS, LOGL_INFO, "Cell available.\n"); + nmsg = gsm322_msgb_alloc(GSM322_EVENT_CELL_FOUND); + if (!nmsg) + return -ENOMEM; + gsm322_c_event(ms, nmsg); + msgb_free(nmsg); + + return 0; +} + +/* process system information when returing to idle mode */ +struct gsm322_ba_list *gsm322_cs_sysinfo_sacch(struct osmocom_ms *ms) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s; + struct gsm322_ba_list *ba = NULL; + int i, refer_pcs; + uint8_t freq[128+38]; + + if (!cs) { + LOGP(DCS, LOGL_INFO, "No BA, because no cell selected\n"); + return ba; + } + s = cs->si; + if (!s) { + LOGP(DCS, LOGL_INFO, "No BA, because no sysinfo\n"); + return ba; + } + + /* collect system information received during dedicated mode */ + if (s->si5 && (!s->nb_ext_ind_si5 || s->si5bis)) { + /* find or create ba list */ + ba = gsm322_find_ba_list(cs, s->mcc, s->mnc); + if (!ba) { + ba = talloc_zero(l23_ctx, struct gsm322_ba_list); + if (!ba) + return NULL; + ba->mcc = s->mcc; + ba->mnc = s->mnc; + llist_add_tail(&ba->entry, &cs->ba_list); + } + /* update (add) ba list */ + refer_pcs = gsm_refer_pcs(cs->arfcn, s); + memset(freq, 0, sizeof(freq)); + for (i = 0; i <= 1023; i++) { + if ((s->freq[i].mask & (FREQ_TYPE_SERV + | FREQ_TYPE_NCELL | FREQ_TYPE_REP))) { + if (refer_pcs && i >= 512 && i <= 810) + freq[(i-512+1024) >> 3] |= (1 << (i&7)); + else + freq[i >> 3] |= (1 << (i & 7)); + } + } + if (!!memcmp(freq, ba->freq, sizeof(freq))) { + LOGP(DCS, LOGL_INFO, "New BA list (mcc=%s mnc=%s " + "%s, %s).\n", gsm_print_mcc(ba->mcc), + gsm_print_mnc(ba->mnc), gsm_get_mcc(ba->mcc), + gsm_get_mnc(ba->mcc, ba->mnc)); + memcpy(ba->freq, freq, sizeof(freq)); + } + } + + return ba; +} + +/* store BA whenever a system informations changes */ +static int gsm322_store_ba_list(struct gsm322_cellsel *cs, + struct gsm48_sysinfo *s) +{ + struct gsm322_ba_list *ba; + int i, refer_pcs; + uint8_t freq[128+38]; + + /* find or create ba list */ + ba = gsm322_find_ba_list(cs, s->mcc, s->mnc); + if (!ba) { + ba = talloc_zero(l23_ctx, struct gsm322_ba_list); + if (!ba) + return -ENOMEM; + ba->mcc = s->mcc; + ba->mnc = s->mnc; + llist_add_tail(&ba->entry, &cs->ba_list); + } + /* update ba list */ + refer_pcs = gsm_refer_pcs(cs->arfcn, s); + memset(freq, 0, sizeof(freq)); + freq[(cs->arfci) >> 3] |= (1 << (cs->arfci & 7)); + for (i = 0; i <= 1023; i++) { + if ((s->freq[i].mask & + (FREQ_TYPE_SERV | FREQ_TYPE_NCELL | FREQ_TYPE_REP))) { + if (refer_pcs && i >= 512 && i <= 810) + freq[(i-512+1024) >> 3] |= (1 << (i & 7)); + else + freq[i >> 3] |= (1 << (i & 7)); + } + } + if (!!memcmp(freq, ba->freq, sizeof(freq))) { + LOGP(DCS, LOGL_INFO, "New BA list (mcc=%s mnc=%s " + "%s, %s).\n", gsm_print_mcc(ba->mcc), + gsm_print_mnc(ba->mnc), gsm_get_mcc(ba->mcc), + gsm_get_mnc(ba->mcc, ba->mnc)); + memcpy(ba->freq, freq, sizeof(freq)); + } + + return 0; +} + +/* process system information during camping on a cell */ +static int gsm322_c_camp_sysinfo_bcch(struct osmocom_ms *ms, struct msgb *msg) +{ +// struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = cs->si; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm322_msg *gm = (struct gsm322_msg *) msg->data; + struct msgb *nmsg; + + /* start in case we are camping on neighbour cell */ + if ((cs->state == GSM322_C3_CAMPED_NORMALLY + || cs->state == GSM322_C7_CAMPED_ANY_CELL) + && (cs->neighbour)) { + if (s->si3 || s->si4) { + stop_cs_timer(cs); + LOGP(DCS, LOGL_INFO, "Relevant sysinfo of neighbour " + "cell is now received or updated.\n"); + return gsm322_nb_read(cs, 1); + } + return 0; + } + + /* Store BA if we have full system info about cells and neigbor cells. + * Depending on the extended bit in the channel description, + * we require more or less system informations about neighbor cells + */ + if (s->mcc + && s->mnc + && (gm->sysinfo == GSM48_MT_RR_SYSINFO_1 + || gm->sysinfo == GSM48_MT_RR_SYSINFO_2 + || gm->sysinfo == GSM48_MT_RR_SYSINFO_2bis + || gm->sysinfo == GSM48_MT_RR_SYSINFO_2ter) + && s->si1 + && s->si2 + && (!s->nb_ext_ind_si2 + || (s->si2bis && s->nb_ext_ind_si2 && !s->nb_ext_ind_si2bis) + || (s->si2bis && s->si2ter && s->nb_ext_ind_si2 + && s->nb_ext_ind_si2bis))) + gsm322_store_ba_list(cs, s); + + /* update sel_si, if all relevant system informations received */ + if (s->si1 && s->si2 && s->si3 + && (!s->nb_ext_ind_si2 + || (s->si2bis && s->nb_ext_ind_si2 && !s->nb_ext_ind_si2bis) + || (s->si2bis && s->si2ter && s->nb_ext_ind_si2 + && s->nb_ext_ind_si2bis))) { + if (cs->selected) { + LOGP(DCS, LOGL_INFO, "Sysinfo of selected cell is " + "now received or updated.\n"); + memcpy(&cs->sel_si, s, sizeof(cs->sel_si)); + + /* start in case we are camping on serving cell */ + if (cs->state == GSM322_C3_CAMPED_NORMALLY + || cs->state == GSM322_C7_CAMPED_ANY_CELL) + gsm322_nb_start(ms, 0); + } + } + + /* check for barred cell */ + if (gm->sysinfo == GSM48_MT_RR_SYSINFO_1) { + /* check if cell becomes barred */ + if (!subscr->acc_barr && s->cell_barr + && !(cs->list[cs->arfci].sysinfo + && cs->list[cs->arfci].sysinfo->sp + && cs->list[cs->arfci].sysinfo->sp_cbq)) { + LOGP(DCS, LOGL_INFO, "Cell becomes barred.\n"); + if (ms->rrlayer.monitor) + vty_notify(ms, "MON: trigger cell re-selection" + ": cell becomes barred\n"); + trigger_resel: + /* mark cell as unscanned */ + cs->list[cs->arfci].flags &= ~GSM322_CS_FLAG_SYSINFO; + if (cs->list[cs->arfci].sysinfo) { + LOGP(DCS, LOGL_DEBUG, "free sysinfo arfcn=%s\n", + gsm_print_arfcn(cs->arfcn)); + talloc_free(cs->list[cs->arfci].sysinfo); + cs->list[cs->arfci].sysinfo = NULL; + } + /* trigger reselection without queueing, + * because other sysinfo message may be queued + * before + */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_CELL_RESEL); + if (!nmsg) + return -ENOMEM; + gsm322_c_event(ms, nmsg); + msgb_free(nmsg); + + return 0; + } + /* check if cell access becomes barred */ + if (!((subscr->acc_class & 0xfbff) + & (s->class_barr ^ 0xffff))) { + LOGP(DCS, LOGL_INFO, "Cell access becomes barred.\n"); + if (ms->rrlayer.monitor) + vty_notify(ms, "MON: trigger cell re-selection" + ": access to cell becomes barred\n"); + goto trigger_resel; + } + } + + /* check if MCC, MNC, LAC, cell ID changes */ + if (cs->sel_mcc != s->mcc || cs->sel_mnc != s->mnc + || cs->sel_lac != s->lac) { + LOGP(DCS, LOGL_NOTICE, "Cell changes location area. " + "This is not good!\n"); + if (ms->rrlayer.monitor) + vty_notify(ms, "MON: trigger cell re-selection: " + "cell changes LAI\n"); + goto trigger_resel; + } + if (cs->sel_id != s->cell_id) { + LOGP(DCS, LOGL_NOTICE, "Cell changes cell ID. " + "This is not good!\n"); + if (ms->rrlayer.monitor) + vty_notify(ms, "MON: trigger cell re-selection: " + "cell changes cell ID\n"); + goto trigger_resel; + } + + return 0; +} + +/* process system information during channel scanning */ +static int gsm322_c_scan_sysinfo_bcch(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = cs->si; + struct gsm322_msg *gm = (struct gsm322_msg *) msg->data; + + /* no sysinfo if we are not done with power scan */ + if (cs->powerscan) { + LOGP(DCS, LOGL_INFO, "Ignoring sysinfo during power scan.\n"); + return -EINVAL; + } + + /* Store BA if we have full system info about cells and neigbor cells. + * Depending on the extended bit in the channel description, + * we require more or less system informations about neighbor cells + */ + if (s->mcc + && s->mnc + && (gm->sysinfo == GSM48_MT_RR_SYSINFO_1 + || gm->sysinfo == GSM48_MT_RR_SYSINFO_2 + || gm->sysinfo == GSM48_MT_RR_SYSINFO_2bis + || gm->sysinfo == GSM48_MT_RR_SYSINFO_2ter) + && s->si1 + && s->si2 + && (!s->nb_ext_ind_si2 || s->si2bis) + && (!s->si2ter_ind || s->si2ter)) + gsm322_store_ba_list(cs, s); + + /* all relevant system informations 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)); + 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; + 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; + } + } + + LOGP(DCS, LOGL_DEBUG, "Scanning frequencies. (%s..%s)\n", + gsm_print_arfcn(index2arfcn(s)), + gsm_print_arfcn(index2arfcn(e))); + + /* 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_db + || 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))); + 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))); + talloc_free(cs->list[cs->arfci].sysinfo); + cs->list[cs->arfci].sysinfo = NULL; + + } + if (cs->selected && cs->sel_arfcn == cs->arfcn) { + LOGP(DCS, LOGL_INFO, "Unselect cell due to sync " + "error!\n"); + /* unset selected cell */ + gsm322_unselect_cell(cs); + } + stop_cs_timer(cs); + + /* start in case we are camping on neighbour * cell */ + if (cs->state == GSM322_C3_CAMPED_NORMALLY + || cs->state == GSM322_C7_CAMPED_ANY_CELL) { + if (cs->neighbour) { + gsm322_nb_synced(cs, 0); + break; + } + } + + gsm322_cs_loss(cs); + break; + case S_L1CTL_LOSS_IND: + ms = signal_data; + cs = &ms->cellsel; + LOGP(DCS, LOGL_INFO, "Loss of CCCH.\n"); + if (cs->selected && cs->sel_arfcn == cs->arfcn) { + /* do not unselect cell */ + LOGP(DCS, LOGL_INFO, "Keep cell selected after loss, " + "so we can use the Neighbour cell information " + "for cell re-selection.\n"); + } + stop_cs_timer(cs); + gsm322_cs_loss(cs); + break; + case S_L1CTL_RESET: + ms = signal_data; + if (ms->mmlayer.power_off_idle) { + mobile_exit(ms, 1); + return 0; + } + break; + case S_L1CTL_NEIGH_PM_IND: + ni = signal_data; + ms = ni->ms; +#ifdef COMMING_LATE_R + /* in dedicated mode */ + if (ms->rrlayer.dm_est) + gsm48_rr_meas_ind(ms, ni->band_arfcn, ni->rx_lev); + else +#endif + /* in camping mode */ + if ((ms->cellsel.state == GSM322_C3_CAMPED_NORMALLY + || ms->cellsel.state == GSM322_C7_CAMPED_ANY_CELL) + && !ms->cellsel.neighbour) + gsm322_nb_meas_ind(ms, ni->band_arfcn, ni->rx_lev); + break; + } + + return 0; +} + +static void gsm322_cs_loss(void *arg) +{ + struct gsm322_cellsel *cs = arg; + struct osmocom_ms *ms = cs->ms; + + if ((cs->state == GSM322_C3_CAMPED_NORMALLY + || cs->state == GSM322_C7_CAMPED_ANY_CELL) + && !cs->neighbour) { + struct msgb *nmsg; + + LOGP(DCS, LOGL_INFO, "Loss of CCCH, Trigger " + "re-selection.\n"); + if (ms->rrlayer.monitor) + vty_notify(ms, "MON: trigger cell " + "re-selection: loss of signal\n"); + + nmsg = gsm322_msgb_alloc(GSM322_EVENT_CELL_RESEL); + if (!nmsg) + return; + gsm322_c_event(ms, nmsg); + msgb_free(nmsg); + + return; + } else + if (cs->state == GSM322_CONNECTED_MODE_1 + || cs->state == GSM322_CONNECTED_MODE_2) { + LOGP(DCS, LOGL_INFO, "Loss of SACCH, Trigger RR " + "abort.\n"); + + /* keep cell info for re-selection */ + + gsm48_rr_los(ms); + /* be shure 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 noraml 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 Avaiable" state. */ + if (msg_type == GSM322_EVENT_NO_CELL_FOUND) { + struct msgb *nmsg; + + /* tell that we have no cell found + * (not any cell at all) */ + nmsg = gsm48_mmevent_msgb_alloc( + GSM48_MM_EVENT_NO_CELL_FOUND); + if (!nmsg) + return -ENOMEM; + gsm48_mmevent_msg(ms, nmsg); + } + } + + new_c_state(cs, GSM322_C6_ANY_CELL_SEL); + + cs->mcc = cs->mnc = 0; + + /* unset selected cell */ + gsm322_unselect_cell(cs); + + /* start power scan */ + return gsm322_cs_powerscan(ms); +} + +static void gsm322_any_timeout(void *arg) +{ + struct gsm322_cellsel *cs = arg; + struct osmocom_ms *ms = cs->ms; + + /* the timer may still run when not camping, so we ignore it. + * it will be restarted whenever the 'camped on any cell' state + * is reached. */ + if (cs->state != GSM322_C7_CAMPED_ANY_CELL) + return; + + /* in case the time has been started before SIM was removed */ + if (!ms->subscr.sim_valid) + return; + + LOGP(DCS, LOGL_INFO, "'Any cell selection timer' timed out. " + "Starting special search to find allowed PLMNs.\n"); + + gsm322_c_any_search(ms, NULL); +} + +/* sim is removed, proceed with any cell selection */ +static int gsm322_c_sim_remove(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct llist_head *lh, *lh2; + + /* flush list of forbidden LAs */ + llist_for_each_safe(lh, lh2, &plmn->forbidden_la) { + llist_del(lh); + talloc_free(lh); + } + return gsm322_c_any_cell_sel(ms, msg); +} + +/* start noraml cell re-selection */ +static int gsm322_c_normal_cell_resel(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct msgb *nmsg; + + /* store last camped cell. this is required for next cell + * monitoring reselection criterion */ + cs->last_serving_arfcn = cs->sel_arfcn; + cs->last_serving_valid = 1; + + /* unset selected cell */ + gsm322_unselect_cell(cs); + + /* tell MM that we lost coverage */ + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_LOST_COVERAGE); + if (!nmsg) + return -ENOMEM; + gsm48_mmevent_msg(ms, nmsg); + + new_c_state(cs, GSM322_C4_NORMAL_CELL_RESEL); + + /* start scanning neighbour cells for reselection */ + return gsm322_nb_scan(ms); +} + +/* start any cell re-selection */ +static int gsm322_c_any_cell_resel(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct msgb *nmsg; + + /* store last camped cell. this is required for next cell + * monitoring reselection criterion */ + cs->last_serving_arfcn = cs->sel_arfcn; + cs->last_serving_valid = 1; + + /* unset selected cell */ + gsm322_unselect_cell(cs); + + /* tell MM that we lost coverage */ + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_LOST_COVERAGE); + if (!nmsg) + return -ENOMEM; + gsm48_mmevent_msg(ms, nmsg); + + new_c_state(cs, GSM322_C8_ANY_CELL_RESEL); + + /* start scanning neighbour cells for reselection */ + return gsm322_nb_scan(ms); +} + +/* a suitable cell was found, so we camp normally */ +static int gsm322_c_camp_normally(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct msgb *nmsg; + + LOGP(DSUM, LOGL_INFO, "Camping normally on cell (ARFCN=%s mcc=%s " + "mnc=%s %s, %s)\n", gsm_print_arfcn(cs->sel_arfcn), + gsm_print_mcc(cs->sel_mcc), + gsm_print_mnc(cs->sel_mnc), gsm_get_mcc(cs->sel_mcc), + gsm_get_mnc(cs->sel_mcc, cs->sel_mnc)); + + /* if we did cell reselection, we have a valid last serving cell */ + if (cs->state != GSM322_C4_NORMAL_CELL_RESEL) + cs->last_serving_valid = 0; + + /* tell that we have selected a (new) cell */ + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_CELL_SELECTED); + if (!nmsg) + return -ENOMEM; + gsm48_mmevent_msg(ms, nmsg); + + new_c_state(cs, GSM322_C3_CAMPED_NORMALLY); + + return 0; +} + +/* any cell was found, so we camp on any cell */ +static int gsm322_c_camp_any_cell(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct msgb *nmsg; + + LOGP(DSUM, LOGL_INFO, "Camping on any cell (ARFCN=%s mcc=%s " + "mnc=%s %s, %s)\n", gsm_print_arfcn(cs->sel_arfcn), + gsm_print_mcc(cs->sel_mcc), + gsm_print_mnc(cs->sel_mnc), gsm_get_mcc(cs->sel_mcc), + gsm_get_mnc(cs->sel_mcc, cs->sel_mnc)); + + /* (re-)starting 'any cell selection' timer to look for coverage of + * allowed PLMNs. + * start timer, if not running. + * restart timer, if we just entered the 'camped any cell' state */ + if (ms->subscr.sim_valid + && (cs->state != GSM322_C8_ANY_CELL_RESEL + || !osmo_timer_pending(&cs->any_timer))) { + struct gsm322_plmn *plmn = &ms->plmn; + + stop_any_timer(cs); + if (ms->settings.plmn_mode == PLMN_MODE_MANUAL + && (!plmn->mcc + || gsm_subscr_is_forbidden_plmn(&ms->subscr, plmn->mcc, + plmn->mnc))) { + LOGP(DCS, LOGL_INFO, "Not starting 'any search' timer, " + "because no selected PLMN or forbidden\n"); + } else + start_any_timer(cs, ms->subscr.any_timeout, 0); + } + + /* if we did cell reselection, we have a valid last serving cell */ + if (cs->state != GSM322_C8_ANY_CELL_RESEL) + cs->last_serving_valid = 0; + + /* tell that we have selected a (new) cell. + * this cell iss not allowable, so the MM state will enter limited + * service */ + if (cs->state != GSM322_C7_CAMPED_ANY_CELL) { + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_CELL_SELECTED); + if (!nmsg) + return -ENOMEM; + gsm48_mmevent_msg(ms, nmsg); + } + + new_c_state(cs, GSM322_C7_CAMPED_ANY_CELL); + + return 0; +} + +/* create temporary ba range with given frequency ranges */ +struct gsm322_ba_list *gsm322_cs_ba_range(struct osmocom_ms *ms, + uint32_t *range, uint8_t ranges, uint8_t refer_pcs) +{ + static struct gsm322_ba_list ba; + int lower, higher; + + 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++; + LOGP(DCS, LOGL_INFO, "Use BA range: %s..%s\n", + gsm_print_arfcn(index2arfcn(lower)), + gsm_print_arfcn(index2arfcn(higher))); + /* GSM 05.08 6.3 */ + while (1) { + ba.freq[lower >> 3] |= 1 << (lower & 7); + if (lower == higher) + break; + lower++; + /* wrap arround, only if not PCS */ + if (lower == 1024) + lower = 0; + } + } + + return &ba; +} + +/* common part of gsm322_c_choose_cell and gsm322_c_choose_any_cell */ +static int gsm322_cs_choose(struct osmocom_ms *ms) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_ba_list *ba = NULL; + int i; + + /* NOTE: The call to this function is synchron to RR layer, so + * we may access the BA range there. + */ + if (rr->ba_ranges) + ba = gsm322_cs_ba_range(ms, rr->ba_range, rr->ba_ranges, + gsm_refer_pcs(cs->sel_arfcn, &cs->sel_si)); + else { + LOGP(DCS, LOGL_INFO, "No BA range(s), try sysinfo.\n"); + /* get and update BA of last received sysinfo 5* */ + ba = gsm322_cs_sysinfo_sacch(ms); + if (!ba) { + LOGP(DCS, LOGL_INFO, "No BA on sysinfo, try stored " + "BA list.\n"); + ba = gsm322_find_ba_list(cs, cs->sel_si.mcc, + cs->sel_si.mnc); + } + } + + if (!ba) { + struct msgb *nmsg; + + LOGP(DCS, LOGL_INFO, "No BA list to use.\n"); + + /* tell CS to start over */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_NO_CELL_FOUND); + if (!nmsg) + return -ENOMEM; + gsm322_c_event(ms, nmsg); + msgb_free(nmsg); + + return 0; + } + + /* flag all frequencies that are in current band allocation */ + for (i = 0; i <= 1023+299; i++) { + if (cs->state == GSM322_C5_CHOOSE_CELL) { + if ((ba->freq[i >> 3] & (1 << (i & 7)))) { + cs->list[i].flags |= GSM322_CS_FLAG_BA; + } else { + cs->list[i].flags &= ~GSM322_CS_FLAG_BA; + } + } + cs->list[i].flags &= ~(GSM322_CS_FLAG_POWER + | GSM322_CS_FLAG_SIGNAL + | GSM322_CS_FLAG_SYSINFO); + } + + /* unset selected cell */ + gsm322_unselect_cell(cs); + + /* start power scan */ + return gsm322_cs_powerscan(ms); +} + +/* start 'Choose cell' after returning to idle mode */ +static int gsm322_c_choose_cell(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm322_msg *gm = (struct gsm322_msg *) msg->data; + + /* After location updating, we choose the last cell */ + if (gm->same_cell) { + struct msgb *nmsg; + + if (!cs->selected) { + LOGP(DCS, LOGL_INFO, "Cell not selected anymore, " + "choose cell!\n"); + goto choose; + } + cs->arfcn = cs->sel_arfcn; + cs->arfci = arfcn2index(cs->arfcn); + + /* be sure to go to current camping frequency on return */ + LOGP(DCS, LOGL_INFO, "Selecting ARFCN %s. after LOC.UPD.\n", + gsm_print_arfcn(cs->arfcn)); + cs->sync_retries = SYNC_RETRIES; + gsm322_sync_to_cell(cs, NULL, 0); + cs->si = cs->list[cs->arfci].sysinfo; + if (!cs->si) { + printf("No SI when ret.idle, please fix!\n"); + exit(0L); + } + + new_c_state(cs, GSM322_C3_CAMPED_NORMALLY); + + /* tell that we have selected the cell, so RR returns IDLE */ + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_CELL_SELECTED); + if (!nmsg) + return -ENOMEM; + gsm48_mmevent_msg(ms, nmsg); + + return 0; + } + +choose: + new_c_state(cs, GSM322_C5_CHOOSE_CELL); + + return gsm322_cs_choose(ms); +} + +/* start 'Choose any cell' after returning to idle mode */ +static int gsm322_c_choose_any_cell(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + + new_c_state(cs, GSM322_C9_CHOOSE_ANY_CELL); + + return gsm322_cs_choose(ms); +} + +/* a new PLMN is selected by PLMN search process */ +static int gsm322_c_new_plmn(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_msg *gm = (struct gsm322_msg *) msg->data; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_ba_list *ba; + + cs->mcc = plmn->mcc; + cs->mnc = plmn->mnc; + + if (gm->limited) { + LOGP(DCS, LOGL_INFO, "Selected PLMN with limited service.\n"); + return gsm322_c_any_cell_sel(ms, msg); + } + + LOGP(DSUM, LOGL_INFO, "Selecting PLMN (mcc=%s mnc=%s %s, %s)\n", + gsm_print_mcc(cs->mcc), gsm_print_mnc(cs->mnc), + gsm_get_mcc(cs->mcc), gsm_get_mnc(cs->mcc, cs->mnc)); + + /* search for BA list */ + ba = gsm322_find_ba_list(cs, plmn->mcc, plmn->mnc); + + if (ba) { + LOGP(DCS, LOGL_INFO, "Start stored cell selection.\n"); + return gsm322_c_stored_cell_sel(ms, ba); + } else { + LOGP(DCS, LOGL_INFO, "Start normal cell selection.\n"); + return gsm322_c_normal_cell_sel(ms, msg); + } +} + +/* go connected mode */ +static int gsm322_c_conn_mode_1(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + + /* check for error */ + if (!cs->selected) { + LOGP(DCS, LOGL_INFO, "No cell selected, please fix!\n"); + exit(0L); + } + cs->arfcn = cs->sel_arfcn; + cs->arfci = arfcn2index(cs->arfcn); + + /* maybe we are currently syncing to neighbours */ + stop_cs_timer(cs); + + new_c_state(cs, GSM322_CONNECTED_MODE_1); + + /* be sure to go to current camping frequency on return */ + LOGP(DCS, LOGL_INFO, "Going to camping (normal) ARFCN %s.\n", + gsm_print_arfcn(cs->arfcn)); + cs->si = cs->list[cs->arfci].sysinfo; + if (!cs->si) { + printf("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) { + printf("No SI when leaving idle, please fix!\n"); + exit(0L); + } + cs->sync_retries = SYNC_RETRIES; + gsm322_sync_to_cell(cs, NULL, 1); + + return 0; +} + +/* switch on */ +static int gsm322_c_switch_on(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_subscriber *subscr = &ms->subscr; + + /* if no SIM is is MS */ + if (!subscr->sim_valid) { + LOGP(DCS, LOGL_INFO, "Switch on without SIM.\n"); + return gsm322_c_any_cell_sel(ms, msg); + } + LOGP(DCS, LOGL_INFO, "Switch on with SIM inserted.\n"); + + /* stay in NULL state until PLMN is selected */ + + return 0; +} + +/* + * state machines + */ + +/* state machine for automatic PLMN selection events */ +static struct plmnastatelist { + uint32_t states; + int type; + int (*rout) (struct osmocom_ms *ms, struct msgb *msg); +} plmnastatelist[] = { + {SBIT(GSM322_A0_NULL), + GSM322_EVENT_SWITCH_ON, gsm322_a_switch_on}, + + /* special case for full search */ + {SBIT(GSM322_A0_NULL), + GSM322_EVENT_PLMN_SEARCH_END, gsm322_a_sel_first_plmn}, + + {ALL_STATES, + GSM322_EVENT_SWITCH_OFF, gsm322_a_switch_off}, + + {SBIT(GSM322_A0_NULL) | SBIT(GSM322_A6_NO_SIM), + GSM322_EVENT_SIM_INSERT, gsm322_a_switch_on}, + + {ALL_STATES, + GSM322_EVENT_SIM_INSERT, gsm322_a_sim_insert}, + + {ALL_STATES, + GSM322_EVENT_SIM_REMOVE, gsm322_a_sim_removed}, + + {ALL_STATES, + GSM322_EVENT_INVALID_SIM, gsm322_a_sim_removed}, + + {SBIT(GSM322_A1_TRYING_RPLMN), + GSM322_EVENT_REG_FAILED, gsm322_a_sel_first_plmn}, + + {SBIT(GSM322_A1_TRYING_RPLMN), + GSM322_EVENT_ROAMING_NA, gsm322_a_sel_first_plmn}, + + {SBIT(GSM322_A1_TRYING_RPLMN), + GSM322_EVENT_NO_CELL_FOUND, gsm322_a_sel_first_plmn}, + + {SBIT(GSM322_A1_TRYING_RPLMN) | SBIT(GSM322_A3_TRYING_PLMN), + GSM322_EVENT_REG_SUCCESS, gsm322_a_go_on_plmn}, + + {SBIT(GSM322_A2_ON_PLMN), + GSM322_EVENT_ROAMING_NA, gsm322_a_roaming_na}, + + {SBIT(GSM322_A2_ON_PLMN), + GSM322_EVENT_HPLMN_SEARCH, gsm322_a_hplmn_search_start}, + + {SBIT(GSM322_A2_ON_PLMN), + GSM322_EVENT_NO_CELL_FOUND, gsm322_a_loss_of_radio}, + + {SBIT(GSM322_A2_ON_PLMN), + GSM322_EVENT_USER_RESEL, gsm322_a_user_resel}, + + {SBIT(GSM322_A3_TRYING_PLMN), + GSM322_EVENT_REG_FAILED, gsm322_a_sel_next_plmn}, + + {SBIT(GSM322_A3_TRYING_PLMN), + GSM322_EVENT_ROAMING_NA, gsm322_a_sel_next_plmn}, + + {SBIT(GSM322_A3_TRYING_PLMN), + GSM322_EVENT_NO_CELL_FOUND, gsm322_a_sel_next_plmn}, + + {SBIT(GSM322_A5_HPLMN_SEARCH), + GSM322_EVENT_CELL_FOUND, gsm322_a_sel_first_plmn}, + + {SBIT(GSM322_A5_HPLMN_SEARCH), + GSM322_EVENT_NO_CELL_FOUND, gsm322_a_go_on_plmn}, + + {SBIT(GSM322_A4_WAIT_FOR_PLMN), + GSM322_EVENT_PLMN_AVAIL, gsm322_a_plmn_avail}, + + {ALL_STATES, + GSM322_EVENT_SEL_MANUAL, gsm322_a_sel_manual}, + + {ALL_STATES, + GSM322_EVENT_NO_CELL_FOUND, gsm322_am_no_cell_found}, +}; + +#define PLMNASLLEN \ + (sizeof(plmnastatelist) / sizeof(struct plmnastatelist)) + +static int gsm322_a_event(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_msg *gm = (struct gsm322_msg *) msg->data; + int msg_type = gm->msg_type; + int rc; + int i; + + LOGP(DPLMN, LOGL_INFO, "(ms %s) Event '%s' for automatic PLMN " + "selection in state '%s'\n", ms->name, get_event_name(msg_type), + get_a_state_name(plmn->state)); + /* find function for current state and message */ + for (i = 0; i < PLMNASLLEN; i++) + if ((msg_type == plmnastatelist[i].type) + && ((1 << plmn->state) & plmnastatelist[i].states)) + break; + if (i == PLMNASLLEN) { + LOGP(DPLMN, LOGL_NOTICE, "Event unhandled at this state.\n"); + return 0; + } + + rc = plmnastatelist[i].rout(ms, msg); + + return rc; +} + +/* state machine for manual PLMN selection events */ +static struct plmnmstatelist { + uint32_t states; + int type; + int (*rout) (struct osmocom_ms *ms, struct msgb *msg); +} plmnmstatelist[] = { + {SBIT(GSM322_M0_NULL), + GSM322_EVENT_SWITCH_ON, gsm322_m_switch_on}, + + {SBIT(GSM322_M0_NULL) | SBIT(GSM322_M3_NOT_ON_PLMN) | + SBIT(GSM322_M2_ON_PLMN), + GSM322_EVENT_PLMN_SEARCH_END, gsm322_m_display_plmns}, + + {ALL_STATES, + GSM322_EVENT_SWITCH_OFF, gsm322_m_switch_off}, + + {SBIT(GSM322_M0_NULL) | SBIT(GSM322_M5_NO_SIM), + GSM322_EVENT_SIM_INSERT, gsm322_m_switch_on}, + + {ALL_STATES, + GSM322_EVENT_SIM_INSERT, gsm322_m_sim_insert}, + + {ALL_STATES, + GSM322_EVENT_SIM_REMOVE, gsm322_m_sim_removed}, + + {SBIT(GSM322_M1_TRYING_RPLMN), + GSM322_EVENT_REG_FAILED, gsm322_m_display_plmns}, + + {SBIT(GSM322_M1_TRYING_RPLMN), + GSM322_EVENT_ROAMING_NA, gsm322_m_display_plmns}, + + {SBIT(GSM322_M1_TRYING_RPLMN), + GSM322_EVENT_NO_CELL_FOUND, gsm322_m_display_plmns}, + + {SBIT(GSM322_M1_TRYING_RPLMN), + GSM322_EVENT_REG_SUCCESS, gsm322_m_go_on_plmn}, + + {SBIT(GSM322_M2_ON_PLMN), + GSM322_EVENT_ROAMING_NA, gsm322_m_display_plmns}, + + /* undocumented case, where we loose coverage */ + {SBIT(GSM322_M2_ON_PLMN), + GSM322_EVENT_NO_CELL_FOUND, gsm322_m_display_plmns}, + + {SBIT(GSM322_M1_TRYING_RPLMN) | SBIT(GSM322_M2_ON_PLMN) | + SBIT(GSM322_M4_TRYING_PLMN), + GSM322_EVENT_INVALID_SIM, gsm322_m_sim_removed}, + + {SBIT(GSM322_M3_NOT_ON_PLMN) | SBIT(GSM322_M2_ON_PLMN), + GSM322_EVENT_USER_RESEL, gsm322_m_user_resel}, + + {SBIT(GSM322_M3_NOT_ON_PLMN), + GSM322_EVENT_PLMN_AVAIL, gsm322_m_plmn_avail}, + + /* choose plmn is only specified when 'not on PLMN', but it makes + * sense to select cell from other states too. */ + {SBIT(GSM322_M3_NOT_ON_PLMN) | SBIT(GSM322_M2_ON_PLMN) | + SBIT(GSM322_M1_TRYING_RPLMN) | SBIT(GSM322_M4_TRYING_PLMN), + GSM322_EVENT_CHOOSE_PLMN, gsm322_m_choose_plmn}, + + {SBIT(GSM322_M4_TRYING_PLMN), + GSM322_EVENT_REG_SUCCESS, gsm322_m_go_on_plmn}, + + /* we also display available PLMNs after trying to register. + * this is not standard. we need that so the user knows + * that registration failed, and the user can select a new network. */ + {SBIT(GSM322_M4_TRYING_PLMN), + GSM322_EVENT_REG_FAILED, gsm322_m_display_plmns}, + + {SBIT(GSM322_M4_TRYING_PLMN), + GSM322_EVENT_ROAMING_NA, gsm322_m_display_plmns}, + + {SBIT(GSM322_M4_TRYING_PLMN), + GSM322_EVENT_NO_CELL_FOUND, gsm322_m_display_plmns}, + + {ALL_STATES, + GSM322_EVENT_SEL_AUTO, gsm322_m_sel_auto}, + + {ALL_STATES, + GSM322_EVENT_NO_CELL_FOUND, gsm322_am_no_cell_found}, +}; + +#define PLMNMSLLEN \ + (sizeof(plmnmstatelist) / sizeof(struct plmnmstatelist)) + +static int gsm322_m_event(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_msg *gm = (struct gsm322_msg *) msg->data; + int msg_type = gm->msg_type; + int rc; + int i; + + LOGP(DPLMN, LOGL_INFO, "(ms %s) Event '%s' for manual PLMN selection " + "in state '%s'\n", ms->name, get_event_name(msg_type), + get_m_state_name(plmn->state)); + /* find function for current state and message */ + for (i = 0; i < PLMNMSLLEN; i++) + if ((msg_type == plmnmstatelist[i].type) + && ((1 << plmn->state) & plmnmstatelist[i].states)) + break; + if (i == PLMNMSLLEN) { + LOGP(DPLMN, LOGL_NOTICE, "Event unhandled at this state.\n"); + return 0; + } + + rc = plmnmstatelist[i].rout(ms, msg); + + return rc; +} + +/* dequeue GSM 03.22 PLMN events */ +int gsm322_plmn_dequeue(struct osmocom_ms *ms) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct msgb *msg; + int work = 0; + + while ((msg = msgb_dequeue(&plmn->event_queue))) { + /* send event to PLMN select process */ + if (ms->settings.plmn_mode == PLMN_MODE_AUTO) + gsm322_a_event(ms, msg); + else + gsm322_m_event(ms, msg); + msgb_free(msg); + work = 1; /* work done */ + } + + return work; +} + +/* state machine for channel selection events */ +static struct cellselstatelist { + uint32_t states; + int type; + int (*rout) (struct osmocom_ms *ms, struct msgb *msg); +} cellselstatelist[] = { + {ALL_STATES, + GSM322_EVENT_SWITCH_ON, gsm322_c_switch_on}, + + {ALL_STATES, + GSM322_EVENT_SIM_REMOVE, gsm322_c_sim_remove}, + + {ALL_STATES, + GSM322_EVENT_NEW_PLMN, gsm322_c_new_plmn}, + + {ALL_STATES, + GSM322_EVENT_PLMN_SEARCH_START, gsm322_c_plmn_search}, + + {SBIT(GSM322_C1_NORMAL_CELL_SEL) | SBIT(GSM322_C2_STORED_CELL_SEL) | + SBIT(GSM322_C4_NORMAL_CELL_RESEL) | SBIT(GSM322_C5_CHOOSE_CELL), + GSM322_EVENT_CELL_FOUND, gsm322_c_camp_normally}, + + {SBIT(GSM322_C9_CHOOSE_ANY_CELL) | SBIT(GSM322_C6_ANY_CELL_SEL) | + SBIT(GSM322_C8_ANY_CELL_RESEL), + GSM322_EVENT_CELL_FOUND, gsm322_c_camp_any_cell}, + + {SBIT(GSM322_C1_NORMAL_CELL_SEL) | SBIT(GSM322_C6_ANY_CELL_SEL) | + SBIT(GSM322_C9_CHOOSE_ANY_CELL) | SBIT(GSM322_C8_ANY_CELL_RESEL) | + SBIT(GSM322_C0_NULL) /* after search */, + GSM322_EVENT_NO_CELL_FOUND, gsm322_c_any_cell_sel}, + + {SBIT(GSM322_C2_STORED_CELL_SEL) | SBIT(GSM322_C5_CHOOSE_CELL) | + SBIT(GSM322_C4_NORMAL_CELL_RESEL), + GSM322_EVENT_NO_CELL_FOUND, gsm322_c_normal_cell_sel}, + + {SBIT(GSM322_C3_CAMPED_NORMALLY), + GSM322_EVENT_LEAVE_IDLE, gsm322_c_conn_mode_1}, + + {SBIT(GSM322_C7_CAMPED_ANY_CELL), + GSM322_EVENT_LEAVE_IDLE, gsm322_c_conn_mode_2}, + + {SBIT(GSM322_CONNECTED_MODE_1), + GSM322_EVENT_RET_IDLE, gsm322_c_choose_cell}, + + {SBIT(GSM322_CONNECTED_MODE_2), + GSM322_EVENT_RET_IDLE, gsm322_c_choose_any_cell}, + + {SBIT(GSM322_C3_CAMPED_NORMALLY), + GSM322_EVENT_CELL_RESEL, gsm322_c_normal_cell_resel}, + + {SBIT(GSM322_C7_CAMPED_ANY_CELL), + GSM322_EVENT_CELL_RESEL, gsm322_c_any_cell_resel}, + + {SBIT(GSM322_C7_CAMPED_ANY_CELL), + GSM322_EVENT_CELL_FOUND, gsm322_c_normal_cell_sel}, + + {SBIT(GSM322_C1_NORMAL_CELL_SEL) | SBIT(GSM322_C2_STORED_CELL_SEL) | + SBIT(GSM322_C4_NORMAL_CELL_RESEL) | SBIT(GSM322_C5_CHOOSE_CELL) | + SBIT(GSM322_C9_CHOOSE_ANY_CELL) | SBIT(GSM322_C8_ANY_CELL_RESEL) | + SBIT(GSM322_C6_ANY_CELL_SEL) | SBIT(GSM322_ANY_SEARCH) | + SBIT(GSM322_PLMN_SEARCH) | SBIT(GSM322_HPLMN_SEARCH) , + GSM322_EVENT_SYSINFO, gsm322_c_scan_sysinfo_bcch}, + + {SBIT(GSM322_C3_CAMPED_NORMALLY) | SBIT(GSM322_C7_CAMPED_ANY_CELL), + GSM322_EVENT_SYSINFO, gsm322_c_camp_sysinfo_bcch}, + + {SBIT(GSM322_C3_CAMPED_NORMALLY), + GSM322_EVENT_HPLMN_SEARCH, gsm322_c_hplmn_search}, +}; + +#define CELLSELSLLEN \ + (sizeof(cellselstatelist) / sizeof(struct cellselstatelist)) + +int gsm322_c_event(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm322_msg *gm = (struct gsm322_msg *) msg->data; + int msg_type = gm->msg_type; + int rc; + int i; + + if (msg_type != GSM322_EVENT_SYSINFO) + LOGP(DCS, LOGL_INFO, "(ms %s) Event '%s' for Cell selection " + "in state '%s'\n", ms->name, get_event_name(msg_type), + get_cs_state_name(cs->state)); + /* find function for current state and message */ + for (i = 0; i < CELLSELSLLEN; i++) + if ((msg_type == cellselstatelist[i].type) + && ((1 << cs->state) & cellselstatelist[i].states)) + break; + if (i == CELLSELSLLEN) { + if (msg_type != GSM322_EVENT_SYSINFO) + LOGP(DCS, LOGL_NOTICE, "Event unhandled at this state." + "\n"); + return 0; + } + + rc = cellselstatelist[i].rout(ms, msg); + + return rc; +} + +/* dequeue GSM 03.22 cell selection events */ +int gsm322_cs_dequeue(struct osmocom_ms *ms) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct msgb *msg; + int work = 0; + + while ((msg = msgb_dequeue(&cs->event_queue))) { + /* send event to cell selection process */ + gsm322_c_event(ms, msg); + msgb_free(msg); + work = 1; /* work done */ + } + + return work; +} + +/* + * neighbour cell measurement process in idle mode + */ + +static struct gsm322_neighbour *gsm322_nb_alloc(struct gsm322_cellsel *cs, + uint16_t arfcn) +{ + struct gsm322_neighbour *nb; + time_t now; + + time(&now); + + nb = talloc_zero(l23_ctx, 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 band, class; + struct gsm322_neighbour *nb; + time_t now; + char arfcn_text[10]; + + time(&now); + + /* set out access class depending on the cell selection type */ + if (any) { + acc_class = (subscr->acc_class | 0x0400); /* add emergency */ + LOGP(DNB, LOGL_DEBUG, "Re-select using access class with " + "Emergency class.\n"); + } else { + acc_class = subscr->acc_class; + LOGP(DNB, LOGL_DEBUG, "Re-select using access class.\n"); + } + + if (ms->rrlayer.monitor) { + vty_notify(ms, "MON: cell ARFCN LAC C1 C2 CRH RLA_C " + "bargraph\n"); + snprintf(arfcn_text, 10, "%s ", + gsm_print_arfcn(cs->sel_arfcn)); + arfcn_text[9] = '\0'; + vty_notify(ms, "MON: serving %s 0x%04x %3d %3d %4d " + "%s\n", arfcn_text, cs->sel_lac, cs->c1, cs->c2, + cs->rla_c_dbm, bargraph(cs->rla_c_dbm / 2, -55, -24)); + } + + /* loop through all neighbour cells and select best cell */ + llist_for_each_entry(nb, &cs->nb_list, entry) { + LOGP(DNB, LOGL_INFO, "Checking cell of ARFCN %s for cell " + "re-selection.\n", gsm_print_arfcn(nb->arfcn)); + s = cs->list[arfcn2index(nb->arfcn)].sysinfo; + nb->checked_for_resel = 0; + nb->suitable_allowable = 0; + nb->c12_valid = 1; + nb->prio_low = 0; + + if (nb->state == GSM322_NB_NOT_SUP) { + LOGP(DNB, LOGL_INFO, "Skip cell: ARFCN not supported." + "\n"); + if (ms->rrlayer.monitor) { + snprintf(arfcn_text, 10, "%s ", + gsm_print_arfcn(nb->arfcn)); + arfcn_text[9] = '\0'; + vty_notify(ms, "MON: nb %2d %s ARFCN not " + "supported\n", i + 1, arfcn_text); + } + goto cont; + } + /* check if we have successfully read BCCH */ + if (!s || nb->state != GSM322_NB_SYSINFO) { + LOGP(DNB, LOGL_INFO, "Skip cell: There are no system " + "informations available.\n"); + if (ms->rrlayer.monitor) { + snprintf(arfcn_text, 10, "%s ", + gsm_print_arfcn(nb->arfcn)); + arfcn_text[9] = '\0'; + vty_notify(ms, "MON: nb %2d %s " + " %4d %s\n", + i + 1, arfcn_text, nb->rla_c_dbm, + bargraph(nb->rla_c_dbm / 2, -55, -24)); + } + goto cont; + } + + /* get prio */ + if (s->sp && s->sp_cbq) + nb->prio_low = 1; + + /* get C1 & C2 */ + band = gsm_arfcn2band(nb->arfcn); + class = class_of_band(ms, band); + nb->c1 = calculate_c1(DNB, nb->rla_c_dbm, s->rxlev_acc_min_db, + ms_pwr_dbm(band, s->ms_txpwr_max_cch), + ms_class_gmsk_dbm(band, class)); + nb->c2 = calculate_c2(nb->c1, 0, + (cs->last_serving_valid + && cs->last_serving_arfcn == nb->arfcn), + s->sp, s->sp_cro, now - nb->created, s->sp_pt, + s->sp_to); + nb->c12_valid = 1; + + /* calculate CRH depending on LAI */ + if (cs->sel_mcc == s->mcc && cs->sel_mnc == s->mnc + && cs->sel_lac == s->lac) { + LOGP(DNB, LOGL_INFO, "-> Cell of is in the same LA, " + "so CRH = 0\n"); + nb->crh = 0; + } else if (any) { + LOGP(DNB, LOGL_INFO, "-> Cell of is in a different LA, " + "but service is limited, so CRH = 0\n"); + nb->crh = 0; + } else { + nb->crh = s->cell_resel_hyst_db; + LOGP(DNB, LOGL_INFO, "-> Cell of is in a different LA, " + "and service is normal, so CRH = %d\n", + nb->crh); + } + + if (ms->rrlayer.monitor) { + snprintf(arfcn_text, 10, "%s ", + gsm_print_arfcn(nb->arfcn)); + arfcn_text[9] = '\0'; + vty_notify(ms, "MON: nb %2d %s 0x%04x %3d %3d %2d" + " %4d %s\n", i + 1, arfcn_text, s->lac, + nb->c1, nb->c2, nb->crh, nb->rla_c_dbm, + bargraph(nb->rla_c_dbm / 2, -55, -24)); + } + + /* if cell is barred and we don't override */ + if (s->cell_barr && !(s->sp && s->sp_cbq)) { + LOGP(DNB, LOGL_INFO, "Skip cell: Cell is barred.\n"); + goto cont; + } + + /* if we have no access to the cell and we don't override */ + if (!subscr->acc_barr + && !(acc_class & (s->class_barr ^ 0xffff))) { + LOGP(DNB, LOGL_INFO, "Skip cell: Class is " + "barred for our access. (access=%04x " + "barred=%04x)\n", acc_class, s->class_barr); + goto cont; + } + + /* check if LA is forbidden */ + if (any && gsm322_is_forbidden_la(ms, s->mcc, s->mnc, s->lac)) { + LOGP(DNB, LOGL_INFO, "Skip cell: Cell has " + "forbidden LA.\n"); + goto cont; + } + + /* check if we have same PLMN */ + if (!any && (cs->sel_mcc != s->mcc || cs->sel_mnc != s->mnc)) { + LOGP(DNB, LOGL_INFO, "Skip cell: PLMN of cell " + "does not match target PLMN. (cell: mcc=%s " + "mnc=%s)\n", gsm_print_mcc(s->mcc), + gsm_print_mnc(s->mnc)); + goto cont; + } + + /* check criterion C1 */ + if (nb->c1 < 0) { + LOGP(DNB, LOGL_INFO, "Skip cell: C1 criterion " + " (>0) not met. (C1 = %d)\n", nb->c1); + goto cont; + } + + /* we can use this cell, if it is better */ + nb->suitable_allowable = 1; + + /* check priority */ + if (!cs->prio_low && nb->prio_low) { + LOGP(DNB, LOGL_INFO, "Skip cell: cell has low " + "priority, but serving cell has normal " + "prio.\n"); + goto cont; + } + if (cs->prio_low && !nb->prio_low) { + LOGP(DNB, LOGL_INFO, "Found cell: cell has normal " + "priority, but serving cell has low prio.\n"); + reselect = 1; + goto cont; + } + + /* find better cell */ + if (nb->c2 - nb->crh > cs->c2) { + LOGP(DNB, LOGL_INFO, "Found cell: cell is better " + "than serving cell.\n"); + reselect = 1; + goto cont; + } + +cont: + if (++i == GSM58_NB_NUMBER) + break; + } + + if (!i) { + if (ms->rrlayer.monitor) + vty_notify(ms, "MON: no neighbour cells\n"); + } + + if (cs->resel_when + GSM58_RESEL_THRESHOLD >= now) { + LOGP(DNB, LOGL_INFO, "Found better neighbour cell, but " + "reselection threshold not reached.\n"); + reselect = 0; + } + + if (reselect && set->stick) { + LOGP(DNB, LOGL_INFO, "Don't trigger cell re-selection, because " + "we stick to serving cell.\n"); + reselect = 0; + } + + return reselect; +} + +/* select a suitable and allowable cell */ +static int gsm322_nb_scan(struct osmocom_ms *ms) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm_settings *set = &ms->settings; + int i = 0; + struct gsm322_neighbour *nb, *best_nb_low = NULL, *best_nb_normal = 0; + int16_t best_low = -32768, best_normal = -32768; + + if (set->stick) { + LOGP(DCS, LOGL_DEBUG, "Do not re-select cell, because we stick " + " to a cell.\n"); + goto no_cell_found; + } + + if (!cs->c12_valid) { + LOGP(DCS, LOGL_DEBUG, "Do not re-select cell, because there " + " are no valid C1 and C2.\n"); + goto no_cell_found; + } + + /* loop through all neighbour cells and select best cell */ + llist_for_each_entry(nb, &cs->nb_list, entry) { + LOGP(DCS, LOGL_INFO, "Checking cell with ARFCN %s for cell " + "re-selection. (C2 = %d)\n", gsm_print_arfcn(nb->arfcn), + nb->c2); + /* track which cells have been checked do far */ + if (nb->checked_for_resel) { + LOGP(DCS, LOGL_INFO, "Skip cell: alredy 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 informations, 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(l23_ctx, + struct gsm48_sysinfo); + if (!cs->list[cs->arfci].sysinfo) + exit(-ENOMEM); + cs->si = cs->list[cs->arfci].sysinfo; + cs->sync_retries = SYNC_RETRIES; + return gsm322_sync_to_cell(cs, NULL, 0); +} + +/* start/modify measurement process with the current list of neighbour cells. + * only do that if: 1. we are camping 2. we are on serving cell */ +static int gsm322_nb_start(struct osmocom_ms *ms, int synced) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = &cs->sel_si; + struct gsm322_neighbour *nb, *nb2; + int i, num; + uint8_t map[128]; + uint16_t nc[32]; + uint8_t changed = 0; + int refer_pcs, index; + uint16_t arfcn; + + if (cs->ms->settings.no_neighbour) + return 0; + + if (synced) + cs->nb_meas_set = 0; + + refer_pcs = gsm_refer_pcs(cs->sel_arfcn, s); + + /* remove all neighbours that are not in list anymore */ + memset(map, 0, sizeof(map)); + llist_for_each_entry_safe(nb, nb2, &cs->nb_list, entry) { + i = nb->arfcn & 1023; + map[i >> 3] |= (1 << (i & 7)); +#ifndef TEST_INCLUDE_SERV + if (!(s->freq[i].mask & FREQ_TYPE_NCELL)) { +#else + if (!(s->freq[i].mask & (FREQ_TYPE_NCELL | FREQ_TYPE_SERV))) { +#endif + LOGP(DNB, LOGL_INFO, "Removing neighbour cell %s from " + "list.\n", gsm_print_arfcn(nb->arfcn)); + gsm322_nb_free(nb); + changed = 1; + continue; + } +#ifndef TEST_INCLUDE_SERV + if (nb->arfcn == cs->sel_arfcn) { + LOGP(DNB, LOGL_INFO, "Removing serving cell %s (former " + "neighbour cell).\n", + gsm_print_arfcn(nb->arfcn)); + gsm322_nb_free(nb); + changed = 1; + continue; + } +#endif + } + + /* add missing entries to list */ + for (i = 0; i <= 1023; i++) { +#ifndef TEST_INCLUDE_SERV + if ((s->freq[i].mask & FREQ_TYPE_NCELL) && + !(map[i >> 3] & (1 << (i & 7)))) { +#else + if ((s->freq[i].mask & (FREQ_TYPE_NCELL | FREQ_TYPE_SERV)) && + !(map[i >> 3] & (1 << (i & 7)))) { +#endif + index = i; + if (refer_pcs && i >= 512 && i <= 810) + index = i-512+1024; + arfcn = index2arfcn(index); +#ifndef TEST_INCLUDE_SERV + if (arfcn == cs->sel_arfcn) { + LOGP(DNB, LOGL_INFO, "Omitting serving cell %s." + "\n", gsm_print_arfcn(cs->arfcn)); + continue; + } +#endif + nb = gsm322_nb_alloc(cs, arfcn); + LOGP(DNB, LOGL_INFO, "Adding neighbour cell %s to " + "list.\n", gsm_print_arfcn(nb->arfcn)); + if (!(cs->list[index].flags & GSM322_CS_FLAG_SUPPORT)) + nb->state = GSM322_NB_NOT_SUP; + changed = 1; + } + } + + /* if nothing has changed, we are done */ + if (!changed && cs->nb_meas_set) + return 0; + + /* start neigbour 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_db) { + /* 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(l23_ctx, + struct gsm48_sysinfo); + if (!cs->list[cs->arfci].sysinfo) + exit(-ENOMEM); + cs->si = cs->list[cs->arfci].sysinfo; + cs->sync_retries = SYNC_RETRIES; + return gsm322_sync_to_cell(cs, nb, 0); + } + + if (gsm322_nb_check(ms, (cs->state == GSM322_C7_CAMPED_ANY_CELL)) > 0) { + struct msgb *nmsg; + + LOGP(DNB, LOGL_INFO, "Better neighbour cell triggers cell " + "reselection.\n"); + + if (ms->rrlayer.monitor) + vty_notify(ms, "MON: trigger cell re-selection: " + "better cell\n"); + + cs->resel_when = now; + + /* unset selected cell */ + gsm322_unselect_cell(cs); + + nmsg = gsm322_msgb_alloc(GSM322_EVENT_CELL_RESEL); + if (!nmsg) + return -ENOMEM; + gsm322_c_event(ms, nmsg); + msgb_free(nmsg); + return 0; + } + + if (cs->neighbour) { + cs->arfcn = cs->sel_arfcn; + cs->arfci = arfcn2index(cs->arfcn); + cs->si = cs->list[cs->arfci].sysinfo; + if (!cs->si) { + printf("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; + int band = gsm_arfcn2band(cs->arfcn); + int class = class_of_band(cs->ms, band); + + + /* calculate the RAL_C of serving cell */ + if (cs->rxlev_count) { + cs->rla_c_dbm = (cs->rxlev_dbm + (cs->rxlev_count / 2)) + / cs->rxlev_count; + cs->rxlev_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_dbm + (nb->rxlev_count / 2)) + / nb->rxlev_count; + nb->rxlev_count = 0; + nb->rxlev_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_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_dbm += rx_lev - 110; + cs->rxlev_count++; + + return 0; +} + +/* + * dump lists + */ + +int gsm322_dump_sorted_plmn(struct osmocom_ms *ms) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_plmn_list *temp; + + LOGP(DPLMN, LOGL_INFO, "MCC |MNC |allowed|rx-lev\n"); + LOGP(DPLMN, LOGL_INFO, "-------+-------+-------+-------\n"); + llist_for_each_entry(temp, &plmn->sorted_plmn, entry) { + LOGP(DPLMN, LOGL_INFO, "%s |%s%s |%s |%s\n", + gsm_print_mcc(temp->mcc), gsm_print_mnc(temp->mnc), + ((temp->mnc & 0x00f) == 0x00f) ? " ":"", + (temp->cause) ? "no ":"yes", + gsm_print_rxlev(temp->rxlev)); + } + + return 0; +} + +int gsm322_dump_cs_list(struct gsm322_cellsel *cs, uint8_t flags, + void (*print)(void *, const char *, ...), void *priv) +{ + int i; + struct gsm48_sysinfo *s; + + print(priv, "ARFCN |MCC |MNC |LAC |cell ID|forb.LA|prio |" + "min-db |max-pwr|rx-lev\n"); + print(priv, "-------+-------+-------+-------+-------+-------+-------+" + "-------+-------+-------\n"); + for (i = 0; i <= 1023+299; i++) { + s = cs->list[i].sysinfo; + if (!s || !(cs->list[i].flags & flags)) + continue; + if (i >= 1024) + print(priv, "%4dPCS|", i-1024+512); + else if (i >= 512 && i <= 885) + print(priv, "%4dDCS|", i); + else + print(priv, "%4d |", i); + if (s->mcc) { + print(priv, "%s |%s%s |", gsm_print_mcc(s->mcc), + gsm_print_mnc(s->mnc), + ((s->mnc & 0x00f) == 0x00f) ? " ":""); + print(priv, "0x%04x |0x%04x |", s->lac, s->cell_id); + } else + print(priv, "n/a |n/a |n/a |n/a |"); + if ((cs->list[i].flags & GSM322_CS_FLAG_SYSINFO)) { + if ((cs->list[i].flags & GSM322_CS_FLAG_FORBIDD)) + print(priv, "yes |"); + else + print(priv, "no |"); + if ((cs->list[i].flags & GSM322_CS_FLAG_BARRED)) + print(priv, "barred |"); + else { + if (cs->list[i].sysinfo->cell_barr) + print(priv, "low |"); + else + print(priv, "normal |"); + } + } else + print(priv, "n/a |n/a |"); + if (s->si3 || s->si4) + print(priv, "%4d |%4d |%s\n", s->rxlev_acc_min_db, + s->ms_txpwr_max_cch, + gsm_print_rxlev(cs->list[i].rxlev)); + else + print(priv, "n/a |n/a |n/a\n"); + } + print(priv, "\n"); + + return 0; +} + +int gsm322_dump_forbidden_la(struct osmocom_ms *ms, + void (*print)(void *, const char *, ...), void *priv) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_la_list *temp; + + print(priv, "MCC |MNC |LAC |cause\n"); + print(priv, "-------+-------+-------+-------\n"); + llist_for_each_entry(temp, &plmn->forbidden_la, entry) + print(priv, "%s |%s%s |0x%04x |#%d\n", + gsm_print_mcc(temp->mcc), gsm_print_mnc(temp->mnc), + ((temp->mnc & 0x00f) == 0x00f) ? " ":"", + temp->lac, temp->cause); + + return 0; +} + +int gsm322_dump_ba_list(struct gsm322_cellsel *cs, uint16_t mcc, uint16_t mnc, + void (*print)(void *, const char *, ...), void *priv) +{ + struct gsm322_ba_list *ba; + int i; + + llist_for_each_entry(ba, &cs->ba_list, entry) { + if (mcc && mnc && (mcc != ba->mcc || mnc != ba->mnc)) + continue; + print(priv, "Band Allocation of network: MCC %s MNC %s " + "(%s, %s)\n", gsm_print_mcc(ba->mcc), + gsm_print_mnc(ba->mnc), gsm_get_mcc(ba->mcc), + gsm_get_mnc(ba->mcc, ba->mnc)); + for (i = 0; i <= 1023+299; i++) { + if ((ba->freq[i >> 3] & (1 << (i & 7)))) + print(priv, " %s", + gsm_print_arfcn(index2arfcn(i))); + } + print(priv, "\n"); + } + + return 0; +} + +int gsm322_dump_nb_list(struct gsm322_cellsel *cs, + void (*print)(void *, const char *, ...), void *priv) +{ + struct gsm48_sysinfo *s; + struct gsm322_neighbour *nb; + int i = 0; + + if (!cs->selected) { + print(priv, "No serving cell selected (yet).\n"); + return 0; + } + print(priv, "Serving cell:\n\n"); + print(priv, "ARFCN=%s ", gsm_print_arfcn(cs->sel_arfcn)); + print(priv, "RLA_C=%s ", gsm_print_rxlev(cs->rla_c_dbm + 110)); + if (cs->c12_valid) + print(priv, "C1=%d C2=%d ", cs->c1, cs->c1); + else + print(priv, "C1 - C2 - "); + print(priv, "LAC=0x%04x\n\n", (cs->selected) ? cs->sel_si.lac : 0); + + print(priv, "Neighbour cells:\n\n"); + llist_for_each_entry(nb, &cs->nb_list, entry) { + if (i == 0) { + print(priv, "# |ARFCN |RLA_C |C1 |C2 |" + "CRH |prio |LAC |cell ID|usable |" + "state\n"); + print(priv, "----------------------------------------" + "----------------------------------------" + "-------\n"); + } else + if (i == GSM58_NB_NUMBER) + print(priv, "--- unmonitored cells: ---\n"); + i++; + if (cs->last_serving_valid + && cs->last_serving_arfcn == nb->arfcn) + print(priv, "%2d last|", i); + else + print(priv, "%2d |", i); + if ((nb->arfcn & ARFCN_PCS)) + print(priv, "%4dPCS|", nb->arfcn & 1023); + else if (i >= 512 && i <= 885) + print(priv, "%4dDCS|", nb->arfcn & 1023); + else + print(priv, "%4d |", nb->arfcn); + if (nb->state == GSM322_NB_NOT_SUP) { + print(priv, " ARFCN not supported\n"); + continue; + } + if (nb->rla_c_dbm > -128) + print(priv, "%6s |", + gsm_print_rxlev(nb->rla_c_dbm + 110)); + else + print(priv, "- |"); + if (nb->state == GSM322_NB_SYSINFO && nb->c12_valid) + print(priv, "%4d |%4d |%4d |", nb->c1, nb->c1, + nb->crh); + else + print(priv, "- |- |- |"); + s = cs->list[arfcn2index(nb->arfcn)].sysinfo; + if (nb->state == GSM322_NB_SYSINFO && s) { + print(priv, "%s |0x%04x |0x%04x |", + (nb->prio_low) ? "low ":"normal", s->lac, + s->cell_id); + } else + print(priv, "- |- |- |"); + + print(priv, "%s |", + (nb->suitable_allowable) ? "yes" : "no "); + print(priv, "%s\n", get_nb_state_name(nb->state)); + } + + if (i == 0) + print(priv, "No neighbour cells available (yet).\n"); + + return 0; +} + +/* + * initialization + */ + +int gsm322_init(struct osmocom_ms *ms) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_cellsel *cs = &ms->cellsel; + FILE *fp; + char filename[PATH_MAX]; + 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 */ + sprintf(filename, "%s/%s.ba", config_dir, ms->name); + fp = fopen(filename, "r"); + 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 missmatch, " + "stored BA list becomes obsolete.\n"); + } else + while(!feof(fp)) { + ba = talloc_zero(l23_ctx, struct gsm322_ba_list); + if (!ba) + return -ENOMEM; + rc = fread(buf, 4, 1, fp); + if (!rc) { + talloc_free(ba); + break; + } + ba->mcc = (buf[0] << 8) | buf[1]; + ba->mnc = (buf[2] << 8) | buf[3]; + rc = fread(ba->freq, sizeof(ba->freq), 1, fp); + if (!rc) { + talloc_free(ba); + break; + } + llist_add_tail(&ba->entry, &cs->ba_list); + LOGP(DCS, LOGL_INFO, "Read stored BA list (mcc=%s " + "mnc=%s %s, %s)\n", gsm_print_mcc(ba->mcc), + gsm_print_mnc(ba->mnc), gsm_get_mcc(ba->mcc), + gsm_get_mnc(ba->mcc, ba->mnc)); + } + fclose(fp); + } else + LOGP(DCS, LOGL_INFO, "No stored BA list\n"); + + return 0; +} + +int gsm322_exit(struct osmocom_ms *ms) +{ + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_cellsel *cs = &ms->cellsel; + struct llist_head *lh, *lh2; + struct msgb *msg; + FILE *fp; + char filename[PATH_MAX]; + struct gsm322_ba_list *ba; + uint8_t buf[4]; + 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->list[i].flags = 0; + } + + /* store BA list */ + sprintf(filename, "%s/%s.ba", config_dir, ms->name); + fp = fopen(filename, "w"); + if (fp) { + int rc; + + fputs(ba_version, fp); + llist_for_each_entry(ba, &cs->ba_list, entry) { + buf[0] = ba->mcc >> 8; + buf[1] = ba->mcc & 0xff; + buf[2] = ba->mnc >> 8; + buf[3] = ba->mnc & 0xff; + rc = fwrite(buf, 4, 1, fp); + rc = fwrite(ba->freq, sizeof(ba->freq), 1, fp); + LOGP(DCS, LOGL_INFO, "Write stored BA list (mcc=%s " + "mnc=%s %s, %s)\n", gsm_print_mcc(ba->mcc), + gsm_print_mnc(ba->mnc), gsm_get_mcc(ba->mcc), + gsm_get_mnc(ba->mcc, ba->mnc)); + } + fclose(fp); + } else + LOGP(DCS, LOGL_ERROR, "Failed to write BA list\n"); + + /* free lists */ + while ((msg = msgb_dequeue(&plmn->event_queue))) + msgb_free(msg); + while ((msg = msgb_dequeue(&cs->event_queue))) + msgb_free(msg); + llist_for_each_safe(lh, lh2, &plmn->sorted_plmn) { + llist_del(lh); + talloc_free(lh); + } + llist_for_each_safe(lh, lh2, &plmn->forbidden_la) { + llist_del(lh); + talloc_free(lh); + } + llist_for_each_safe(lh, lh2, &cs->ba_list) { + llist_del(lh); + talloc_free(lh); + } + llist_for_each_safe(lh, lh2, &cs->nb_list) + gsm322_nb_free(container_of(lh, struct gsm322_neighbour, + entry)); + return 0; +} diff --git a/src/host/layer23/src/mobile/gsm411_sms.c b/src/host/layer23/src/mobile/gsm411_sms.c new file mode 100644 index 00000000..4347e86a --- /dev/null +++ b/src/host/layer23/src/mobile/gsm411_sms.c @@ -0,0 +1,941 @@ +/* + * Code based on work of: + * (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de> + * (C) 2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2010 by On-Waves + * + * (C) 2011 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdint.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/mobile/mncc.h> +#include <osmocom/bb/mobile/transaction.h> +#include <osmocom/bb/mobile/gsm411_sms.h> +#include <osmocom/gsm/gsm0411_utils.h> +#include <osmocom/core/talloc.h> +#include <osmocom/bb/mobile/vty.h> + +#define UM_SAPI_SMS 3 + +extern void *l23_ctx; +static uint32_t new_callref = 0x40000001; + +static int gsm411_rl_recv(struct gsm411_smr_inst *inst, int msg_type, + struct msgb *msg); +static int gsm411_mn_recv(struct gsm411_smc_inst *inst, int msg_type, + struct msgb *msg); +static int gsm411_mm_send(struct gsm411_smc_inst *inst, int msg_type, + struct msgb *msg, int cp_msg_type); +static int gsm411_mn_send(struct gsm411_smr_inst *inst, int msg_type, + struct msgb *msg); +/* + * init / exit + */ + +int gsm411_sms_init(struct osmocom_ms *ms) +{ + LOGP(DLSMS, LOGL_INFO, "init SMS\n"); + + return 0; +} + +int gsm411_sms_exit(struct osmocom_ms *ms) +{ + struct gsm_trans *trans, *trans2; + + LOGP(DLSMS, LOGL_INFO, "exit SMS processes for %s\n", ms->name); + + llist_for_each_entry_safe(trans, trans2, &ms->trans_list, entry) { + if (trans->protocol == GSM48_PDISC_SMS) { + LOGP(DLSMS, LOGL_NOTICE, "Free pendig " + "SMS-transaction.\n"); + trans_free(trans); + } + } + + return 0; +} + +/* + * SMS content + */ + +struct gsm_sms *sms_alloc(void) +{ + return talloc_zero(l23_ctx, struct gsm_sms); +} + +void sms_free(struct gsm_sms *sms) +{ + talloc_free(sms); +} + +struct gsm_sms *sms_from_text(const char *receiver, int dcs, const char *text) +{ + struct gsm_sms *sms = sms_alloc(); + + if (!sms) + return NULL; + + strncpy(sms->text, text, sizeof(sms->text)-1); + + /* FIXME: don't use ID 1 static */ + sms->reply_path_req = 0; + sms->status_rep_req = 0; + sms->ud_hdr_ind = 0; + sms->protocol_id = 0; /* implicit */ + sms->data_coding_scheme = dcs; + strncpy(sms->address, receiver, sizeof(sms->address)-1); + /* Generate user_data */ + sms->user_data_len = gsm_7bit_encode(sms->user_data, sms->text); + + return sms; +} + +static int gsm411_sms_report(struct osmocom_ms *ms, struct gsm_sms *sms, + uint8_t cause) +{ + vty_notify(ms, NULL); + if (!cause) + vty_notify(ms, "SMS to %s successfull\n", sms->address); + else + vty_notify(ms, "SMS to %s failed: %s\n", sms->address, + get_value_string(gsm411_rp_cause_strs, cause)); + + return 0; +} +/* + * transaction + */ + +/* SMS Specific transaction release. + * gets called by trans_free, DO NOT CALL YOURSELF! + */ +void _gsm411_sms_trans_free(struct gsm_trans *trans) +{ + gsm411_smr_clear(&trans->sms.smr_inst); + gsm411_smc_clear(&trans->sms.smc_inst); + + if (trans->sms.sms) { + LOGP(DLSMS, LOGL_ERROR, "Transaction contains SMS.\n"); + gsm411_sms_report(trans->ms, trans->sms.sms, + GSM411_RP_CAUSE_MO_SMS_REJECTED); + sms_free(trans->sms.sms); + trans->sms.sms = NULL; + } +} + +/* release MM connection, free transaction */ +static int gsm411_trans_free(struct gsm_trans *trans) +{ + struct msgb *nmsg; + + /* release MM connection */ + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSMS_REL_REQ, trans->callref, + trans->transaction_id, trans->sms.sapi); + if (!nmsg) + return -ENOMEM; + LOGP(DLSMS, LOGL_INFO, "Sending MMSMS_REL_REQ\n"); + gsm48_mmxx_downmsg(trans->ms, nmsg); + + trans->callref = 0; + trans_free(trans); + + return 0; +} + +/* + * receive SMS + */ + +/* now here comes our SMS */ +static int gsm340_rx_sms_deliver(struct osmocom_ms *ms, struct msgb *msg, + struct gsm_sms *gsms) +{ + const char osmocomsms[] = ".osmocom/bb/sms.txt"; + int len; + const char *home; + char *sms_file; + char vty_text[sizeof(gsms->text)], *p; + FILE *fp; + + /* remove linefeeds and show at VTY */ + strcpy(vty_text, gsms->text); + for (p = vty_text; *p; p++) { + if (*p == '\n' || *p == '\r') + *p = ' '; + } + vty_notify(ms, NULL); + vty_notify(ms, "SMS from %s: '%s'\n", gsms->address, vty_text); + + home = getenv("HOME"); + if (!home) { +fail: + fprintf(stderr, "Can't deliver SMS, be sure to create '%s' in " + "your home directory.\n", osmocomsms); + return GSM411_RP_CAUSE_MT_MEM_EXCEEDED; + } + len = strlen(home) + 1 + sizeof(osmocomsms); + sms_file = talloc_size(l23_ctx, len); + if (!sms_file) + goto fail; + snprintf(sms_file, len, "%s/%s", home, osmocomsms); + + fp = fopen(sms_file, "a"); + if (!fp) + goto fail; + fprintf(fp, "[SMS from %s]\n%s\n", gsms->address, gsms->text); + fclose(fp); + + talloc_free(sms_file); + + return 0; +} + +/* process an incoming TPDU (called from RP-DATA) + * return value > 0: RP CAUSE for ERROR; < 0: silent error; 0 = success */ +static int gsm340_rx_tpdu(struct gsm_trans *trans, struct msgb *msg) +{ + uint8_t *smsp = msgb_sms(msg); + struct gsm_sms *gsms; + unsigned int sms_alphabet; + uint8_t sms_mti, sms_mms; + uint8_t oa_len_bytes; + uint8_t address_lv[12]; /* according to 03.40 / 9.1.2.5 */ + int rc = 0; + + gsms = sms_alloc(); + + /* invert those fields where 0 means active/present */ + sms_mti = *smsp & 0x03; + sms_mms = !!(*smsp & 0x04); + gsms->status_rep_req = (*smsp & 0x20); + gsms->ud_hdr_ind = (*smsp & 0x40); + gsms->reply_path_req = (*smsp & 0x80); + smsp++; + + /* length in bytes of the originate address */ + oa_len_bytes = 2 + *smsp/2 + *smsp%2; + if (oa_len_bytes > 12) { + LOGP(DLSMS, LOGL_ERROR, "Originate Address > 12 bytes ?!?\n"); + rc = GSM411_RP_CAUSE_SEMANT_INC_MSG; + goto out; + } + memset(address_lv, 0, sizeof(address_lv)); + memcpy(address_lv, smsp, oa_len_bytes); + /* mangle first byte to reflect length in bytes, not digits */ + address_lv[0] = oa_len_bytes - 1; + /* convert to real number */ + if (((smsp[1] & 0x70) >> 4) == 1) + strcpy(gsms->address, "+"); + else if (((smsp[1] & 0x70) >> 4) == 2) + strcpy(gsms->address, "0"); + else + gsms->address[0] = '\0'; + gsm48_decode_bcd_number(gsms->address + strlen(gsms->address), + sizeof(gsms->address) - strlen(gsms->address), address_lv, 1); + smsp += oa_len_bytes; + + gsms->protocol_id = *smsp++; + gsms->data_coding_scheme = *smsp++; + + sms_alphabet = gsm338_get_sms_alphabet(gsms->data_coding_scheme); + if (sms_alphabet == 0xffffffff) { + sms_free(gsms); + return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER; + } + + /* get timestamp */ + gsms->time = gsm340_scts(smsp); + smsp += 7; + + /* user data */ + gsms->user_data_len = *smsp++; + if (gsms->user_data_len) { + memcpy(gsms->user_data, smsp, gsms->user_data_len); + + switch (sms_alphabet) { + case DCS_7BIT_DEFAULT: + gsm_7bit_decode(gsms->text, smsp, gsms->user_data_len); + break; + case DCS_8BIT_DATA: + case DCS_UCS2: + case DCS_NONE: + break; + } + } + + LOGP(DLSMS, LOGL_INFO, "RX SMS: MTI: 0x%02x, " + "MR: 0x%02x PID: 0x%02x, DCS: 0x%02x, OA: %s, " + "UserDataLength: 0x%02x, UserData: \"%s\"\n", + sms_mti, gsms->msg_ref, + gsms->protocol_id, gsms->data_coding_scheme, gsms->address, + gsms->user_data_len, + sms_alphabet == DCS_7BIT_DEFAULT ? gsms->text : + osmo_hexdump(gsms->user_data, + gsms->user_data_len)); + + switch (sms_mti) { + case GSM340_SMS_DELIVER_SC2MS: + /* MS is receiving an SMS */ + rc = gsm340_rx_sms_deliver(trans->ms, msg, gsms); + break; + case GSM340_SMS_STATUS_REP_SC2MS: + case GSM340_SMS_SUBMIT_REP_SC2MS: + LOGP(DLSMS, LOGL_NOTICE, "Unimplemented MTI 0x%02x\n", sms_mti); + rc = GSM411_RP_CAUSE_IE_NOTEXIST; + break; + default: + LOGP(DLSMS, LOGL_NOTICE, "Undefined MTI 0x%02x\n", sms_mti); + rc = GSM411_RP_CAUSE_IE_NOTEXIST; + break; + } + +out: + sms_free(gsms); + + return rc; +} + +static int gsm411_send_rp_ack(struct gsm_trans *trans, uint8_t msg_ref) +{ + struct msgb *msg = gsm411_msgb_alloc(); + + LOGP(DLSMS, LOGL_INFO, "TX: SMS RP ACK\n"); + + gsm411_push_rp_header(msg, GSM411_MT_RP_ACK_MO, msg_ref); + return gsm411_smr_send(&trans->sms.smr_inst, GSM411_SM_RL_REPORT_REQ, + msg); +} + +static int gsm411_send_rp_error(struct gsm_trans *trans, + uint8_t msg_ref, uint8_t cause) +{ + struct msgb *msg = gsm411_msgb_alloc(); + + msgb_tv_put(msg, 1, cause); + + LOGP(DLSMS, LOGL_NOTICE, "TX: SMS RP ERROR, cause %d (%s)\n", cause, + get_value_string(gsm411_rp_cause_strs, cause)); + + gsm411_push_rp_header(msg, GSM411_MT_RP_ERROR_MO, msg_ref); + return gsm411_smr_send(&trans->sms.smr_inst, GSM411_SM_RL_REPORT_REQ, + msg); +} + +/* Receive a 04.11 TPDU inside RP-DATA / user data */ +static int gsm411_rx_rp_ud(struct msgb *msg, struct gsm_trans *trans, + struct gsm411_rp_hdr *rph, + uint8_t src_len, uint8_t *src, + uint8_t dst_len, uint8_t *dst, + uint8_t tpdu_len, uint8_t *tpdu) +{ + int rc = 0; + + if (dst_len && dst) + LOGP(DLSMS, LOGL_ERROR, "RP-DATA (MT) with DST ?!?\n"); + + if (!src_len || !src || !tpdu_len || !tpdu) { + LOGP(DLSMS, LOGL_ERROR, + "RP-DATA (MO) without DST or TPDU ?!?\n"); + gsm411_send_rp_error(trans, rph->msg_ref, + GSM411_RP_CAUSE_INV_MAND_INF); + return -EIO; + } + msg->l4h = tpdu; + + LOGP(DLSMS, LOGL_INFO, "DST(%u,%s)\n", src_len, + osmo_hexdump(src, src_len)); + LOGP(DLSMS, LOGL_INFO, "TPDU(%u,%s)\n", msg->tail-msg->l4h, + osmo_hexdump(msg->l4h, msg->tail-msg->l4h)); + + rc = gsm340_rx_tpdu(trans, msg); + if (rc == 0) + return gsm411_send_rp_ack(trans, rph->msg_ref); + else if (rc > 0) + return gsm411_send_rp_error(trans, rph->msg_ref, rc); + else + return rc; +} + +/* Receive a 04.11 RP-DATA message in accordance with Section 7.3.1.2 */ +static int gsm411_rx_rp_data(struct msgb *msg, struct gsm_trans *trans, + struct gsm411_rp_hdr *rph) +{ + uint8_t src_len, dst_len, rpud_len; + uint8_t *src = NULL, *dst = NULL , *rp_ud = NULL; + + /* in the MO case, this should always be zero length */ + src_len = rph->data[0]; + if (src_len) + src = &rph->data[1]; + + dst_len = rph->data[1+src_len]; + if (dst_len) + dst = &rph->data[1+src_len+1]; + + rpud_len = rph->data[1+src_len+1+dst_len]; + if (rpud_len) + rp_ud = &rph->data[1+src_len+1+dst_len+1]; + + LOGP(DLSMS, LOGL_INFO, "RX_RP-DATA: src_len=%u, dst_len=%u ud_len=%u\n", + src_len, dst_len, rpud_len); + return gsm411_rx_rp_ud(msg, trans, rph, src_len, src, dst_len, dst, + rpud_len, rp_ud); +} + +/* receive RL DATA */ +static int gsm411_rx_rl_data(struct msgb *msg, struct gsm48_hdr *gh, + struct gsm_trans *trans) +{ + struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data; + uint8_t msg_type = rp_data->msg_type & 0x07; + int rc = 0; + + switch (msg_type) { + case GSM411_MT_RP_DATA_MT: + LOGP(DLSMS, LOGL_INFO, "RX SMS RP-DATA (MT)\n"); + rc = gsm411_rx_rp_data(msg, trans, rp_data); + break; + default: + LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type); + gsm411_trans_free(trans); + rc = -EINVAL; + break; + } + + return rc; +} + +/* + * send SMS + */ + +/* Receive a 04.11 RP-ACK message (response to RP-DATA from us) */ +static int gsm411_rx_rp_ack(struct msgb *msg, struct gsm_trans *trans, + struct gsm411_rp_hdr *rph) +{ + struct osmocom_ms *ms = trans->ms; + struct gsm_sms *sms = trans->sms.sms; + + /* Acnkowledgement to MT RP_DATA, i.e. the MS confirms it + * successfully received a SMS. We can now safely mark it as + * transmitted */ + + if (!sms) { + LOGP(DLSMS, LOGL_ERROR, "RX RP-ACK but no sms in " + "transaction?!?\n"); + return gsm411_send_rp_error(trans, rph->msg_ref, + GSM411_RP_CAUSE_PROTOCOL_ERR); + } + + gsm411_sms_report(ms, sms, 0); + + sms_free(sms); + trans->sms.sms = NULL; + + return 0; +} + +static int gsm411_rx_rp_error(struct msgb *msg, struct gsm_trans *trans, + struct gsm411_rp_hdr *rph) +{ + struct osmocom_ms *ms = trans->ms; + struct gsm_sms *sms = trans->sms.sms; + uint8_t cause_len = rph->data[0]; + uint8_t cause = rph->data[1]; + + /* Error in response to MT RP_DATA, i.e. the MS did not + * successfully receive the SMS. We need to investigate + * the cause and take action depending on it */ + + LOGP(DLSMS, LOGL_NOTICE, "%s: RX SMS RP-ERROR, cause %d:%d (%s)\n", + trans->ms->name, cause_len, cause, + get_value_string(gsm411_rp_cause_strs, cause)); + + if (!sms) { + LOGP(DLSMS, LOGL_ERROR, + "RX RP-ERR, but no sms in transaction?!?\n"); + return -EINVAL; +#if 0 + return gsm411_send_rp_error(trans, rph->msg_ref, + GSM411_RP_CAUSE_PROTOCOL_ERR); +#endif + } + + gsm411_sms_report(ms, sms, cause); + + sms_free(sms); + trans->sms.sms = NULL; + + return 0; +} + +/* receive RL REPORT */ +static int gsm411_rx_rl_report(struct msgb *msg, struct gsm48_hdr *gh, + struct gsm_trans *trans) +{ + struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data; + uint8_t msg_type = rp_data->msg_type & 0x07; + int rc = 0; + + switch (msg_type) { + case GSM411_MT_RP_ACK_MT: + LOGP(DLSMS, LOGL_INFO, "RX SMS RP-ACK (MT)\n"); + rc = gsm411_rx_rp_ack(msg, trans, rp_data); + break; + case GSM411_MT_RP_ERROR_MT: + LOGP(DLSMS, LOGL_INFO, "RX SMS RP-ERROR (MT)\n"); + rc = gsm411_rx_rp_error(msg, trans, rp_data); + break; + default: + LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type); + gsm411_trans_free(trans); + rc = -EINVAL; + break; + } + + return rc; +} + +/* generate a msgb containing a TPDU derived from struct gsm_sms, + * returns total size of TPDU */ +static int gsm340_gen_tpdu(struct msgb *msg, struct gsm_sms *sms) +{ + uint8_t *smsp; + uint8_t da[12]; /* max len per 03.40 */ + uint8_t da_len = 0; + uint8_t octet_len; + unsigned int old_msg_len = msg->len; + uint8_t sms_vpf = GSM340_TP_VPF_NONE; + uint8_t sms_vp; + + /* generate first octet with masked bits */ + smsp = msgb_put(msg, 1); + /* TP-MTI (message type indicator) */ + *smsp = GSM340_SMS_SUBMIT_MS2SC; + /* TP-RD */ + if (0 /* FIXME */) + *smsp |= 0x04; + /* TP-VPF */ + *smsp |= (sms_vpf << 3); + /* TP-SRI(deliver)/SRR(submit) */ + if (sms->status_rep_req) + *smsp |= 0x20; + /* TP-UDHI (indicating TP-UD contains a header) */ + if (sms->ud_hdr_ind) + *smsp |= 0x40; + /* TP-RP */ + if (sms->reply_path_req) + *smsp |= 0x80; + + /* generate message ref */ + smsp = msgb_put(msg, 1); + *smsp = sms->msg_ref; + + /* generate destination address */ + if (sms->address[0] == '+') + da_len = gsm340_gen_oa(da, sizeof(da), 0x1, 0x1, + sms->address + 1); + else + da_len = gsm340_gen_oa(da, sizeof(da), 0x0, 0x1, sms->address); + smsp = msgb_put(msg, da_len); + memcpy(smsp, da, da_len); + + /* generate TP-PID */ + smsp = msgb_put(msg, 1); + *smsp = sms->protocol_id; + + /* generate TP-DCS */ + smsp = msgb_put(msg, 1); + *smsp = sms->data_coding_scheme; + + /* generate TP-VP */ + switch (sms_vpf) { + case GSM340_TP_VPF_NONE: + sms_vp = 0; + break; + default: + fprintf(stderr, "VPF unsupported, please fix!\n"); + exit(0); + } + smsp = msgb_put(msg, sms_vp); + + /* generate TP-UDL */ + smsp = msgb_put(msg, 1); + *smsp = sms->user_data_len; + + /* generate TP-UD */ + switch (gsm338_get_sms_alphabet(sms->data_coding_scheme)) { + case DCS_7BIT_DEFAULT: + octet_len = sms->user_data_len*7/8; + if (sms->user_data_len*7%8 != 0) + octet_len++; + /* Warning, user_data_len indicates the amount of septets + * (characters), we need amount of octets occupied */ + smsp = msgb_put(msg, octet_len); + memcpy(smsp, sms->user_data, octet_len); + break; + case DCS_UCS2: + case DCS_8BIT_DATA: + smsp = msgb_put(msg, sms->user_data_len); + memcpy(smsp, sms->user_data, sms->user_data_len); + break; + default: + LOGP(DLSMS, LOGL_NOTICE, "Unhandled Data Coding Scheme: " + "0x%02X\n", sms->data_coding_scheme); + break; + } + + return msg->len - old_msg_len; +} + +/* Take a SMS in gsm_sms structure and send it. */ +static int gsm411_tx_sms_submit(struct osmocom_ms *ms, const char *sms_sca, + struct gsm_sms *sms) +{ + struct msgb *msg; + struct gsm_trans *trans; + uint8_t *data, *rp_ud_len; + uint8_t msg_ref = 42; + int rc; + uint8_t transaction_id; + uint8_t sca[11]; /* max len per 03.40 */ + + LOGP(DLSMS, LOGL_INFO, "..._sms_submit()\n"); + + /* no running, no transaction */ + if (!ms->started || ms->shutdown) { + LOGP(DLSMS, LOGL_ERROR, "Phone is down\n"); + gsm411_sms_report(ms, sms, GSM411_RP_CAUSE_MO_TEMP_FAIL); + sms_free(sms); + return -EIO; + } + + /* allocate transaction with dummy reference */ + transaction_id = trans_assign_trans_id(ms, GSM48_PDISC_SMS, 0); + if (transaction_id < 0) { + LOGP(DLSMS, LOGL_ERROR, "No transaction ID available\n"); + gsm411_sms_report(ms, sms, GSM411_RP_CAUSE_MO_CONGESTION); + sms_free(sms); + return -ENOMEM; + } + trans = trans_alloc(ms, GSM48_PDISC_SMS, transaction_id, new_callref++); + if (!trans) { + LOGP(DLSMS, LOGL_ERROR, "No memory for trans\n"); + gsm411_sms_report(ms, sms, GSM411_RP_CAUSE_MO_TEMP_FAIL); + sms_free(sms); + return -ENOMEM; + } + gsm411_smc_init(&trans->sms.smc_inst, 0, gsm411_mn_recv, + gsm411_mm_send); + gsm411_smr_init(&trans->sms.smr_inst, 0, gsm411_rl_recv, + gsm411_mn_send); + trans->sms.sms = sms; + trans->sms.sapi = UM_SAPI_SMS; + + msg = gsm411_msgb_alloc(); + + /* no orig Address */ + data = (uint8_t *)msgb_put(msg, 1); + data[0] = 0x00; /* originator length == 0 */ + + /* Destination Address */ + sca[1] = 0x80; /* no extension */ + sca[1] |= ((sms_sca[0] == '+') ? 0x01 : 0x00) << 4; /* type */ + sca[1] |= 0x1; /* plan*/ + + rc = gsm48_encode_bcd_number(sca, sizeof(sca), 1, + sms_sca + (sms_sca[0] == '+')); + if (rc < 0) { +error: + gsm411_sms_report(ms, sms, GSM411_RP_CAUSE_SEMANT_INC_MSG); + gsm411_trans_free(trans); + msgb_free(msg); + return rc; + } + data = msgb_put(msg, rc); + memcpy(data, sca, rc); + + /* obtain a pointer for the rp_ud_len, so we can fill it later */ + rp_ud_len = (uint8_t *)msgb_put(msg, 1); + + /* generate the 03.40 TPDU */ + rc = gsm340_gen_tpdu(msg, sms); + if (rc < 0) + goto error; + *rp_ud_len = rc; + + LOGP(DLSMS, LOGL_INFO, "TX: SMS DELIVER\n"); + + gsm411_push_rp_header(msg, GSM411_MT_RP_DATA_MO, msg_ref); + return gsm411_smr_send(&trans->sms.smr_inst, GSM411_SM_RL_DATA_REQ, + msg); +} + +/* create and send SMS */ +int sms_send(struct osmocom_ms *ms, const char *sms_sca, const char *number, + const char *text) +{ + struct gsm_sms *sms = sms_from_text(number, 0, text); + + if (!sms) + return -ENOMEM; + + return gsm411_tx_sms_submit(ms, sms_sca, sms); +} + +/* + * message flow between layers + */ + +/* push MMSMS header and send to MM */ +static int gsm411_to_mm(struct msgb *msg, struct gsm_trans *trans, + int msg_type) +{ + struct gsm48_mmxx_hdr *mmh; + + /* push RR header */ + msgb_push(msg, sizeof(struct gsm48_mmxx_hdr)); + mmh = (struct gsm48_mmxx_hdr *)msg->data; + mmh->msg_type = msg_type; + mmh->ref = trans->callref; + mmh->transaction_id = trans->transaction_id; + mmh->sapi = trans->sms.sapi; + mmh->emergency = 0; + + /* send message to MM */ + LOGP(DLSMS, LOGL_INFO, "Sending '%s' to MM (callref=%x, " + "transaction_id=%d, sapi=%d)\n", get_mmxx_name(msg_type), + trans->callref, trans->transaction_id, trans->sms.sapi); + return gsm48_mmxx_downmsg(trans->ms, msg); +} + +/* mm_send: receive MMSMS sap message from SMC */ +static int gsm411_mm_send(struct gsm411_smc_inst *inst, int msg_type, + struct msgb *msg, int cp_msg_type) +{ + struct gsm_trans *trans = + container_of(inst, struct gsm_trans, sms.smc_inst); + int rc = 0; + + switch (msg_type) { + case GSM411_MMSMS_EST_REQ: + gsm411_to_mm(msg, trans, msg_type); + break; + case GSM411_MMSMS_DATA_REQ: + gsm411_push_cp_header(msg, trans->protocol, + trans->transaction_id, cp_msg_type); + msg->l3h = msg->data; + LOGP(DLSMS, LOGL_INFO, "sending CP message (trans=%x)\n", + trans->transaction_id); + rc = gsm411_to_mm(msg, trans, msg_type); + break; + case GSM411_MMSMS_REL_REQ: + LOGP(DLSMS, LOGL_INFO, "Got MMSMS_REL_REQ, destroying " + "transaction.\n"); + gsm411_to_mm(msg, trans, msg_type); + gsm411_trans_free(trans); + break; + default: + msgb_free(msg); + rc = -EINVAL; + } + + return rc; +} + +/* mm_send: receive MNSMS sap message from SMR */ +static int gsm411_mn_send(struct gsm411_smr_inst *inst, int msg_type, + struct msgb *msg) +{ + struct gsm_trans *trans = + container_of(inst, struct gsm_trans, sms.smr_inst); + + /* forward to SMC */ + return gsm411_smc_send(&trans->sms.smc_inst, msg_type, msg); +} + +/* receive SM-RL sap message from SMR + * NOTE: Message is freed by sender + */ +static int gsm411_rl_recv(struct gsm411_smr_inst *inst, int msg_type, + struct msgb *msg) +{ + struct gsm_trans *trans = + container_of(inst, struct gsm_trans, sms.smr_inst); + struct gsm48_hdr *gh = msgb_l3(msg); + int rc = 0; + + switch (msg_type) { + case GSM411_SM_RL_DATA_IND: + rc = gsm411_rx_rl_data(msg, gh, trans); + break; + case GSM411_SM_RL_REPORT_IND: + if (!gh) + LOGP(DLSMS, LOGL_INFO, "Release transaction on empty " + "report.\n"); + else { + LOGP(DLSMS, LOGL_INFO, "Release transaction on RL " + "report.\n"); + rc = gsm411_rx_rl_report(msg, gh, trans); + } + break; + default: + rc = -EINVAL; + } + + return rc; +} + +/* receive MNSMS sap message from SMC + * NOTE: Message is freed by sender + */ +static int gsm411_mn_recv(struct gsm411_smc_inst *inst, int msg_type, + struct msgb *msg) +{ + struct gsm_trans *trans = + container_of(inst, struct gsm_trans, sms.smc_inst); + struct gsm48_hdr *gh = msgb_l3(msg); + int rc = 0; + + switch (msg_type) { + case GSM411_MNSMS_EST_IND: + case GSM411_MNSMS_DATA_IND: + LOGP(DLSMS, LOGL_INFO, "MNSMS-DATA/EST-IND\n"); + rc = gsm411_smr_recv(&trans->sms.smr_inst, msg_type, msg); + break; + case GSM411_MNSMS_ERROR_IND: + if (gh) + LOGP(DLSMS, LOGL_INFO, "MNSMS-ERROR-IND, cause %d " + "(%s)\n", gh->data[0], + get_value_string(gsm411_cp_cause_strs, + gh->data[0])); + else + LOGP(DLSMS, LOGL_INFO, "MNSMS-ERROR-IND, no cause\n"); + rc = gsm411_smr_recv(&trans->sms.smr_inst, msg_type, msg); + break; + default: + rc = -EINVAL; + } + + return rc; +} + +/* receive est/data message from MM layer */ +static int gsm411_mmsms_ind(int mmsms_msg, struct gsm_trans *trans, + struct msgb *msg) +{ + struct osmocom_ms *ms = trans->ms; + struct gsm48_hdr *gh = msgb_l3(msg); + int msg_type = gh->msg_type & 0xbf; + uint8_t transaction_id = ((gh->proto_discr & 0xf0) ^ 0x80) >> 4; + /* flip */ + + /* pull the MMSMS header */ + msgb_pull(msg, sizeof(struct gsm48_mmxx_hdr)); + + LOGP(DLSMS, LOGL_INFO, "(ms %s) Received est/data '%u'\n", ms->name, + msg_type); + + /* 5.4: For MO, if a CP-DATA is received for a new + * transaction, equals reception of an implicit + * last CP-ACK for previous transaction */ + if (trans->sms.smc_inst.cp_state == GSM411_CPS_IDLE + && msg_type == GSM411_MT_CP_DATA) { + int i; + struct gsm_trans *ptrans; + + /* Scan through all remote initiated transactions */ + for (i=8; i<15; i++) { + if (i == transaction_id) + continue; + + ptrans = trans_find_by_id(ms, GSM48_PDISC_SMS, i); + if (!ptrans) + continue; + + LOGP(DLSMS, LOGL_INFO, "Implicit CP-ACK for " + "trans_id=%x\n", i); + + /* Finish it for good */ + gsm411_trans_free(ptrans); + } + } + return gsm411_smc_recv(&trans->sms.smc_inst, mmsms_msg, msg, msg_type); +} + +/* receive message from MM layer */ +int gsm411_rcv_sms(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + int msg_type = mmh->msg_type; + int sapi = mmh->sapi; + struct gsm_trans *trans; + int rc = 0; + + trans = trans_find_by_callref(ms, mmh->ref); + if (!trans) { + LOGP(DLSMS, LOGL_INFO, " -> (new transaction sapi=%d)\n", sapi); + trans = trans_alloc(ms, GSM48_PDISC_SMS, mmh->transaction_id, + mmh->ref); + if (!trans) + return -ENOMEM; + gsm411_smc_init(&trans->sms.smc_inst, 0, gsm411_mn_recv, + gsm411_mm_send); + gsm411_smr_init(&trans->sms.smr_inst, 0, gsm411_rl_recv, + gsm411_mn_send); + trans->sms.sapi = mmh->sapi; + } + + LOGP(DLSMS, LOGL_INFO, "(ms %s) Received '%s' from MM\n", ms->name, + get_mmxx_name(msg_type)); + + switch (msg_type) { + case GSM48_MMSMS_EST_CNF: + rc = gsm411_smc_recv(&trans->sms.smc_inst, GSM411_MMSMS_EST_CNF, + msg, 0); + break; + case GSM48_MMSMS_EST_IND: + case GSM48_MMSMS_DATA_IND: + rc = gsm411_mmsms_ind(msg_type, trans, msg); + break; + case GSM48_MMSMS_REL_IND: + case GSM48_MMSMS_ERR_IND: + LOGP(DLSMS, LOGL_INFO, "MM connection released.\n"); + trans_free(trans); + break; + default: + LOGP(DLSMS, LOGL_NOTICE, "Message unhandled.\n"); + rc = -ENOTSUP; + } + + return rc; +} + diff --git a/src/host/layer23/src/mobile/gsm480_ss.c b/src/host/layer23/src/mobile/gsm480_ss.c new file mode 100644 index 00000000..fda62881 --- /dev/null +++ b/src/host/layer23/src/mobile/gsm480_ss.c @@ -0,0 +1,1289 @@ +/* + * (C) 2011 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdint.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/mobile/mncc.h> +#include <osmocom/bb/mobile/transaction.h> +#include <osmocom/bb/mobile/gsm480_ss.h> +#include <osmocom/core/talloc.h> +#include <osmocom/bb/mobile/vty.h> +#include <osmocom/gsm/protocol/gsm_04_80.h> +#include <osmocom/gsm/gsm48.h> + +static uint32_t new_callref = 0x80000001; + +static int gsm480_to_mm(struct msgb *msg, struct gsm_trans *trans, + int msg_type); +static const struct value_string gsm480_err_names[] = { + { GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER, + "UNKNOWN SUBSCRIBER" }, + { GSM0480_ERR_CODE_ILLEGAL_SUBSCRIBER, + "ILLEGAL SUBSCRIBER" }, + { GSM0480_ERR_CODE_BEARER_SERVICE_NOT_PROVISIONED, + "BEARER SERVICE NOT PROVISIONED" }, + { GSM0480_ERR_CODE_TELESERVICE_NOT_PROVISIONED, + "TELESERVICE NOT PROVISIONED" }, + { GSM0480_ERR_CODE_ILLEGAL_EQUIPMENT, + "ILLEGAL EQUIPMENT" }, + { GSM0480_ERR_CODE_CALL_BARRED, + "CALL BARRED" }, + { GSM0480_ERR_CODE_ILLEGAL_SS_OPERATION, + "ILLEGAL SS OPERATION" }, + { GSM0480_ERR_CODE_SS_ERROR_STATUS, + "SS ERROR STATUS" }, + { GSM0480_ERR_CODE_SS_NOT_AVAILABLE, + "SS NOT AVAILABLE" }, + { GSM0480_ERR_CODE_SS_SUBSCRIPTION_VIOLATION, + "SS SUBSCRIPTION VIOLATION" }, + { GSM0480_ERR_CODE_SS_INCOMPATIBILITY, + "SS INCOMPATIBILITY" }, + { GSM0480_ERR_CODE_FACILITY_NOT_SUPPORTED, + "FACILITY NOT SUPPORTED" }, + { GSM0480_ERR_CODE_ABSENT_SUBSCRIBER, + "ABSENT SUBSCRIBER" }, + { GSM0480_ERR_CODE_SYSTEM_FAILURE, + "SYSTEM FAILURE" }, + { GSM0480_ERR_CODE_DATA_MISSING, + "DATA MISSING" }, + { GSM0480_ERR_CODE_UNEXPECTED_DATA_VALUE, + "UNEXPECTED DATA VALUE" }, + { GSM0480_ERR_CODE_PW_REGISTRATION_FAILURE, + "PW REGISTRATION FAILURE" }, + { GSM0480_ERR_CODE_NEGATIVE_PW_CHECK, + "NEGATIVE PW CHECK" }, + { GSM0480_ERR_CODE_NUM_PW_ATTEMPTS_VIOLATION, + "NUM PW ATTEMPTS VIOLATION" }, + { GSM0480_ERR_CODE_UNKNOWN_ALPHABET, + "UNKNOWN ALPHABET" }, + { GSM0480_ERR_CODE_USSD_BUSY, + "USSD BUSY" }, + { GSM0480_ERR_CODE_MAX_MPTY_PARTICIPANTS, + "MAX MPTY PARTICIPANTS" }, + { GSM0480_ERR_CODE_RESOURCES_NOT_AVAILABLE, + "RESOURCES NOT AVAILABLE" }, + {0, NULL } +}; + +/* taken from Wireshark */ +static const struct value_string Teleservice_vals[] = { + {0x00, "allTeleservices" }, + {0x10, "allSpeechTransmissionServices" }, + {0x11, "telephony" }, + {0x12, "emergencyCalls" }, + {0x20, "allShortMessageServices" }, + {0x21, "shortMessageMT-PP" }, + {0x22, "shortMessageMO-PP" }, + {0x60, "allFacsimileTransmissionServices" }, + {0x61, "facsimileGroup3AndAlterSpeech" }, + {0x62, "automaticFacsimileGroup3" }, + {0x63, "facsimileGroup4" }, + + {0x70, "allDataTeleservices" }, + {0x80, "allTeleservices-ExeptSMS" }, + + {0x90, "allVoiceGroupCallServices" }, + {0x91, "voiceGroupCall" }, + {0x92, "voiceBroadcastCall" }, + + {0xd0, "allPLMN-specificTS" }, + {0xd1, "plmn-specificTS-1" }, + {0xd2, "plmn-specificTS-2" }, + {0xd3, "plmn-specificTS-3" }, + {0xd4, "plmn-specificTS-4" }, + {0xd5, "plmn-specificTS-5" }, + {0xd6, "plmn-specificTS-6" }, + {0xd7, "plmn-specificTS-7" }, + {0xd8, "plmn-specificTS-8" }, + {0xd9, "plmn-specificTS-9" }, + {0xda, "plmn-specificTS-A" }, + {0xdb, "plmn-specificTS-B" }, + {0xdc, "plmn-specificTS-C" }, + {0xdd, "plmn-specificTS-D" }, + {0xde, "plmn-specificTS-E" }, + {0xdf, "plmn-specificTS-F" }, + { 0, NULL } +}; + +/* taken from Wireshark */ +static const struct value_string Bearerservice_vals[] = { + {0x00, "allBearerServices" }, + {0x10, "allDataCDA-Services" }, + {0x11, "dataCDA-300bps" }, + {0x12, "dataCDA-1200bps" }, + {0x13, "dataCDA-1200-75bps" }, + {0x14, "dataCDA-2400bps" }, + {0x15, "dataCDA-4800bps" }, + {0x16, "dataCDA-9600bps" }, + {0x17, "general-dataCDA" }, + + {0x18, "allDataCDS-Services" }, + {0x1A, "dataCDS-1200bps" }, + {0x1C, "dataCDS-2400bps" }, + {0x1D, "dataCDS-4800bps" }, + {0x1E, "dataCDS-9600bps" }, + {0x1F, "general-dataCDS" }, + + {0x20, "allPadAccessCA-Services" }, + {0x21, "padAccessCA-300bps" }, + {0x22, "padAccessCA-1200bps" }, + {0x23, "padAccessCA-1200-75bps" }, + {0x24, "padAccessCA-2400bps" }, + {0x25, "padAccessCA-4800bps" }, + {0x26, "padAccessCA-9600bps" }, + {0x27, "general-padAccessCA" }, + + {0x28, "allDataPDS-Services" }, + {0x2C, "dataPDS-2400bps" }, + {0x2D, "dataPDS-4800bps" }, + {0x2E, "dataPDS-9600bps" }, + {0x2F, "general-dataPDS" }, + + {0x30, "allAlternateSpeech-DataCDA" }, + {0x38, "allAlternateSpeech-DataCDS" }, + {0x40, "allSpeechFollowedByDataCDA" }, + {0x48, "allSpeechFollowedByDataCDS" }, + + {0x50, "allDataCircuitAsynchronous" }, + {0x60, "allAsynchronousServices" }, + {0x58, "allDataCircuitSynchronous" }, + {0x68, "allSynchronousServices" }, + + {0xD0, "allPLMN-specificBS" }, + {0xD1, "plmn-specificBS-1" }, + {0xD2, "plmn-specificBS-2" }, + {0xD3, "plmn-specificBS-3" }, + {0xD4, "plmn-specificBS-4" }, + {0xD5, "plmn-specificBS-5" }, + {0xD6, "plmn-specificBS-6" }, + {0xD7, "plmn-specificBS-7" }, + {0xD8, "plmn-specificBS-8" }, + {0xD9, "plmn-specificBS-9" }, + {0xDA, "plmn-specificBS-A" }, + {0xDB, "plmn-specificBS-B" }, + {0xDC, "plmn-specificBS-C" }, + {0xDD, "plmn-specificBS-D" }, + {0xDE, "plmn-specificBS-E" }, + {0xDF, "plmn-specificBS-F" }, + { 0, NULL } +}; + +static int gsm480_ss_result(struct osmocom_ms *ms, const char *response, + uint8_t error) +{ + vty_notify(ms, NULL); + if (response) { + char text[256], *t = text, *s; + + strncpy(text, response, sizeof(text) - 1); + text[sizeof(text) - 1] = '\0'; + while ((s = strchr(text, '\r'))) + *s = '\n'; + while ((s = strsep(&t, "\n"))) { + vty_notify(ms, "Service response: %s\n", s); + } + } else if (error) + vty_notify(ms, "Service request failed: %s\n", + get_value_string(gsm480_err_names, error)); + else + vty_notify(ms, "Service request failed.\n"); + + return 0; +} + +enum { + GSM480_SS_ST_IDLE = 0, + GSM480_SS_ST_REGISTER, + GSM480_SS_ST_ACTIVE, +}; + +/* + * init / exit + */ + +int gsm480_ss_init(struct osmocom_ms *ms) +{ + LOGP(DSS, LOGL_INFO, "init SS\n"); + + return 0; +} + +int gsm480_ss_exit(struct osmocom_ms *ms) +{ + struct gsm_trans *trans, *trans2; + + LOGP(DSS, LOGL_INFO, "exit SS processes for %s\n", ms->name); + + llist_for_each_entry_safe(trans, trans2, &ms->trans_list, entry) { + if (trans->protocol == GSM48_PDISC_NC_SS) { + LOGP(DSS, LOGL_NOTICE, "Free pendig " + "SS-transaction.\n"); + trans_free(trans); + } + } + + return 0; +} + +/* + * transaction + */ + +/* SS Specific transaction release. + * gets called by trans_free, DO NOT CALL YOURSELF! + */ +void _gsm480_ss_trans_free(struct gsm_trans *trans) +{ + if (trans->ss.msg) { + LOGP(DSS, LOGL_INFO, "Free pending SS request\n"); + msgb_free(trans->ss.msg); + trans->ss.msg = NULL; + } + vty_notify(trans->ms, NULL); + vty_notify(trans->ms, "Service connection terminated.\n"); +} + +/* release MM connection, free transaction */ +static int gsm480_trans_free(struct gsm_trans *trans) +{ + struct msgb *nmsg; + + /* release MM connection */ + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSS_REL_REQ, trans->callref, + trans->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + LOGP(DSS, LOGL_INFO, "Sending MMSS_REL_REQ\n"); + gsm48_mmxx_downmsg(trans->ms, nmsg); + + trans->callref = 0; + trans_free(trans); + + return 0; +} + +/* + * endcoding + */ + +#define GSM480_ALLOC_SIZE 512+128 +#define GSM480_ALLOC_HEADROOM 128 + +struct msgb *gsm480_msgb_alloc(void) +{ + return msgb_alloc_headroom(GSM480_ALLOC_SIZE, GSM480_ALLOC_HEADROOM, + "GSM 04.80"); +} + +static inline unsigned char *msgb_wrap_with_L(struct msgb *msgb) +{ + uint8_t *data = msgb_push(msgb, 1); + + data[0] = msgb->len - 1; + return data; +} + +/* support function taken from OpenBSC */ +static inline unsigned char *msgb_wrap_with_TL(struct msgb *msgb, uint8_t tag) +{ + uint8_t *data = msgb_push(msgb, 2); + + data[0] = tag; + data[1] = msgb->len - 2; + return data; +} + +static inline void msgb_wrap_with_TL_asn(struct msgb *msg, uint8_t tag) +{ + int len = msg->len; + uint8_t *data = msgb_push(msg, (len >= 128) ? 3 : 2); + + *data++ = tag; + if (len >= 128) + *data++ = 0x81; + *data = len; + return; +} + +/* support function taken from OpenBSC */ +static inline unsigned char *msgb_push_TLV1(struct msgb *msgb, uint8_t tag, +uint8_t value) +{ + uint8_t *data = msgb_push(msgb, 3); + + data[0] = tag; + data[1] = 1; + data[2] = value; + return data; +} + +static const char *ss_code_by_char(const char *code, uint8_t *ss_code) +{ + if (!strncmp(code, "21", 2)) { + *ss_code = 33; + return code + 2; + } + if (!strncmp(code, "67", 2)) { + *ss_code = 41; + return code + 2; + } + if (!strncmp(code, "61", 2)) { + *ss_code = 42; + return code + 2; + } + if (!strncmp(code, "62", 2)) { + *ss_code = 43; + return code + 2; + } + if (!strncmp(code, "002", 3)) { + *ss_code = 32; + return code + 3; + } + if (!strncmp(code, "004", 3)) { + *ss_code = 40; + return code + 3; + } + + return NULL; +} + +static const char *decode_ss_code(uint8_t ss_code) +{ + static char unknown[16]; + + switch (ss_code) { + case 33: + return "CFU"; + case 41: + return "CFB"; + case 42: + return "CFNR"; + case 43: + return "CF Not Reachable"; + case 32: + return "All CF"; + case 40: + return "All conditional CF"; + default: + sprintf(unknown, "Unknown %d", ss_code); + return unknown; + } +} + +static int gsm480_tx_release_compl(struct gsm_trans *trans, uint8_t cause) +{ + struct msgb *msg; + struct gsm48_hdr *gh; + + msg = gsm480_msgb_alloc(); + if (!msg) + return -ENOMEM; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_NC_SS | (trans->transaction_id << 4); + gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE; + + if (cause) { + uint8_t *tlv = msgb_put(msg, 4); + *tlv = GSM48_IE_CAUSE; + *tlv = 2; + *tlv = 0x80 | cause; + *tlv = 0x80 | GSM48_CAUSE_LOC_USER; + } + return gsm480_to_mm(msg, trans, GSM48_MMSS_DATA_REQ); +} + +static int return_imei(struct osmocom_ms *ms) +{ + char text[32]; + struct gsm_settings *set = &ms->settings; + + sprintf(text, "IMEI: %s SV: %s", set->imei, + set->imeisv + strlen(set->imei)); + gsm480_ss_result(ms, text, 0); + + return 0; +} + +/* prepend invoke-id, facility IE and facility message */ +static int gsm480_tx_invoke(struct gsm_trans *trans, struct msgb *msg, + uint8_t msg_type) +{ + struct gsm48_hdr *gh; + + /* Pre-pend the invoke ID */ + msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, trans->ss.invoke_id); + + /* Wrap this up as invoke vomponent */ + if (msg_type == GSM0480_MTYPE_FACILITY) + msgb_wrap_with_TL_asn(msg, GSM0480_CTYPE_RETURN_RESULT); + else + msgb_wrap_with_TL_asn(msg, GSM0480_CTYPE_INVOKE); + + /* Wrap this up as facility IE */ + if (msg_type == GSM0480_MTYPE_FACILITY) + msgb_wrap_with_L(msg); + else + msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY); + + /* FIXME: If phase 2, we need SSVERSION to be added */ + + /* Push L3 header */ + gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_NC_SS | (trans->transaction_id << 4); + gh->msg_type = msg_type; + + if (msg_type == GSM0480_MTYPE_FACILITY) { + /* directly transmit data on established connection */ + return gsm480_to_mm(msg, trans, GSM48_MMSS_DATA_REQ); + } else { + /* store header until our MM connection is established */ + trans->ss.msg = msg; + + /* request establishment */ + msg = gsm480_msgb_alloc(); + if (!msg) { + trans_free(trans); + return -ENOMEM; + } + return gsm480_to_mm(msg, trans, GSM48_MMSS_EST_REQ); + } +} + +static int gsm480_tx_cf(struct gsm_trans *trans, uint8_t msg_type, + uint8_t op_code, uint8_t ss_code, const char *dest) +{ + struct msgb *msg; + + /* allocate message */ + msg = gsm480_msgb_alloc(); + if (!msg) { + trans_free(trans); + return -ENOMEM; + } + + if (dest) { + uint8_t tlv[32]; + int rc; + + /* Forwarding To address */ + tlv[0] = 0x84; + tlv[2] = 0x80; /* no extension */ + tlv[2] |= ((dest[0] == '+') ? 0x01 : 0x00) << 4; /* type */ + tlv[2] |= 0x1; /* plan*/ + rc = gsm48_encode_bcd_number(tlv + 1, sizeof(tlv) - 1, 1, + dest + (dest[0] == '+')); + if (rc < 0) { + msgb_free(msg); + trans_free(trans); + return -EINVAL; + } + memcpy(msgb_put(msg, rc + 1), tlv, rc + 1); + } + + /* Encode ss-Code */ + msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, ss_code); + + /* Then wrap these as a Sequence */ + msgb_wrap_with_TL_asn(msg, GSM_0480_SEQUENCE_TAG); + + /* Pre-pend the operation code */ + msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, op_code); + + return gsm480_tx_invoke(trans, msg, msg_type); +} + +static int gsm480_tx_ussd(struct gsm_trans *trans, uint8_t msg_type, + const char *text) +{ + struct msgb *msg; + int length; + + /* allocate message */ + msg = gsm480_msgb_alloc(); + if (!msg) { + trans_free(trans); + return -ENOMEM; + } + + /* Encode service request */ + length = gsm_7bit_encode(msg->data, text); + msgb_put(msg, length); + + /* Then wrap it as an Octet String */ + msgb_wrap_with_TL_asn(msg, ASN1_OCTET_STRING_TAG); + + /* Pre-pend the DCS octet string */ + msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, 0x0F); + + /* Then wrap these as a Sequence */ + msgb_wrap_with_TL_asn(msg, GSM_0480_SEQUENCE_TAG); + + if (msg_type == GSM0480_MTYPE_FACILITY) { + /* Pre-pend the operation code */ + msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, + GSM0480_OP_CODE_USS_REQUEST); + + /* Then wrap these as a Sequence */ + msgb_wrap_with_TL_asn(msg, GSM_0480_SEQUENCE_TAG); + } else { + /* Pre-pend the operation code */ + msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, + GSM0480_OP_CODE_PROCESS_USS_REQ); + } + + return gsm480_tx_invoke(trans, msg, msg_type); +} + +/* create and send service code */ +int ss_send(struct osmocom_ms *ms, const char *code, int new_trans) +{ + struct gsm_trans *trans = NULL, *transt; + uint8_t transaction_id; + + /* look for an old transaction */ + if (!new_trans) { + llist_for_each_entry(transt, &ms->trans_list, entry) { + if (transt->protocol == GSM48_PDISC_NC_SS) { + trans = transt; + break; + } + } + } + + /* if there is an old transaction, check if we can send data */ + if (trans) { + if (trans->ss.state != GSM480_SS_ST_ACTIVE) { + LOGP(DSS, LOGL_INFO, "Pending trans not active.\n"); + gsm480_ss_result(trans->ms, "Current service pending", + 0); + return 0; + } + if (!strcmp(code, "hangup")) { + gsm480_tx_release_compl(trans, 0); + gsm480_trans_free(trans); + return 0; + } + LOGP(DSS, LOGL_INFO, "Existing transaction.\n"); + return gsm480_tx_ussd(trans, GSM0480_MTYPE_FACILITY, code); + } + + /* do nothing, if hangup is received */ + if (!strcmp(code, "hangup")) + return 0; + + /* internal codes */ + if (!strcmp(code, "*#06#")) { + return return_imei(ms); + } + + /* no running, no transaction */ + if (!ms->started || ms->shutdown) { + gsm480_ss_result(ms, "<phone is down>", 0); + return -EIO; + } + + /* allocate transaction with dummy reference */ + transaction_id = trans_assign_trans_id(ms, GSM48_PDISC_NC_SS, + 0); + if (transaction_id < 0) { + LOGP(DSS, LOGL_ERROR, "No transaction ID available\n"); + gsm480_ss_result(ms, NULL, + GSM0480_ERR_CODE_RESOURCES_NOT_AVAILABLE); + return -ENOMEM; + } + trans = trans_alloc(ms, GSM48_PDISC_NC_SS, transaction_id, + new_callref++); + if (!trans) { + LOGP(DSS, LOGL_ERROR, "No memory for trans\n"); + gsm480_ss_result(ms, NULL, + GSM0480_ERR_CODE_RESOURCES_NOT_AVAILABLE); + return -ENOMEM; + } + + /* go register sent state */ + trans->ss.state = GSM480_SS_ST_REGISTER; + + /* FIXME: generate invoke ID */ + trans->ss.invoke_id = 5; + + /* interrogate */ + if (code[0] == '*' && code[1] == '#' && code[strlen(code) - 1] == '#') { + uint8_t ss_code = 0; + + ss_code_by_char(code + 2, &ss_code); + if (code) + return gsm480_tx_cf(trans, GSM0480_MTYPE_REGISTER, + GSM0480_OP_CODE_INTERROGATE_SS, ss_code, NULL); + } else + /* register / activate */ + if (code[0] == '*' && code[strlen(code) - 1] == '#') { + uint8_t ss_code = 0; + const char *to; + char dest[32]; + + /* double star */ + if (code[1] == '*') + code++; + + to = ss_code_by_char(code + 1, &ss_code); + + /* register */ + if (code && to && to[0] == '*') { + strncpy(dest, to + 1, sizeof(dest) - 1); + dest[sizeof(dest) - 1] = '\0'; + dest[strlen(dest) - 1] = '\0'; + return gsm480_tx_cf(trans, GSM0480_MTYPE_REGISTER, + GSM0480_OP_CODE_REGISTER_SS, ss_code, dest); + } + /* activate */ + if (code && to && to[0] == '#') { + return gsm480_tx_cf(trans, GSM0480_MTYPE_REGISTER, + GSM0480_OP_CODE_ACTIVATE_SS, ss_code, NULL); + } + } else + /* erasure */ + if (code[0] == '#' && code[1] == '#' && code[strlen(code) - 1] == '#') { + uint8_t ss_code = 0; + + ss_code_by_char(code + 2, &ss_code); + + if (code) + return gsm480_tx_cf(trans, GSM0480_MTYPE_REGISTER, + GSM0480_OP_CODE_ERASE_SS, ss_code, NULL); + } else + /* deactivate */ + if (code[0] == '#' && code[strlen(code) - 1] == '#') { + uint8_t ss_code = 0; + + ss_code_by_char(code + 1, &ss_code); + + if (code) + return gsm480_tx_cf(trans, GSM0480_MTYPE_REGISTER, + GSM0480_OP_CODE_DEACTIVATE_SS, ss_code, NULL); + } + + /* other codes */ + return gsm480_tx_ussd(trans, GSM0480_MTYPE_REGISTER, code); +} + +/* + * decoding + */ + +static int parse_tag_asn1(const uint8_t *data, int len, + const uint8_t **tag_data, int *tag_len) +{ + /* at least 2 bytes (tag + len) */ + if (len < 2) + return -1; + + /* extended length */ + if (data[1] == 0x81) { + /* at least 2 bytes (tag + 0x81 + len) */ + if (len < 3) + return -1; + *tag_len = data[2]; + *tag_data = data + 3; + len -= 3; + } else { + *tag_len = data[1]; + *tag_data = data + 2; + len -= 2; + } + + /* check for buffer overflow */ + if (len < *tag_len) + return -1; + + /* return length */ + return len; +} + +static int gsm480_rx_ussd(struct gsm_trans *trans, const uint8_t *data, + int len) +{ + int num_chars; + char text[256]; + int i; + const uint8_t *tag_data; + int tag_len; + + /* sequence tag */ + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) { + LOGP(DSS, LOGL_NOTICE, "2. Sequence tag too short\n"); + return -EINVAL; + } + if (data[0] != GSM_0480_SEQUENCE_TAG) { + LOGP(DSS, LOGL_NOTICE, "Expecting 2. Sequence Tag\n"); + return -EINVAL; + } + len = tag_len; + data = tag_data; + + /* DSC */ + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 1) { + LOGP(DSS, LOGL_NOTICE, "DSC tag too short\n"); + return -EINVAL; + } + if (data[0] != ASN1_OCTET_STRING_TAG || tag_len != 1) { + LOGP(DSS, LOGL_NOTICE, "Expecting DSC tag\n"); + return -EINVAL; + } + if (tag_data[0] != 0x0f) { + LOGP(DSS, LOGL_NOTICE, "DSC not 0x0f\n"); + return -EINVAL; + } + len -= tag_data - data + tag_len; + data = tag_data + tag_len; + + /* text */ + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) { + LOGP(DSS, LOGL_NOTICE, "Text tag too short\n"); + return -EINVAL; + } + if (data[0] != ASN1_OCTET_STRING_TAG) { + LOGP(DSS, LOGL_NOTICE, "Expecting text tag\n"); + return -EINVAL; + } + num_chars = tag_len * 8 / 7; + /* Prevent a mobile-originated buffer-overrun! */ + if (num_chars > sizeof(text) - 1) + num_chars = sizeof(text) - 1; + text[sizeof(text) - 1] = '\0'; + gsm_7bit_decode(text, tag_data, num_chars); + + for (i = 0; text[i]; i++) { + if (text[i] == '\r') + text[i] = '\n'; + } + /* remove last CR, if exists */ + if (text[0] && text[strlen(text) - 1] == '\n') + text[strlen(text) - 1] = '\0'; + gsm480_ss_result(trans->ms, text, 0); + + return 0; +} + +static int gsm480_rx_cf(struct gsm_trans *trans, const uint8_t *data, + int len) +{ + struct osmocom_ms *ms = trans->ms; + const uint8_t *tag_data, *data2; + int tag_len, len2; + char number[32]; + + LOGP(DSS, LOGL_INFO, "call forwarding reply: len %d data %s\n", len, + osmo_hexdump(data, len)); + + vty_notify(ms, NULL); + + /* forwarding feature list */ + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) { + LOGP(DSS, LOGL_NOTICE, "Tag too short\n"); + return -EINVAL; + } + if (data[0] == 0x80) { + if ((tag_data[0] & 0x01)) + vty_notify(ms, "Status: activated\n"); + else + vty_notify(ms, "Status: deactivated\n"); + return 0; + } + + switch(data[0]) { + case 0xa3: + len = tag_len; + data = tag_data; + break; + case 0xa0: /* forwarding info */ + len = tag_len; + data = tag_data; + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 1) { + LOGP(DSS, LOGL_NOTICE, "Tag too short\n"); + return -EINVAL; + } + /* check for SS code */ + if (data[0] != 0x04) + break; + vty_notify(ms, "Reply for %s\n", decode_ss_code(tag_data[0])); + len -= tag_data - data + tag_len; + data = tag_data + tag_len; + /* sequence tag */ + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) { + LOGP(DSS, LOGL_NOTICE, "Tag too short\n"); + return -EINVAL; + } + if (data[0] != GSM_0480_SEQUENCE_TAG) { + LOGP(DSS, LOGL_NOTICE, "Expecting sequence tag\n"); + return -EINVAL; + } + len = tag_len; + data = tag_data; + break; + default: + vty_notify(ms, "Call Forwarding reply unsupported.\n"); + return 0; + } + + while (len) { + /* sequence tag */ + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) { + LOGP(DSS, LOGL_NOTICE, "Tag too short\n"); + return -EINVAL; + } + if (data[0] != GSM_0480_SEQUENCE_TAG) { + len -= tag_data - data + tag_len; + data = tag_data + tag_len; + LOGP(DSS, LOGL_NOTICE, "Skipping tag 0x%x\n", data[0]); + continue; + } + len -= tag_data - data + tag_len; + data = tag_data + tag_len; + len2 = tag_len; + data2 = tag_data; + + while (len2) { + /* tags in sequence */ + if (parse_tag_asn1(data2, len2, &tag_data, &tag_len) + < 1) { + LOGP(DSS, LOGL_NOTICE, "Tag too short\n"); + return -EINVAL; + } + LOGP(DSS, LOGL_INFO, "Tag: len %d data %s\n", tag_len, + osmo_hexdump(tag_data, tag_len)); + switch (data2[0]) { + case 0x82: + vty_notify(ms, "Bearer Service: %s\n", + get_value_string(Bearerservice_vals, + tag_data[0])); + break; + case 0x83: + vty_notify(ms, "Teleservice: %s\n", + get_value_string(Teleservice_vals, + tag_data[0])); + break; + case 0x84: + if ((tag_data[0] & 0x01)) + vty_notify(ms, "Status: activated\n"); + else + vty_notify(ms, "Status: deactivated\n"); + break; + case 0x85: + if (((tag_data[0] & 0x70) >> 4) == 1) + strcpy(number, "+"); + else if (((tag_data[0] & 0x70) >> 4) == 1) + strcpy(number, "+"); + else + number[0] = '\0'; + gsm48_decode_bcd_number(number + strlen(number), + sizeof(number) - strlen(number), + tag_data - 1, 1); + vty_notify(ms, "Destination: %s\n", number); + break; + } + len2 -= tag_data - data2 + tag_len; + data2 = tag_data + tag_len; + } + } + + return 0; +} + +static int gsm480_rx_result(struct gsm_trans *trans, const uint8_t *data, + int len, int msg_type) +{ + const uint8_t *tag_data; + int tag_len; + int rc = 0; + + LOGP(DSS, LOGL_INFO, "Result received (len %d)\n", len); + + if (len && data[0] == 0x8d) { + LOGP(DSS, LOGL_NOTICE, "Skipping mysterious 0x8d\n"); + len--; + data++; + } + + /* invoke ID */ + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 1) { + LOGP(DSS, LOGL_NOTICE, "Invoke ID too short\n"); + return -EINVAL; + } + if (data[0] != GSM0480_COMPIDTAG_INVOKE_ID || tag_len != 1) { + LOGP(DSS, LOGL_NOTICE, "Expecting invoke ID\n"); + return -EINVAL; + } + + if (msg_type == GSM0480_CTYPE_RETURN_RESULT) { + if (trans->ss.invoke_id != data[2]) { + LOGP(DSS, LOGL_NOTICE, "Invoke ID mismatch\n"); + } + } + /* Store invoke ID, in case we wan't to send a result. */ + trans->ss.invoke_id = tag_data[0]; + len -= tag_data - data + tag_len; + data = tag_data + tag_len; + + if (!len) { + gsm480_ss_result(trans->ms, "<no result>", 0); + return 0; + } + + /* sequence tag */ + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) { + LOGP(DSS, LOGL_NOTICE, "Sequence tag too short\n"); + return -EINVAL; + } + if (data[0] != GSM_0480_SEQUENCE_TAG) { + LOGP(DSS, LOGL_NOTICE, "Expecting Sequence Tag, trying " + "Operation Tag\n"); + goto operation; + } + len = tag_len; + data = tag_data; + + /* operation */ +operation: + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 1) { + LOGP(DSS, LOGL_NOTICE, "Operation too short\n"); + return -EINVAL; + } + if (data[0] != GSM0480_OPERATION_CODE || tag_len != 1) { + LOGP(DSS, LOGL_NOTICE, "Expecting Operation Code\n"); + return -EINVAL; + } + len -= tag_data - data + tag_len; + data = tag_data + tag_len; + + switch (tag_data[0]) { + case GSM0480_OP_CODE_PROCESS_USS_REQ: + case GSM0480_OP_CODE_USS_REQUEST: + rc = gsm480_rx_ussd(trans, data, len); + break; + case GSM0480_OP_CODE_INTERROGATE_SS: + case GSM0480_OP_CODE_REGISTER_SS: + case GSM0480_OP_CODE_ACTIVATE_SS: + case GSM0480_OP_CODE_DEACTIVATE_SS: + case GSM0480_OP_CODE_ERASE_SS: + rc = gsm480_rx_cf(trans, data, len); + break; + default: + LOGP(DSS, LOGL_NOTICE, "Operation code not USS\n"); + rc = -EINVAL; + } + + return rc; +} + +/* facility from BSC */ +static int gsm480_rx_fac_ie(struct gsm_trans *trans, const uint8_t *data, + int len) +{ + int rc = 0; + const uint8_t *tag_data; + int tag_len; + + LOGP(DSS, LOGL_INFO, "Facility received (len %d)\n", len); + + if (parse_tag_asn1(data, len, &tag_data, &tag_len) < 0) { + LOGP(DSS, LOGL_NOTICE, "Facility too short\n"); + return -EINVAL; + } + + switch (data[0]) { + case GSM0480_CTYPE_INVOKE: + case GSM0480_CTYPE_RETURN_RESULT: + rc = gsm480_rx_result(trans, tag_data, tag_len, data[0]); + break; + case GSM0480_CTYPE_RETURN_ERROR: + // FIXME: return error code + gsm480_ss_result(trans->ms, "<error received>", 0); + break; + case GSM0480_CTYPE_REJECT: + gsm480_ss_result(trans->ms, "<service rejected>", 0); + break; + default: + LOGP(DSS, LOGL_NOTICE, "CTYPE unknown\n"); + rc = -EINVAL; + } + + return rc; +} + +static int gsm480_rx_cause_ie(struct gsm_trans *trans, const uint8_t *data, + int len) +{ + uint8_t value; + + LOGP(DSS, LOGL_INFO, "Cause received (len %d)\n", len); + + if (len < 2) { + LOGP(DSS, LOGL_NOTICE, "Cause too short\n"); + return -EINVAL; + } + if (!(data[1] & 0x80)) { + if (len < 3) { + LOGP(DSS, LOGL_NOTICE, "Cause too short\n"); + return -EINVAL; + } + value = data[3] & 0x7f; + } else + value = data[2] & 0x7f; + + LOGP(DSS, LOGL_INFO, "Received Cause %d\n", value); + + /* this is an error */ + return -EINVAL; +} + +/* release complete from BSC */ +static int gsm480_rx_release_comp(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + int rc = 0; + + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + rc = gsm480_rx_fac_ie(trans, TLVP_VAL(&tp, GSM48_IE_FACILITY), + *(TLVP_VAL(&tp, GSM48_IE_FACILITY)-1)); + } else { + /* facility optional */ + LOGP(DSS, LOGL_INFO, "No facility IE received\n"); + if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { + rc = gsm480_rx_cause_ie(trans, + TLVP_VAL(&tp, GSM48_IE_CAUSE), + *(TLVP_VAL(&tp, GSM48_IE_CAUSE)-1)); + } + } + + if (rc < 0) + gsm480_ss_result(trans->ms, NULL, 0); + if (rc > 0) + gsm480_ss_result(trans->ms, NULL, rc); + + /* remote releases */ + gsm480_trans_free(trans); + + return rc; +} + +/* facility from BSC */ +static int gsm480_rx_facility(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + int rc = 0; + + /* go register state */ + trans->ss.state = GSM480_SS_ST_ACTIVE; + + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, + GSM48_IE_FACILITY, 0); + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + rc = gsm480_rx_fac_ie(trans, TLVP_VAL(&tp, GSM48_IE_FACILITY), + *(TLVP_VAL(&tp, GSM48_IE_FACILITY)-1)); + } else { + LOGP(DSS, LOGL_INFO, "No facility IE received\n"); + /* release 3.7.5 */ + gsm480_tx_release_compl(trans, 96); + /* local releases */ + gsm480_trans_free(trans); + } + + if (rc < 0) + gsm480_ss_result(trans->ms, NULL, 0); + if (rc > 0) + gsm480_ss_result(trans->ms, NULL, rc); + + return rc; +} + +/* regisster from BSC */ +static int gsm480_rx_register(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + int rc = 0; + + /* go register state */ + trans->ss.state = GSM480_SS_ST_ACTIVE; + + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + rc = gsm480_rx_fac_ie(trans, TLVP_VAL(&tp, GSM48_IE_FACILITY), + *(TLVP_VAL(&tp, GSM48_IE_FACILITY)-1)); + } else { + /* facility optional */ + LOGP(DSS, LOGL_INFO, "No facility IE received\n"); + /* release 3.7.5 */ + gsm480_tx_release_compl(trans, 96); + /* local releases */ + gsm480_trans_free(trans); + } + + if (rc < 0) + gsm480_ss_result(trans->ms, NULL, 0); + if (rc > 0) + gsm480_ss_result(trans->ms, NULL, rc); + + return rc; +} + +/* + * message handling + */ + +/* push MMSS header and send to MM */ +static int gsm480_to_mm(struct msgb *msg, struct gsm_trans *trans, + int msg_type) +{ + struct gsm48_mmxx_hdr *mmh; + + /* set l3H */ + msg->l3h = msg->data; + + /* push RR header */ + msgb_push(msg, sizeof(struct gsm48_mmxx_hdr)); + mmh = (struct gsm48_mmxx_hdr *)msg->data; + mmh->msg_type = msg_type; + mmh->ref = trans->callref; + mmh->transaction_id = trans->transaction_id; + mmh->sapi = 0; + mmh->emergency = 0; + + /* send message to MM */ + LOGP(DSS, LOGL_INFO, "Sending '%s' to MM (callref=%x, " + "transaction_id=%d)\n", get_mmxx_name(msg_type), trans->callref, + trans->transaction_id); + return gsm48_mmxx_downmsg(trans->ms, msg); +} + +/* receive est confirm from MM layer */ +static int gsm480_mmss_est(int mmss_msg, struct gsm_trans *trans, + struct msgb *msg) +{ + struct osmocom_ms *ms = trans->ms; + struct msgb *temp; + + LOGP(DSS, LOGL_INFO, "(ms %s) Received confirm, sending pending SS\n", + ms->name); + + /* remove transaction, if no SS message */ + if (!trans->ss.msg) { + LOGP(DSS, LOGL_ERROR, "(ms %s) No pending SS!\n", ms->name); + gsm480_trans_free(trans); + return -EINVAL; + } + + /* detach message and then send */ + temp = trans->ss.msg; + trans->ss.msg = NULL; + return gsm480_to_mm(temp, trans, GSM48_MMSS_DATA_REQ); +} + +/* receive data indication from MM layer */ +static int gsm480_mmss_ind(int mmss_msg, struct gsm_trans *trans, + struct msgb *msg) +{ + struct osmocom_ms *ms = trans->ms; + struct gsm48_hdr *gh = msgb_l3(msg); + int msg_type = gh->msg_type & 0xbf; + int rc = 0; + + /* pull the MMSS header */ + msgb_pull(msg, sizeof(struct gsm48_mmxx_hdr)); + + LOGP(DSS, LOGL_INFO, "(ms %s) Received est/data '%u'\n", ms->name, + msg_type); + + switch (msg_type) { + case GSM0480_MTYPE_RELEASE_COMPLETE: + rc = gsm480_rx_release_comp(trans, msg); + break; + case GSM0480_MTYPE_FACILITY: + rc = gsm480_rx_facility(trans, msg); + break; + case GSM0480_MTYPE_REGISTER: + rc = gsm480_rx_register(trans, msg); + break; + default: + LOGP(DSS, LOGL_NOTICE, "Message unhandled.\n"); + /* release 3.7.4 */ + gsm480_tx_release_compl(trans, 97); + gsm480_trans_free(trans); + rc = -ENOTSUP; + } + return 0; +} + +/* receive message from MM layer */ +int gsm480_rcv_ss(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + int msg_type = mmh->msg_type; + struct gsm_trans *trans; + int rc = 0; + + trans = trans_find_by_callref(ms, mmh->ref); + if (!trans) { + LOGP(DSS, LOGL_INFO, " -> (new transaction)\n"); + trans = trans_alloc(ms, GSM48_PDISC_NC_SS, mmh->transaction_id, + mmh->ref); + if (!trans) + return -ENOMEM; + } + + LOGP(DSS, LOGL_INFO, "(ms %s) Received '%s' from MM\n", ms->name, + get_mmxx_name(msg_type)); + + switch (msg_type) { + case GSM48_MMSS_EST_CNF: + rc = gsm480_mmss_est(msg_type, trans, msg); + break; + case GSM48_MMSS_EST_IND: + case GSM48_MMSS_DATA_IND: + rc = gsm480_mmss_ind(msg_type, trans, msg); + break; + case GSM48_MMSS_REL_IND: + case GSM48_MMSS_ERR_IND: + LOGP(DSS, LOGL_INFO, "MM connection released.\n"); + trans_free(trans); + break; + default: + LOGP(DSS, LOGL_NOTICE, "Message unhandled.\n"); + rc = -ENOTSUP; + } + + return rc; +} + diff --git a/src/host/layer23/src/mobile/gsm48_cc.c b/src/host/layer23/src/mobile/gsm48_cc.c new file mode 100644 index 00000000..38dfab02 --- /dev/null +++ b/src/host/layer23/src/mobile/gsm48_cc.c @@ -0,0 +1,2223 @@ +/* + * (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdint.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm48.h> +#include <osmocom/core/talloc.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/mobile/mncc.h> +#include <osmocom/bb/mobile/transaction.h> +#include <osmocom/bb/mobile/gsm48_cc.h> +#include <osmocom/bb/mobile/voice.h> +#include <l1ctl_proto.h> + +extern void *l23_ctx; + +static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg); +static int gsm48_rel_null_free(struct gsm_trans *trans); +int mncc_release_ind(struct osmocom_ms *ms, struct gsm_trans *trans, + uint32_t callref, int location, int value); +static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg); +static int gsm48_cc_tx_connect_ack(struct gsm_trans *trans, void *arg); + +/* + * init + */ + +int gsm48_cc_init(struct osmocom_ms *ms) +{ + struct gsm48_cclayer *cc = &ms->cclayer; + + cc->ms = ms; + + if (!cc->mncc_upqueue.next == 0) + return 0; + + LOGP(DCC, LOGL_INFO, "init Call Control\n"); + + INIT_LLIST_HEAD(&cc->mncc_upqueue); + + return 0; +} + +int gsm48_cc_exit(struct osmocom_ms *ms) +{ + struct gsm48_cclayer *cc = &ms->cclayer; + struct gsm_trans *trans, *trans2; + struct msgb *msg; + + LOGP(DCC, LOGL_INFO, "exit Call Control processes for %s\n", ms->name); + + llist_for_each_entry_safe(trans, trans2, &ms->trans_list, entry) { + if (trans->protocol == GSM48_PDISC_CC) { + LOGP(DCC, LOGL_NOTICE, "Free pendig CC-transaction.\n"); + trans_free(trans); + } + } + + while ((msg = msgb_dequeue(&cc->mncc_upqueue))) + msgb_free(msg); + + return 0; +} + +/* + * messages + */ + +/* names of MNCC-SAP */ +static const struct value_string gsm_mncc_names[] = { + { MNCC_SETUP_REQ, "MNCC_SETUP_REQ" }, + { MNCC_SETUP_IND, "MNCC_SETUP_IND" }, + { MNCC_SETUP_RSP, "MNCC_SETUP_RSP" }, + { MNCC_SETUP_CNF, "MNCC_SETUP_CNF" }, + { MNCC_SETUP_COMPL_REQ, "MNCC_SETUP_COMPL_REQ" }, + { MNCC_SETUP_COMPL_IND, "MNCC_SETUP_COMPL_IND" }, + { MNCC_CALL_CONF_IND, "MNCC_CALL_CONF_IND" }, + { MNCC_CALL_PROC_REQ, "MNCC_CALL_PROC_REQ" }, + { MNCC_PROGRESS_REQ, "MNCC_PROGRESS_REQ" }, + { MNCC_ALERT_REQ, "MNCC_ALERT_REQ" }, + { MNCC_ALERT_IND, "MNCC_ALERT_IND" }, + { MNCC_NOTIFY_REQ, "MNCC_NOTIFY_REQ" }, + { MNCC_NOTIFY_IND, "MNCC_NOTIFY_IND" }, + { MNCC_DISC_REQ, "MNCC_DISC_REQ" }, + { MNCC_DISC_IND, "MNCC_DISC_IND" }, + { MNCC_REL_REQ, "MNCC_REL_REQ" }, + { MNCC_REL_IND, "MNCC_REL_IND" }, + { MNCC_REL_CNF, "MNCC_REL_CNF" }, + { MNCC_FACILITY_REQ, "MNCC_FACILITY_REQ" }, + { MNCC_FACILITY_IND, "MNCC_FACILITY_IND" }, + { MNCC_START_DTMF_IND, "MNCC_START_DTMF_IND" }, + { MNCC_START_DTMF_RSP, "MNCC_START_DTMF_RSP" }, + { MNCC_START_DTMF_REJ, "MNCC_START_DTMF_REJ" }, + { MNCC_STOP_DTMF_IND, "MNCC_STOP_DTMF_IND" }, + { MNCC_STOP_DTMF_RSP, "MNCC_STOP_DTMF_RSP" }, + { MNCC_MODIFY_REQ, "MNCC_MODIFY_REQ" }, + { MNCC_MODIFY_IND, "MNCC_MODIFY_IND" }, + { MNCC_MODIFY_RSP, "MNCC_MODIFY_RSP" }, + { MNCC_MODIFY_CNF, "MNCC_MODIFY_CNF" }, + { MNCC_MODIFY_REJ, "MNCC_MODIFY_REJ" }, + { MNCC_HOLD_IND, "MNCC_HOLD_IND" }, + { MNCC_HOLD_CNF, "MNCC_HOLD_CNF" }, + { MNCC_HOLD_REJ, "MNCC_HOLD_REJ" }, + { MNCC_RETRIEVE_IND, "MNCC_RETRIEVE_IND" }, + { MNCC_RETRIEVE_CNF, "MNCC_RETRIEVE_CNF" }, + { MNCC_RETRIEVE_REJ, "MNCC_RETRIEVE_REJ" }, + { MNCC_USERINFO_REQ, "MNCC_USERINFO_REQ" }, + { MNCC_USERINFO_IND, "MNCC_USERINFO_IND" }, + { MNCC_REJ_REQ, "MNCC_REJ_REQ" }, + { MNCC_REJ_IND, "MNCC_REJ_IND" }, + { MNCC_PROGRESS_IND, "MNCC_PROGRESS_IND" }, + { MNCC_CALL_PROC_IND, "MNCC_CALL_PROC_IND" }, + { MNCC_CALL_CONF_REQ, "MNCC_CALL_CONF_REQ" }, + { MNCC_START_DTMF_REQ, "MNCC_START_DTMF_REQ" }, + { MNCC_STOP_DTMF_REQ, "MNCC_STOP_DTMF_REQ" }, + { MNCC_HOLD_REQ, "MNCC_HOLD_REQ " }, + { MNCC_RETRIEVE_REQ, "MNCC_RETRIEVE_REQ" }, + { MNCC_FRAME_RECV, "MNCC_FRAME_RECV" }, + { MNCC_FRAME_DROP, "MNCC_FRAME_DROP" }, + { MNCC_LCHAN_MODIFY, "MNCC_LCHAN_MODIFY" }, + { 0, NULL } +}; + +const char *get_mncc_name(int value) +{ + return get_value_string(gsm_mncc_names, value); +} + +/* push MMCC header and send to MM */ +static int gsm48_cc_to_mm(struct msgb *msg, struct gsm_trans *trans, + int msg_type) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_mmxx_hdr *mmh; + int emergency = 0; + + /* Add protocol type and transaction ID */ + gh->proto_discr = trans->protocol | (trans->transaction_id << 4); + + /* indicate emergency setup to MM layer */ + if (gh->msg_type == GSM48_MT_CC_EMERG_SETUP) + emergency = 1; + + /* push RR header */ + msgb_push(msg, sizeof(struct gsm48_mmxx_hdr)); + mmh = (struct gsm48_mmxx_hdr *)msg->data; + mmh->msg_type = msg_type; + mmh->ref = trans->callref; + mmh->transaction_id = trans->transaction_id; + mmh->sapi = 0; + mmh->emergency = emergency; + + /* send message to MM */ + LOGP(DCC, LOGL_INFO, "Sending '%s' using %s (callref=%x, " + "transaction_id=%d)\n", gsm48_cc_msg_name(gh->msg_type), + get_mmxx_name(msg_type), trans->callref, trans->transaction_id); + return gsm48_mmxx_downmsg(trans->ms, msg); +} + +/* enqueue message to application (MNCC-SAP) */ +static int mncc_recvmsg(struct osmocom_ms *ms, struct gsm_trans *trans, + int msg_type, struct gsm_mncc *mncc) +{ + struct gsm48_cclayer *cc = &ms->cclayer; + struct msgb *msg; + + if (trans) + LOGP(DCC, LOGL_INFO, "(ms %s ti %x) Sending '%s' to MNCC.\n", + ms->name, trans->transaction_id, + get_mncc_name(msg_type)); + else + LOGP(DCC, LOGL_INFO, "(ms %s ti -) Sending '%s' to MNCC.\n", + ms->name, get_mncc_name(msg_type)); + + mncc->msg_type = msg_type; + + msg = msgb_alloc(sizeof(struct gsm_mncc), "MNCC"); + if (!msg) + return -ENOMEM; + memcpy(msg->data, mncc, sizeof(struct gsm_mncc)); + msgb_enqueue(&cc->mncc_upqueue, msg); + + return 0; +} + +/* dequeue messages to layer 4 */ +int mncc_dequeue(struct osmocom_ms *ms) +{ + struct gsm48_cclayer *cc = &ms->cclayer; + struct gsm_mncc *mncc; + struct msgb *msg; + int work = 0; + + while ((msg = msgb_dequeue(&cc->mncc_upqueue))) { + mncc = (struct gsm_mncc *)msg->data; + if (ms->mncc_entity.mncc_recv) + ms->mncc_entity.mncc_recv(ms, mncc->msg_type, mncc); + work = 1; /* work done */ + msgb_free(msg); + } + + return work; +} + + +/* + * state transition + */ + +static void new_cc_state(struct gsm_trans *trans, int state) +{ + if (state > 31 || state < 0) + return; + + DEBUGP(DCC, "new state %s -> %s\n", + gsm48_cc_state_name(trans->cc.state), + gsm48_cc_state_name(state)); + + trans->cc.state = state; +} + +/* + * timers + */ + +/* timeout events of all timers */ +static void gsm48_cc_timeout(void *arg) +{ + struct gsm_trans *trans = arg; + int disconnect = 0, release = 0, abort = 1; + int mo_cause = GSM48_CC_CAUSE_RECOVERY_TIMER; + int mo_location = GSM48_CAUSE_LOC_PRN_S_LU; + int l4_cause = GSM48_CC_CAUSE_NORMAL_UNSPEC; + int l4_location = GSM48_CAUSE_LOC_PRN_S_LU; + struct gsm_mncc mo_rel, l4_rel; + + memset(&mo_rel, 0, sizeof(struct gsm_mncc)); + mo_rel.callref = trans->callref; + memset(&l4_rel, 0, sizeof(struct gsm_mncc)); + l4_rel.callref = trans->callref; + + LOGP(DCC, LOGL_INFO, "Timer T%x has fired.\n", trans->cc.Tcurrent); + + switch(trans->cc.Tcurrent) { + case 0x303: + /* abort if connection is not already esablished */ + if (trans->cc.state == GSM_CSTATE_MM_CONNECTION_PEND) + abort = 1; + else + release = 1; + l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND; + break; + case 0x305: + release = 1; + mo_cause = trans->cc.msg.cause.value; + mo_location = trans->cc.msg.cause.location; + break; + case 0x308: + if (!trans->cc.T308_second) { + /* restart T308 a second time */ + gsm48_cc_tx_release(trans, &trans->cc.msg); + trans->cc.T308_second = 1; + break; /* stay in release state */ + } + /* release MM conn, got NULL state, free trans */ + gsm48_rel_null_free(trans); + + return; + case 0x310: + disconnect = 1; + l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND; + break; + case 0x313: + disconnect = 1; + /* unknown, did not find it in the specs */ + break; + default: + release = 1; + } + + if ((release || abort) && trans->callref) { + /* process release towards layer 4 */ + mncc_release_ind(trans->ms, trans, trans->callref, + l4_location, l4_cause); + } + + if (disconnect && trans->callref) { + /* process disconnect towards layer 4 */ + mncc_set_cause(&l4_rel, l4_location, l4_cause); + mncc_recvmsg(trans->ms, trans, MNCC_DISC_IND, &l4_rel); + } + + /* process disconnect towards mobile station */ + if (disconnect || release || abort) { + mncc_set_cause(&mo_rel, mo_location, mo_cause); + mo_rel.cause.diag[0] = + ((trans->cc.Tcurrent & 0xf00) >> 8) + '0'; + mo_rel.cause.diag[1] = + ((trans->cc.Tcurrent & 0x0f0) >> 4) + '0'; + mo_rel.cause.diag[2] = (trans->cc.Tcurrent & 0x00f) + '0'; + mo_rel.cause.diag_len = 3; + + if (disconnect) + gsm48_cc_tx_disconnect(trans, &mo_rel); + if (release) + gsm48_cc_tx_release(trans, &mo_rel); + if (abort) { + /* release MM conn, got NULL state, free trans */ + gsm48_rel_null_free(trans); + } + } +} + +/* start various timers */ +static void gsm48_start_cc_timer(struct gsm_trans *trans, int current, + int sec, int micro) +{ + LOGP(DCC, LOGL_INFO, "starting timer T%x with %d seconds\n", current, + sec); + trans->cc.timer.cb = gsm48_cc_timeout; + trans->cc.timer.data = trans; + osmo_timer_schedule(&trans->cc.timer, sec, micro); + trans->cc.Tcurrent = current; +} + +/* stop various timers */ +static void gsm48_stop_cc_timer(struct gsm_trans *trans) +{ + if (osmo_timer_pending(&trans->cc.timer)) { + LOGP(DCC, LOGL_INFO, "stopping pending timer T%x\n", + trans->cc.Tcurrent); + osmo_timer_del(&trans->cc.timer); + trans->cc.Tcurrent = 0; + } +} + +/* + * process handlers (misc) + */ + +/* Call Control Specific transaction release. + * gets called by trans_free, DO NOT CALL YOURSELF! + */ +void _gsm48_cc_trans_free(struct gsm_trans *trans) +{ + gsm48_stop_cc_timer(trans); + + /* disable audio distribution */ + if (trans->ms->mncc_entity.ref == trans->callref) + trans->ms->mncc_entity.ref = 0; + + /* send release to L4, if callref still exists */ + if (trans->callref) { + /* Ressource unavailable */ + mncc_release_ind(trans->ms, trans, trans->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + } + if (trans->cc.state != GSM_CSTATE_NULL) + new_cc_state(trans, GSM_CSTATE_NULL); +} + +/* release MM connection, go NULL state, free transaction */ +static int gsm48_rel_null_free(struct gsm_trans *trans) +{ + struct msgb *nmsg; + + /* release MM connection */ + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_REL_REQ, trans->callref, + trans->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + LOGP(DCC, LOGL_INFO, "Sending MMCC_REL_REQ\n"); + gsm48_mmxx_downmsg(trans->ms, nmsg); + + new_cc_state(trans, GSM_CSTATE_NULL); + + trans->callref = 0; + trans_free(trans); + + return 0; +} + +void mncc_set_cause(struct gsm_mncc *data, int loc, int val) +{ + data->fields |= MNCC_F_CAUSE; + data->cause.coding = 0x3; + data->cause.location = loc; + data->cause.value = val; +} + +/* send release indication to upper layer */ +int mncc_release_ind(struct osmocom_ms *ms, struct gsm_trans *trans, + uint32_t callref, int location, int value) +{ + struct gsm_mncc rel; + + memset(&rel, 0, sizeof(rel)); + rel.callref = callref; + mncc_set_cause(&rel, location, value); + return mncc_recvmsg(ms, trans, MNCC_REL_IND, &rel); +} + +/* sending status message in response to unknown message */ +static int gsm48_cc_tx_status(struct gsm_trans *trans, int cause) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + uint8_t *cause_ie, *call_state_ie; + + LOGP(DCC, LOGL_INFO, "sending STATUS (cause %d)\n", cause); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_STATUS; + + cause_ie = msgb_put(nmsg, 3); + cause_ie[0] = 2; + cause_ie[1] = GSM48_CAUSE_CS_GSM | GSM48_CAUSE_LOC_PRN_S_LU; + cause_ie[2] = 0x80 | cause; + + call_state_ie = msgb_put(nmsg, 1); + call_state_ie[0] = 0xc0 | trans->cc.state; + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* reply status enquiry */ +static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg) +{ + LOGP(DCC, LOGL_INFO, "received STATUS ENQUIREY\n"); + + return gsm48_cc_tx_status(trans, GSM48_CC_CAUSE_RESP_STATUS_INQ); +} + +static int gsm48_cc_rx_status(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct gsm_mncc_cause cause; + + if (payload_len < 1 || payload_len < gh->data[0] + 1) { + LOGP(DCC, LOGL_NOTICE, "Short read of status message " + "error.\n"); + return -EINVAL; + } + gsm48_decode_cause(&cause, gh->data); + + LOGP(DCC, LOGL_INFO, "received STATUS (cause %d)\n", cause.value); + + return 0; +} + +/* + * process handlers (mobile originating call establish) + */ + +/* on SETUP request from L4, init MM connection */ +static int gsm48_cc_init_mm(struct gsm_trans *trans, void *arg) +{ + struct msgb *nmsg; + struct gsm_mncc *data = arg; + struct gsm48_mmxx_hdr *nmmh; + + /* store setup message */ + memcpy(&trans->cc.msg, data, sizeof(struct gsm_mncc)); + + new_cc_state(trans, GSM_CSTATE_MM_CONNECTION_PEND); + + /* establish MM connection */ + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_EST_REQ, trans->callref, + trans->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + nmmh = (struct gsm48_mmxx_hdr *) nmsg->data; + if (data->emergency) + nmmh->emergency = 1; + LOGP(DCC, LOGL_INFO, "Sending MMCC_EST_REQ\n"); + return gsm48_mmxx_downmsg(trans->ms, nmsg); +} + +/* abort connection prior SETUP */ +static int gsm48_cc_abort_mm(struct gsm_trans *trans, void *arg) +{ + struct msgb *nmsg; + + /* abort MM connection */ + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_REL_REQ, trans->callref, + trans->transaction_id, 0); + if (!nmsg) + return -ENOMEM; + LOGP(DCC, LOGL_INFO, "Sending MMCC_REL_REQ\n"); + gsm48_mmxx_downmsg(trans->ms, nmsg); + + new_cc_state(trans, GSM_CSTATE_NULL); + + trans->callref = 0; + trans_free(trans); + + return 0; +} + +/* setup message from upper layer */ +static int gsm48_cc_tx_setup(struct gsm_trans *trans) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm_mncc *setup = &trans->cc.msg; + int rc, transaction_id; + uint8_t *ie; + + LOGP(DCC, LOGL_INFO, "sending SETUP\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + /* transaction id must not be assigned */ + if (trans->transaction_id != 0xff) { /* unasssigned */ + LOGP(DCC, LOGL_NOTICE, "TX Setup with assigned transaction. " + "This is not allowed!\n"); + /* Temporarily out of order */ + rc = mncc_release_ind(trans->ms, trans, trans->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_NORMAL_UNSPEC); + trans->callref = 0; + trans_free(trans); + return rc; + } + + /* Get free transaction_id */ + transaction_id = trans_assign_trans_id(trans->ms, GSM48_PDISC_CC, 0); + if (transaction_id < 0) { + /* no free transaction ID */ + rc = mncc_release_ind(trans->ms, trans, trans->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + trans->callref = 0; + trans_free(trans); + return rc; + } + trans->transaction_id = transaction_id; + + gh->msg_type = (setup->emergency) ? GSM48_MT_CC_EMERG_SETUP : + GSM48_MT_CC_SETUP; + + /* actually we have to start it when CM SERVICE REQUEST has been sent, + * but there is no primitive for that defined. i think it is ok to + * do it here rather than inventing MMCC-NOTIFY-IND. + */ + gsm48_start_cc_timer(trans, 0x303, GSM48_T303_MS); + + /* bearer capability (optional for emergency calls only) */ + if (setup->fields & MNCC_F_BEARER_CAP) + gsm48_encode_bearer_cap(nmsg, 0, &setup->bearer_cap); + if (!setup->emergency) { + /* facility */ + if (setup->fields & MNCC_F_FACILITY) + gsm48_encode_facility(nmsg, 0, &setup->facility); + /* called party BCD number */ + if (setup->fields & MNCC_F_CALLED) + gsm48_encode_called(nmsg, &setup->called); + /* user-user */ + if (setup->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(nmsg, 0, &setup->useruser); + /* ss version */ + if (setup->fields & MNCC_F_SSVERSION) + gsm48_encode_ssversion(nmsg, &setup->ssversion); + /* CLIR suppression */ + if (setup->clir.sup) { + ie = msgb_put(nmsg, 1); + ie[0] = GSM48_IE_CLIR_SUPP; + } + /* CLIR invocation */ + if (setup->clir.inv) { + ie = msgb_put(nmsg, 1); + ie[0] = GSM48_IE_CLIR_INVOC; + } + /* cc cap */ + if (setup->fields & MNCC_F_CCCAP) + gsm48_encode_cccap(nmsg, &setup->cccap); + } + + /* actually MM CONNECTION PENDING */ + new_cc_state(trans, GSM_CSTATE_INITIATED); + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* progress is received from lower layer */ +static int gsm48_cc_rx_progress(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc progress; + + LOGP(DCC, LOGL_INFO, "received PROGRESS\n"); + + memset(&progress, 0, sizeof(struct gsm_mncc)); + progress.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, + GSM48_IE_PROGR_IND, 0); + /* progress */ + if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) { + progress.fields |= MNCC_F_PROGRESS; + gsm48_decode_progress(&progress.progress, + TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1); + /* store last progress indicator */ + trans->cc.prog_ind = progress.progress.descr; + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + progress.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&progress.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + + return mncc_recvmsg(trans->ms, trans, MNCC_PROGRESS_IND, &progress); +} + +/* call proceeding is received from lower layer */ +static int gsm48_cc_rx_call_proceeding(struct gsm_trans *trans, + struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc call_proc; + + LOGP(DCC, LOGL_INFO, "sending CALL PROCEEDING\n"); + + gsm48_stop_cc_timer(trans); + + memset(&call_proc, 0, sizeof(struct gsm_mncc)); + call_proc.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); +#if 0 + /* repeat */ + if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_CIR)) + call_conf.repeat = 1; + if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_SEQ)) + call_conf.repeat = 2; +#endif + /* bearer capability */ + if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) { + call_proc.fields |= MNCC_F_BEARER_CAP; + gsm48_decode_bearer_cap(&call_proc.bearer_cap, + TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1); + } + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + call_proc.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&call_proc.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + + /* progress */ + if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) { + call_proc.fields |= MNCC_F_PROGRESS; + gsm48_decode_progress(&call_proc.progress, + TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1); + /* store last progress indicator */ + trans->cc.prog_ind = call_proc.progress.descr; + } + + /* start T310, if last progress indicator was 1 or 2 or 64 */ + if (trans->cc.prog_ind == 1 + || trans->cc.prog_ind == 2 + || trans->cc.prog_ind == 64) + gsm48_start_cc_timer(trans, 0x310, GSM48_T310_MS); + + new_cc_state(trans, GSM_CSTATE_MO_CALL_PROC); + + return mncc_recvmsg(trans->ms, trans, MNCC_CALL_PROC_IND, + &call_proc); +} + +/* alerting is received by the lower layer */ +static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc alerting; + + LOGP(DCC, LOGL_INFO, "received ALERTING\n"); + + gsm48_stop_cc_timer(trans); + /* no T301 in MS call control */ + + memset(&alerting, 0, sizeof(struct gsm_mncc)); + alerting.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + alerting.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&alerting.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* progress */ + if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) { + alerting.fields |= MNCC_F_PROGRESS; + gsm48_decode_progress(&alerting.progress, + TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1); + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + alerting.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&alerting.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + + new_cc_state(trans, GSM_CSTATE_CALL_DELIVERED); + + return mncc_recvmsg(trans->ms, trans, MNCC_ALERT_IND, + &alerting); +} + +/* connect is received from lower layer */ +static int gsm48_cc_rx_connect(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc connect; + + LOGP(DCC, LOGL_INFO, "received CONNECT\n"); + + gsm48_stop_cc_timer(trans); + + memset(&connect, 0, sizeof(struct gsm_mncc)); + connect.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + connect.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&connect.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* connected */ + if (TLVP_PRESENT(&tp, GSM48_IE_CONN_BCD)) { + connect.fields |= MNCC_F_CONNECTED; + gsm48_decode_connected(&connect.connected, + TLVP_VAL(&tp, GSM48_IE_CONN_BCD)-1); + } + /* progress */ + if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) { + connect.fields |= MNCC_F_PROGRESS; + gsm48_decode_progress(&connect.progress, + TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1); + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + connect.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&connect.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + + /* ACTIVE state is set during this: */ + gsm48_cc_tx_connect_ack(trans, NULL); + + return mncc_recvmsg(trans->ms, trans, MNCC_SETUP_CNF, &connect); +} + +/* connect ack message from upper layer */ +static int gsm48_cc_tx_connect_ack(struct gsm_trans *trans, void *arg) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending CONNECT ACKNOWLEDGE\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_CONNECT_ACK; + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* + * process handlers (mobile terminating call establish) + */ + +/* setup is received from lower layer */ +static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc setup; + + LOGP(DCC, LOGL_INFO, "received SETUP\n"); + + memset(&setup, 0, sizeof(struct gsm_mncc)); + setup.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + + /* bearer capability */ + if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) { + setup.fields |= MNCC_F_BEARER_CAP; + gsm48_decode_bearer_cap(&setup.bearer_cap, + TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1); + } + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + setup.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&setup.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* progress */ + if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) { + setup.fields |= MNCC_F_PROGRESS; + gsm48_decode_progress(&setup.progress, + TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1); + } + /* signal */ + if (TLVP_PRESENT(&tp, GSM48_IE_SIGNAL)) { + setup.fields |= MNCC_F_SIGNAL; + gsm48_decode_signal(&setup.signal, + TLVP_VAL(&tp, GSM48_IE_SIGNAL)-1); + } + /* calling party bcd number */ + if (TLVP_PRESENT(&tp, GSM48_IE_CALLING_BCD)) { + setup.fields |= MNCC_F_CALLING; + gsm48_decode_calling(&setup.calling, + TLVP_VAL(&tp, GSM48_IE_CALLING_BCD)-1); + } + /* called party bcd number */ + if (TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) { + setup.fields |= MNCC_F_CALLED; + gsm48_decode_called(&setup.called, + TLVP_VAL(&tp, GSM48_IE_CALLED_BCD)-1); + } + /* redirecting party bcd number */ + if (TLVP_PRESENT(&tp, GSM48_IE_REDIR_BCD)) { + setup.fields |= MNCC_F_REDIRECTING; + gsm48_decode_redirecting(&setup.redirecting, + TLVP_VAL(&tp, GSM48_IE_REDIR_BCD)-1); + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + setup.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&setup.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + + new_cc_state(trans, GSM_CSTATE_CALL_PRESENT); + + /* indicate setup to MNCC */ + mncc_recvmsg(trans->ms, trans, MNCC_SETUP_IND, &setup); + + return 0; +} + +/* call conf message from upper layer */ +static int gsm48_cc_tx_call_conf(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *confirm = arg; + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending CALL CONFIRMED (proceeding)\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_CALL_CONF; + + new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF); + + /* bearer capability */ + if (confirm->fields & MNCC_F_BEARER_CAP) + gsm48_encode_bearer_cap(nmsg, 0, &confirm->bearer_cap); + /* cause */ + if (confirm->fields & MNCC_F_CAUSE) + gsm48_encode_cause(nmsg, 0, &confirm->cause); + /* cc cap */ + if (confirm->fields & MNCC_F_CCCAP) + gsm48_encode_cccap(nmsg, &confirm->cccap); + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* alerting message from upper layer */ +static int gsm48_cc_tx_alerting(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *alerting = arg; + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending ALERTING\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_ALERTING; + + /* facility */ + if (alerting->fields & MNCC_F_FACILITY) + gsm48_encode_facility(nmsg, 0, &alerting->facility); + /* user-user */ + if (alerting->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(nmsg, 0, &alerting->useruser); + /* ss version */ + if (alerting->fields & MNCC_F_SSVERSION) + gsm48_encode_ssversion(nmsg, &alerting->ssversion); + + new_cc_state(trans, GSM_CSTATE_CALL_RECEIVED); + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* connect message from upper layer */ +static int gsm48_cc_tx_connect(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *connect = arg; + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending CONNECT\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_CONNECT; + + gsm48_stop_cc_timer(trans); + gsm48_start_cc_timer(trans, 0x313, GSM48_T313_MS); + + /* facility */ + if (connect->fields & MNCC_F_FACILITY) + gsm48_encode_facility(nmsg, 0, &connect->facility); + /* user-user */ + if (connect->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(nmsg, 0, &connect->useruser); + /* ss version */ + if (connect->fields & MNCC_F_SSVERSION) + gsm48_encode_ssversion(nmsg, &connect->ssversion); + + new_cc_state(trans, GSM_CSTATE_CONNECT_REQUEST); + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* connect ack is received from lower layer */ +static int gsm48_cc_rx_connect_ack(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm_mncc connect_ack; + + LOGP(DCC, LOGL_INFO, "received CONNECT ACKNOWLEDGE\n"); + + gsm48_stop_cc_timer(trans); + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + + memset(&connect_ack, 0, sizeof(struct gsm_mncc)); + connect_ack.callref = trans->callref; + return mncc_recvmsg(trans->ms, trans, MNCC_SETUP_COMPL_IND, + &connect_ack); +} + +/* + * process handlers (during active state) + */ + +/* notify message from upper layer */ +static int gsm48_cc_tx_notify(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *notify = arg; + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending NOTIFY\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_NOTIFY; + + /* notify */ + gsm48_encode_notify(nmsg, notify->notify); + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* notify is received from lower layer */ +static int gsm48_cc_rx_notify(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct gsm_mncc notify; + + LOGP(DCC, LOGL_INFO, "received NOTIFY\n"); + + memset(¬ify, 0, sizeof(struct gsm_mncc)); + notify.callref = trans->callref; + /* notify */ + if (payload_len < 1) { + LOGP(DCC, LOGL_NOTICE, "Short read of notify message error.\n"); + return -EINVAL; + } + gsm48_decode_notify(¬ify.notify, gh->data); + + return mncc_recvmsg(trans->ms, trans, MNCC_NOTIFY_IND, ¬ify); +} + +/* start dtmf message from upper layer */ +static int gsm48_cc_tx_start_dtmf(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *dtmf = arg; + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending START DTMF\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_START_DTMF; + + /* keypad */ + gsm48_encode_keypad(nmsg, dtmf->keypad); + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* start dtmf ack is received from lower layer */ +static int gsm48_cc_rx_start_dtmf_ack(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc dtmf; + + LOGP(DCC, LOGL_INFO, "received START DTMF ACKNOWLEDGE\n"); + + memset(&dtmf, 0, sizeof(struct gsm_mncc)); + dtmf.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* keypad facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_KPD_FACILITY)) { + dtmf.fields |= MNCC_F_KEYPAD; + gsm48_decode_keypad(&dtmf.keypad, + TLVP_VAL(&tp, GSM48_IE_KPD_FACILITY)-1); + } + + return mncc_recvmsg(trans->ms, trans, MNCC_START_DTMF_RSP, &dtmf); +} + +/* start dtmf rej is received from lower layer */ +static int gsm48_cc_rx_start_dtmf_rej(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct gsm_mncc dtmf; + + LOGP(DCC, LOGL_INFO, "received START DTMF REJECT\n"); + + memset(&dtmf, 0, sizeof(struct gsm_mncc)); + dtmf.callref = trans->callref; + /* cause */ + if (payload_len < 1) { + LOGP(DCC, LOGL_NOTICE, "Short read of dtmf reject message " + "error.\n"); + return -EINVAL; + } + gsm48_decode_cause(&dtmf.cause, gh->data); + + return mncc_recvmsg(trans->ms, trans, MNCC_START_DTMF_REJ, &dtmf); +} + +/* stop dtmf message from upper layer */ +static int gsm48_cc_tx_stop_dtmf(struct gsm_trans *trans, void *arg) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending STOP DTMF\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_STOP_DTMF; + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* stop dtmf ack is received from lower layer */ +static int gsm48_cc_rx_stop_dtmf_ack(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc dtmf; + + LOGP(DCC, LOGL_INFO, "received STOP DTMF ACKNOWLEDGE\n"); + + memset(&dtmf, 0, sizeof(struct gsm_mncc)); + dtmf.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + + return mncc_recvmsg(trans->ms, trans, MNCC_STOP_DTMF_RSP, &dtmf); +} + +/* hold message from upper layer */ +static int gsm48_cc_tx_hold(struct gsm_trans *trans, void *arg) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending HOLD\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_HOLD; + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* hold ack is received from lower layer */ +static int gsm48_cc_rx_hold_ack(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm_mncc hold; + + LOGP(DCC, LOGL_INFO, "received HOLD ACKNOWLEDGE\n"); + + memset(&hold, 0, sizeof(struct gsm_mncc)); + hold.callref = trans->callref; + + return mncc_recvmsg(trans->ms, trans, MNCC_HOLD_CNF, &hold); +} + +/* hold rej is received from lower layer */ +static int gsm48_cc_rx_hold_rej(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct gsm_mncc hold; + + LOGP(DCC, LOGL_INFO, "received HOLD REJECT\n"); + + memset(&hold, 0, sizeof(struct gsm_mncc)); + hold.callref = trans->callref; + /* cause */ + if (payload_len < 1) { + LOGP(DCC, LOGL_NOTICE, "Short read of hold reject message " + "error.\n"); + return -EINVAL; + } + gsm48_decode_cause(&hold.cause, gh->data); + + return mncc_recvmsg(trans->ms, trans, MNCC_HOLD_REJ, &hold); +} + +/* retrieve message from upper layer */ +static int gsm48_cc_tx_retrieve(struct gsm_trans *trans, void *arg) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending RETRIEVE\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_RETR; + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* retrieve ack is received from lower layer */ +static int gsm48_cc_rx_retrieve_ack(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm_mncc retrieve; + + LOGP(DCC, LOGL_INFO, "received RETRIEVE ACKNOWLEDGE\n"); + + memset(&retrieve, 0, sizeof(struct gsm_mncc)); + retrieve.callref = trans->callref; + + return mncc_recvmsg(trans->ms, trans, MNCC_RETRIEVE_CNF, &retrieve); +} + +/* retrieve rej is received from lower layer */ +static int gsm48_cc_rx_retrieve_rej(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct gsm_mncc retrieve; + + LOGP(DCC, LOGL_INFO, "received RETRIEVE REJECT\n"); + + memset(&retrieve, 0, sizeof(struct gsm_mncc)); + retrieve.callref = trans->callref; + /* cause */ + if (payload_len < 1) { + LOGP(DCC, LOGL_NOTICE, "Short read of retrieve reject message " + "error.\n"); + return -EINVAL; + } + gsm48_decode_cause(&retrieve.cause, gh->data); + + return mncc_recvmsg(trans->ms, trans, MNCC_RETRIEVE_REJ, &retrieve); +} + +/* facility message from upper layer or from timer event */ +static int gsm48_cc_tx_facility(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *fac = arg; + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending FACILITY\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_FACILITY; + + /* facility */ + gsm48_encode_facility(nmsg, 1, &fac->facility); + /* ss version */ + if (fac->fields & MNCC_F_SSVERSION) + gsm48_encode_ssversion(nmsg, &fac->ssversion); + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* facility is received from lower layer */ +static int gsm48_cc_rx_facility(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct gsm_mncc fac; + + LOGP(DCC, LOGL_INFO, "received FACILITY\n"); + + memset(&fac, 0, sizeof(struct gsm_mncc)); + fac.callref = trans->callref; + if (payload_len < 1) { + LOGP(DCC, LOGL_NOTICE, "Short read of facility message " + "error.\n"); + return -EINVAL; + } + /* facility */ + gsm48_decode_facility(&fac.facility, gh->data); + + return mncc_recvmsg(trans->ms, trans, MNCC_FACILITY_IND, &fac); +} + +/* user info message from upper layer or from timer event */ +static int gsm48_cc_tx_userinfo(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *user = arg; + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending USERINFO\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_USER_INFO; + + /* user-user */ + if (user->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(nmsg, 1, &user->useruser); + /* more data */ + if (user->more) + gsm48_encode_more(nmsg); + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* user info is received from lower layer */ +static int gsm48_cc_rx_userinfo(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc user; + + LOGP(DCC, LOGL_INFO, "received USERINFO\n"); + + memset(&user, 0, sizeof(struct gsm_mncc)); + user.callref = trans->callref; + if (payload_len < 1) { + LOGP(DCC, LOGL_NOTICE, "Short read of userinfo message " + "error.\n"); + return -EINVAL; + } + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, + GSM48_IE_USER_USER, 0); + /* user-user */ + gsm48_decode_useruser(&user.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + /* more data */ + if (TLVP_PRESENT(&tp, GSM48_IE_MORE_DATA)) + user.more = 1; + + return mncc_recvmsg(trans->ms, trans, MNCC_USERINFO_IND, &user); +} + +/* modify message from upper layer or from timer event */ +static int gsm48_cc_tx_modify(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *modify = arg; + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending MODIFY\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_MODIFY; + + gsm48_start_cc_timer(trans, 0x323, GSM48_T323_MS); + + /* bearer capability */ + gsm48_encode_bearer_cap(nmsg, 1, &modify->bearer_cap); + + new_cc_state(trans, GSM_CSTATE_MO_TERM_MODIFY); + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* modify complete is received from lower layer */ +static int gsm48_cc_rx_modify_complete(struct gsm_trans *trans, + struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct gsm_mncc modify; + + LOGP(DCC, LOGL_INFO, "received MODIFY COMPLETE\n"); + + gsm48_stop_cc_timer(trans); + + memset(&modify, 0, sizeof(struct gsm_mncc)); + modify.callref = trans->callref; + if (payload_len < 1) { + LOGP(DCC, LOGL_NOTICE, "Short read of modify complete message " + "error.\n"); + return -EINVAL; + } + /* bearer capability */ + gsm48_decode_bearer_cap(&modify.bearer_cap, gh->data); + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + + return mncc_recvmsg(trans->ms, trans, MNCC_MODIFY_CNF, &modify); +} + +/* modify reject is received from lower layer */ +static int gsm48_cc_rx_modify_reject(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc modify; + + LOGP(DCC, LOGL_INFO, "received MODIFY REJECT\n"); + + gsm48_stop_cc_timer(trans); + + memset(&modify, 0, sizeof(struct gsm_mncc)); + modify.callref = trans->callref; + if (payload_len < 1) { + LOGP(DCC, LOGL_NOTICE, "Short read of modify reject message " + "error.\n"); + return -EINVAL; + } + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, + GSM48_IE_BEARER_CAP, GSM48_IE_CAUSE); + /* bearer capability */ + if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) { + modify.fields |= MNCC_F_BEARER_CAP; + gsm48_decode_bearer_cap(&modify.bearer_cap, + TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1); + } + /* cause */ + if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { + modify.fields |= MNCC_F_CAUSE; + gsm48_decode_cause(&modify.cause, + TLVP_VAL(&tp, GSM48_IE_CAUSE)-1); + } + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + + return mncc_recvmsg(trans->ms, trans, MNCC_MODIFY_REJ, &modify); +} + +/* modify is received from lower layer */ +static int gsm48_cc_rx_modify(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct gsm_mncc modify; + + LOGP(DCC, LOGL_INFO, "received MODIFY\n"); + + memset(&modify, 0, sizeof(struct gsm_mncc)); + modify.callref = trans->callref; + if (payload_len < 1) { + LOGP(DCC, LOGL_NOTICE, "Short read of modify message error.\n"); + return -EINVAL; + } + /* bearer capability */ + gsm48_decode_bearer_cap(&modify.bearer_cap, gh->data); + + new_cc_state(trans, GSM_CSTATE_MO_ORIG_MODIFY); + + return mncc_recvmsg(trans->ms, trans, MNCC_MODIFY_IND, &modify); +} + +/* modify complete message from upper layer or from timer event */ +static int gsm48_cc_tx_modify_complete(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *modify = arg; + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending MODIFY COMPLETE\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_MODIFY_COMPL; + + /* bearer capability */ + gsm48_encode_bearer_cap(nmsg, 1, &modify->bearer_cap); + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* modify reject message from upper layer or from timer event */ +static int gsm48_cc_tx_modify_reject(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *modify = arg; + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending MODIFY REJECT\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_MODIFY_REJECT; + + /* bearer capability */ + gsm48_encode_bearer_cap(nmsg, 1, &modify->bearer_cap); + /* cause */ + gsm48_encode_cause(nmsg, 1, &modify->cause); + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* + * process handlers (call clearing) + */ + +static struct gsm_mncc_cause default_cause = { + .location = GSM48_CAUSE_LOC_PRN_S_LU, + .coding = 0, + .rec = 0, + .rec_val = 0, + .value = GSM48_CC_CAUSE_NORMAL_UNSPEC, + .diag_len = 0, + .diag = { 0 }, +}; + +/* disconnect message from upper layer or from timer event */ +static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *disc = arg; + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending DISCONNECT\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_DISCONNECT; + + gsm48_stop_cc_timer(trans); + gsm48_start_cc_timer(trans, 0x305, GSM48_T305_MS); + + /* cause */ + if (disc->fields & MNCC_F_CAUSE) + gsm48_encode_cause(nmsg, 1, &disc->cause); + else + gsm48_encode_cause(nmsg, 1, &default_cause); + + /* facility */ + if (disc->fields & MNCC_F_FACILITY) + gsm48_encode_facility(nmsg, 0, &disc->facility); + /* progress */ + if (disc->fields & MNCC_F_PROGRESS) + gsm48_encode_progress(nmsg, 0, &disc->progress); + /* user-user */ + if (disc->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(nmsg, 0, &disc->useruser); + /* ss version */ + if (disc->fields & MNCC_F_SSVERSION) + gsm48_encode_ssversion(nmsg, &disc->ssversion); + + new_cc_state(trans, GSM_CSTATE_DISCONNECT_REQ); + + return gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); +} + +/* release message from upper layer or from timer event */ +static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *rel = arg; + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending RELEASE\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_RELEASE; + + gsm48_stop_cc_timer(trans); + gsm48_start_cc_timer(trans, 0x308, GSM48_T308_MS); + + /* cause */ + if (rel->fields & MNCC_F_CAUSE) + gsm48_encode_cause(nmsg, 0, &rel->cause); + /* facility */ + if (rel->fields & MNCC_F_FACILITY) + gsm48_encode_facility(nmsg, 0, &rel->facility); + /* user-user */ + if (rel->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(nmsg, 0, &rel->useruser); + /* ss version */ + if (rel->fields & MNCC_F_SSVERSION) + gsm48_encode_ssversion(nmsg, &rel->ssversion); + + trans->cc.T308_second = 0; + memcpy(&trans->cc.msg, rel, sizeof(struct gsm_mncc)); + + if (trans->cc.state != GSM_CSTATE_RELEASE_REQ) + new_cc_state(trans, GSM_CSTATE_RELEASE_REQ); + + gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); + +#if 0 + /* release without sending MMCC_REL_REQ */ + new_cc_state(trans, GSM_CSTATE_NULL); + trans->callref = 0; + trans_free(trans); +#endif + + return 0; +} + +/* reject message from upper layer */ +static int gsm48_cc_tx_release_compl(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *rel = arg; + struct msgb *nmsg; + struct gsm48_hdr *gh; + + LOGP(DCC, LOGL_INFO, "sending RELEASE COMPLETE\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_RELEASE_COMPL; + + gsm48_stop_cc_timer(trans); + + /* cause */ + if (rel->fields & MNCC_F_CAUSE) + gsm48_encode_cause(nmsg, 0, &rel->cause); + /* facility */ + if (rel->fields & MNCC_F_FACILITY) + gsm48_encode_facility(nmsg, 0, &rel->facility); + /* user-user */ + if (rel->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(nmsg, 0, &rel->useruser); + /* ss version */ + if (rel->fields & MNCC_F_SSVERSION) + gsm48_encode_ssversion(nmsg, &rel->ssversion); + + /* release without sending MMCC_REL_REQ */ + new_cc_state(trans, GSM_CSTATE_NULL); + trans->callref = 0; + trans_free(trans); + + return 0; +} + +/* disconnect is received from lower layer */ +static int gsm48_cc_rx_disconnect(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc disc; + + LOGP(DCC, LOGL_INFO, "received DISCONNECT\n"); + + gsm48_stop_cc_timer(trans); + + new_cc_state(trans, GSM_CSTATE_DISCONNECT_IND); + + memset(&disc, 0, sizeof(struct gsm_mncc)); + disc.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, + GSM48_IE_CAUSE, 0); + /* cause */ + if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { + disc.fields |= MNCC_F_CAUSE; + gsm48_decode_cause(&disc.cause, + TLVP_VAL(&tp, GSM48_IE_CAUSE)-1); + } + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + disc.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&disc.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* progress */ + if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) { + disc.fields |= MNCC_F_PROGRESS; + gsm48_decode_progress(&disc.progress, + TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1); + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + disc.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&disc.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + + /* store disconnect cause for T305 expiry */ + memcpy(&trans->cc.msg, &disc, sizeof(struct gsm_mncc)); + + return mncc_recvmsg(trans->ms, trans, MNCC_DISC_IND, &disc); +} + +/* release is received from lower layer */ +static int gsm48_cc_rx_release(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc rel; + + LOGP(DCC, LOGL_INFO, "received RELEASE\n"); + + gsm48_stop_cc_timer(trans); + + memset(&rel, 0, sizeof(struct gsm_mncc)); + rel.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* cause */ + if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { + rel.fields |= MNCC_F_CAUSE; + gsm48_decode_cause(&rel.cause, + TLVP_VAL(&tp, GSM48_IE_CAUSE)-1); + } + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + rel.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&rel.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + rel.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&rel.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + + /* in case we receive a relase, when we are already in NULL state */ + if (trans->cc.state == GSM_CSTATE_NULL) { + LOGP(DCC, LOGL_INFO, "ignoring RELEASE in NULL state\n"); + /* release MM conn, free trans */ + return gsm48_rel_null_free(trans); + } + if (trans->cc.state == GSM_CSTATE_RELEASE_REQ) { + /* release collision 5.4.5 */ + mncc_recvmsg(trans->ms, trans, MNCC_REL_CNF, &rel); + } else { + struct msgb *nmsg; + + /* forward cause only */ + LOGP(DCC, LOGL_INFO, "sending RELEASE COMPLETE\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_RELEASE_COMPL; + + if (rel.fields & MNCC_F_CAUSE) + gsm48_encode_cause(nmsg, 0, &rel.cause); + + gsm48_cc_to_mm(nmsg, trans, GSM48_MMCC_DATA_REQ); + + /* release indication */ + mncc_recvmsg(trans->ms, trans, MNCC_REL_IND, &rel); + } + + /* release MM conn, got NULL state, free trans */ + return gsm48_rel_null_free(trans); +} + +/* release complete is received from lower layer */ +static int gsm48_cc_rx_release_compl(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc rel; + + LOGP(DCC, LOGL_INFO, "received RELEASE COMPLETE\n"); + + gsm48_stop_cc_timer(trans); + + memset(&rel, 0, sizeof(struct gsm_mncc)); + rel.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* cause */ + if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { + rel.fields |= MNCC_F_CAUSE; + gsm48_decode_cause(&rel.cause, + TLVP_VAL(&tp, GSM48_IE_CAUSE)-1); + } + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + rel.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&rel.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + rel.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&rel.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + + if (trans->callref) { + switch (trans->cc.state) { + case GSM_CSTATE_CALL_PRESENT: + mncc_recvmsg(trans->ms, trans, + MNCC_REJ_IND, &rel); + break; + case GSM_CSTATE_RELEASE_REQ: + mncc_recvmsg(trans->ms, trans, + MNCC_REL_CNF, &rel); + break; + default: + mncc_recvmsg(trans->ms, trans, + MNCC_REL_IND, &rel); + } + } + + /* release MM conn, got NULL state, free trans */ + return gsm48_rel_null_free(trans); +} + +/* + * state machines + */ + +/* state trasitions for MNCC messages (upper layer) */ +static struct downstate { + uint32_t states; + int type; + int (*rout) (struct gsm_trans *trans, void *arg); +} downstatelist[] = { + /* mobile originating call establishment */ + {SBIT(GSM_CSTATE_NULL), /* 5.2.1 */ + MNCC_SETUP_REQ, gsm48_cc_init_mm}, + + {SBIT(GSM_CSTATE_MM_CONNECTION_PEND), /* 5.2.1 */ + MNCC_REL_REQ, gsm48_cc_abort_mm}, + + /* mobile terminating call establishment */ + {SBIT(GSM_CSTATE_CALL_PRESENT), /* 5.2.2.3.1 */ + MNCC_CALL_CONF_REQ, gsm48_cc_tx_call_conf}, + + {SBIT(GSM_CSTATE_MO_TERM_CALL_CONF), /* 5.2.2.3.2 */ + MNCC_ALERT_REQ, gsm48_cc_tx_alerting}, + + {SBIT(GSM_CSTATE_MO_TERM_CALL_CONF) | + SBIT(GSM_CSTATE_CALL_RECEIVED), /* 5.2.2.5 */ + MNCC_SETUP_RSP, gsm48_cc_tx_connect}, + + /* signalling during call */ + {SBIT(GSM_CSTATE_ACTIVE), /* 5.3.1 */ + MNCC_NOTIFY_REQ, gsm48_cc_tx_notify}, + + {ALL_STATES, /* 5.5.7.1 */ + MNCC_START_DTMF_REQ, gsm48_cc_tx_start_dtmf}, + + {ALL_STATES, /* 5.5.7.3 */ + MNCC_STOP_DTMF_REQ, gsm48_cc_tx_stop_dtmf}, + + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_HOLD_REQ, gsm48_cc_tx_hold}, + + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_RETRIEVE_REQ, gsm48_cc_tx_retrieve}, + + {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), + MNCC_FACILITY_REQ, gsm48_cc_tx_facility}, + + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_USERINFO_REQ, gsm48_cc_tx_userinfo}, + + /* clearing */ + {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_DISCONNECT_IND) - + SBIT(GSM_CSTATE_RELEASE_REQ) - + SBIT(GSM_CSTATE_DISCONNECT_REQ), /* 5.4.3.1 */ + MNCC_DISC_REQ, gsm48_cc_tx_disconnect}, + + {SBIT(GSM_CSTATE_INITIATED), + MNCC_REJ_REQ, gsm48_cc_tx_release_compl}, + + {ALL_STATES - SBIT(GSM_CSTATE_NULL) - + SBIT(GSM_CSTATE_RELEASE_REQ), /* ??? */ + MNCC_REL_REQ, gsm48_cc_tx_release}, + + /* modify */ + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_MODIFY_REQ, gsm48_cc_tx_modify}, + + {SBIT(GSM_CSTATE_MO_ORIG_MODIFY), + MNCC_MODIFY_RSP, gsm48_cc_tx_modify_complete}, + + {SBIT(GSM_CSTATE_MO_ORIG_MODIFY), + MNCC_MODIFY_REJ, gsm48_cc_tx_modify_reject}, +}; + +#define DOWNSLLEN \ + (sizeof(downstatelist) / sizeof(struct downstate)) + +int mncc_tx_to_cc(void *inst, int msg_type, void *arg) +{ + struct osmocom_ms *ms = (struct osmocom_ms *) inst; + struct gsm_mncc *data = arg; + struct gsm_trans *trans; + int i, rc; + + if (!ms->started || ms->shutdown) { + LOGP(DCC, LOGL_NOTICE, "Phone is down!\n"); + if (ms->mncc_entity.mncc_recv && msg_type != MNCC_REL_REQ) { + struct gsm_mncc rel; + + memset(&rel, 0, sizeof(rel)); + rel.callref = data->callref; + mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_DEST_OOO); + ms->mncc_entity.mncc_recv(ms, MNCC_REL_IND, &rel); + } + return -EBUSY; + } + + data->msg_type = msg_type; + + /* Find callref */ + trans = trans_find_by_callref(ms, data->callref); + + if (!trans) { + /* check for SETUP message */ + if (msg_type != MNCC_SETUP_REQ) { + /* Invalid call reference */ + LOGP(DCC, LOGL_NOTICE, "transaction not found\n"); + return mncc_release_ind(ms, NULL, data->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_INVAL_TRANS_ID); + } + if (data->callref >= 0x40000000) { + LOGP(DCC, LOGL_FATAL, "MNCC ref wrong.\n"); + return mncc_release_ind(ms, NULL, data->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_INVAL_TRANS_ID); + } + + /* Create transaction */ + trans = trans_alloc(ms, GSM48_PDISC_CC, 0xff, data->callref); + if (!trans) { + /* No memory or whatever */ + return mncc_release_ind(ms, NULL, data->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + } + } + + switch (msg_type) { + case GSM_TCHF_FRAME: + return gsm_send_voice(ms, arg); + case MNCC_LCHAN_MODIFY: + return 0; + case MNCC_FRAME_RECV: + ms->mncc_entity.ref = trans->callref; + gsm48_rr_audio_mode(ms, + AUDIO_TX_TRAFFIC_REQ | AUDIO_RX_TRAFFIC_IND); + return 0; + case MNCC_FRAME_DROP: + if (ms->mncc_entity.ref == trans->callref) + ms->mncc_entity.ref = 0; + gsm48_rr_audio_mode(ms, AUDIO_TX_MICROPHONE | AUDIO_RX_SPEAKER); + return 0; + } + + /* Find function for current state and message */ + for (i = 0; i < DOWNSLLEN; i++) + if ((msg_type == downstatelist[i].type) + && ((1 << trans->cc.state) & downstatelist[i].states)) + break; + if (i == DOWNSLLEN) { + LOGP(DCC, LOGL_NOTICE, "Message %d unhandled at state %d\n", + msg_type, trans->cc.state); + return 0; + } + + rc = downstatelist[i].rout(trans, arg); + + return rc; +} + +/* state trasitions for call control messages (lower layer) */ +static struct datastate { + uint32_t states; + int type; + int (*rout) (struct gsm_trans *trans, struct msgb *msg); +} datastatelist[] = { + /* mobile originating call establishment */ + {SBIT(GSM_CSTATE_INITIATED), /* 5.2.1.3 */ + GSM48_MT_CC_CALL_PROC, gsm48_cc_rx_call_proceeding}, + + {SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC) | + SBIT(GSM_CSTATE_CALL_DELIVERED), /* 5.2.1.4.1 */ + GSM48_MT_CC_PROGRESS, gsm48_cc_rx_progress}, + + {SBIT(GSM_CSTATE_INITIATED) | + SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.5 */ + GSM48_MT_CC_ALERTING, gsm48_cc_rx_alerting}, + + {SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC) | + SBIT(GSM_CSTATE_CALL_DELIVERED), /* 5.2.1.6 */ + GSM48_MT_CC_CONNECT, gsm48_cc_rx_connect}, + + /* mobile terminating call establishment */ + {SBIT(GSM_CSTATE_NULL), /* 5.2.2.1 */ + GSM48_MT_CC_SETUP, gsm48_cc_rx_setup}, + + {SBIT(GSM_CSTATE_CONNECT_REQUEST), /* 5.2.2.6 */ + GSM48_MT_CC_CONNECT_ACK, gsm48_cc_rx_connect_ack}, + + /* signalling during call */ + {SBIT(GSM_CSTATE_ACTIVE), /* 5.3.1 */ + GSM48_MT_CC_NOTIFY, gsm48_cc_rx_notify}, + + {ALL_STATES, /* 8.4 */ + GSM48_MT_CC_STATUS_ENQ, gsm48_cc_rx_status_enq}, + + {ALL_STATES, + GSM48_MT_CC_STATUS, gsm48_cc_rx_status}, + + {ALL_STATES, /* 5.5.7.2 */ + GSM48_MT_CC_START_DTMF_ACK, gsm48_cc_rx_start_dtmf_ack}, + + {ALL_STATES, /* 5.5.7.2 */ + GSM48_MT_CC_START_DTMF_REJ, gsm48_cc_rx_start_dtmf_rej}, + + {ALL_STATES, /* 5.5.7.4 */ + GSM48_MT_CC_STOP_DTMF_ACK, gsm48_cc_rx_stop_dtmf_ack}, + + {SBIT(GSM_CSTATE_ACTIVE), + GSM48_MT_CC_HOLD_ACK, gsm48_cc_rx_hold_ack}, + + {SBIT(GSM_CSTATE_ACTIVE), + GSM48_MT_CC_HOLD_REJ, gsm48_cc_rx_hold_rej}, + + {SBIT(GSM_CSTATE_ACTIVE), + GSM48_MT_CC_RETR_ACK, gsm48_cc_rx_retrieve_ack}, + + {SBIT(GSM_CSTATE_ACTIVE), + GSM48_MT_CC_RETR_REJ, gsm48_cc_rx_retrieve_rej}, + + {ALL_STATES - SBIT(GSM_CSTATE_NULL), + GSM48_MT_CC_FACILITY, gsm48_cc_rx_facility}, + + {SBIT(GSM_CSTATE_ACTIVE), + GSM48_MT_CC_USER_INFO, gsm48_cc_rx_userinfo}, + + /* clearing */ + {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ) - + SBIT(GSM_CSTATE_DISCONNECT_IND), /* 5.4.4.1.1 */ + GSM48_MT_CC_DISCONNECT, gsm48_cc_rx_disconnect}, + + {ALL_STATES, /* 5.4.3.3 & 5.4.5!!!*/ + GSM48_MT_CC_RELEASE, gsm48_cc_rx_release}, + + {ALL_STATES, /* 5.4.4.1.3 */ + GSM48_MT_CC_RELEASE_COMPL, gsm48_cc_rx_release_compl}, + + /* modify */ + {SBIT(GSM_CSTATE_ACTIVE), + GSM48_MT_CC_MODIFY, gsm48_cc_rx_modify}, + + {SBIT(GSM_CSTATE_MO_TERM_MODIFY), + GSM48_MT_CC_MODIFY_COMPL, gsm48_cc_rx_modify_complete}, + + {SBIT(GSM_CSTATE_MO_TERM_MODIFY), + GSM48_MT_CC_MODIFY_REJECT, gsm48_cc_rx_modify_reject}, +}; + +#define DATASLLEN \ + (sizeof(datastatelist) / sizeof(struct datastate)) + +static int gsm48_cc_data_ind(struct gsm_trans *trans, struct msgb *msg) +{ + struct osmocom_ms *ms = trans->ms; + struct gsm48_hdr *gh = msgb_l3(msg); + int msg_type = gh->msg_type & 0xbf; + uint8_t transaction_id = ((gh->proto_discr & 0xf0) ^ 0x80) >> 4; + /* flip */ + int msg_supported = 0; /* determine, if message is supported at all */ + int i, rc; + + /* set transaction ID, if not already */ + trans->transaction_id = transaction_id; + + /* pull the MMCC header */ + msgb_pull(msg, sizeof(struct gsm48_mmxx_hdr)); + + LOGP(DCC, LOGL_INFO, "(ms %s) Received '%s' in CC state %s\n", ms->name, + gsm48_cc_msg_name(msg_type), + gsm48_cc_state_name(trans->cc.state)); + + /* find function for current state and message */ + for (i = 0; i < DATASLLEN; i++) { + if (msg_type == datastatelist[i].type) + msg_supported = 1; + if ((msg_type == datastatelist[i].type) + && ((1 << trans->cc.state) & datastatelist[i].states)) + break; + } + if (i == DATASLLEN) { + if (msg_supported) { + LOGP(DCC, LOGL_NOTICE, "Message unhandled at this " + "state.\n"); + return gsm48_cc_tx_status(trans, + GSM48_REJECT_MSG_TYPE_NOT_COMPATIBLE); + } else { + LOGP(DCC, LOGL_NOTICE, "Message not supported.\n"); + return gsm48_cc_tx_status(trans, + GSM48_REJECT_MSG_TYPE_NOT_IMPLEMENTED); + } + } + + rc = datastatelist[i].rout(trans, msg); + + return rc; +} + +/* receive message from MM layer */ +int gsm48_rcv_cc(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + int msg_type = mmh->msg_type; + struct gsm_trans *trans; + int rc = 0; + + trans = trans_find_by_callref(ms, mmh->ref); + if (!trans) { + trans = trans_alloc(ms, GSM48_PDISC_CC, mmh->transaction_id, + mmh->ref); + if (!trans) + return -ENOMEM; + } + + LOGP(DCC, LOGL_INFO, "(ms %s) Received '%s' in CC state %s\n", ms->name, + get_mmxx_name(msg_type), + gsm48_cc_state_name(trans->cc.state)); + + switch (msg_type) { + case GSM48_MMCC_EST_IND: + /* data included */ + rc = gsm48_cc_data_ind(trans, msg); + break; + case GSM48_MMCC_EST_CNF: + /* send setup after confirm */ + if (trans->cc.state == GSM_CSTATE_MM_CONNECTION_PEND) + rc = gsm48_cc_tx_setup(trans); + else + LOGP(DCC, LOGL_ERROR, "Oops, MMCC-EST-CONF in state " + "%d?\n", trans->cc.state); + break; + case GSM48_MMCC_ERR_IND: /* no supporting re-establishment */ + case GSM48_MMCC_REL_IND: + /* release L4, release transaction */ + mncc_release_ind(trans->ms, trans, trans->callref, + GSM48_CAUSE_LOC_PRN_S_LU, mmh->cause); + /* release without sending MMCC_REL_REQ */ + new_cc_state(trans, GSM_CSTATE_NULL); + trans->callref = 0; + trans_free(trans); + break; + case GSM48_MMCC_DATA_IND: + rc = gsm48_cc_data_ind(trans, msg); + break; + case GSM48_MMCC_UNIT_DATA_IND: + break; + case GSM48_MMCC_SYNC_IND: + break; + default: + LOGP(DCC, LOGL_NOTICE, "Message unhandled.\n"); + rc = -ENOTSUP; + } + + return rc; +} + +int mncc_clear_trans(void *inst, uint8_t protocol) +{ + struct osmocom_ms *ms = (struct osmocom_ms *) inst; + struct gsm_mncc rel; + struct gsm_trans *trans, *trans2; + + memset(&rel, 0, sizeof(struct gsm_mncc)); + + /* safe, in case the release process will destroy transaction node */ + llist_for_each_entry_safe(trans, trans2, &ms->trans_list, entry) { + if (trans->protocol == protocol) { + LOGP(DCC, LOGL_NOTICE, "Release CC-transaction.\n"); + rel.callref = trans->callref; + mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_TEMP_FAILURE); + mncc_tx_to_cc(ms, MNCC_REL_REQ, &rel); + } + } + + return 0; + +} + diff --git a/src/host/layer23/src/mobile/gsm48_mm.c b/src/host/layer23/src/mobile/gsm48_mm.c new file mode 100644 index 00000000..331cfe3b --- /dev/null +++ b/src/host/layer23/src/mobile/gsm48_mm.c @@ -0,0 +1,4394 @@ +/* + * (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdint.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <arpa/inet.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm48.h> +#include <osmocom/core/talloc.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/networks.h> +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/mobile/gsm48_cc.h> +#include <osmocom/bb/mobile/gsm480_ss.h> +#include <osmocom/bb/mobile/gsm411_sms.h> +#include <osmocom/bb/mobile/app_mobile.h> +#include <osmocom/bb/mobile/vty.h> + +extern void *l23_ctx; + +void mm_conn_free(struct gsm48_mm_conn *conn); +static int gsm48_rcv_rr(struct osmocom_ms *ms, struct msgb *msg); +static int gsm48_rcv_mmr(struct osmocom_ms *ms, struct msgb *msg); +static int gsm48_mm_ev(struct osmocom_ms *ms, int msg_type, struct msgb *msg); +static int gsm48_mm_tx_id_rsp(struct osmocom_ms *ms, uint8_t mi_type); +static int gsm48_mm_tx_loc_upd_req(struct osmocom_ms *ms); +static int gsm48_mm_loc_upd_failed(struct osmocom_ms *ms, struct msgb *msg); +static int gsm48_mm_conn_go_dedic(struct osmocom_ms *ms); +static int gsm48_mm_init_mm_reject(struct osmocom_ms *ms, struct msgb *msg); +static int gsm48_mm_data_ind(struct osmocom_ms *ms, struct msgb *msg); +static void new_mm_state(struct gsm48_mmlayer *mm, int state, int substate); +static int gsm48_mm_loc_upd_normal(struct osmocom_ms *ms, struct msgb *msg); +static int gsm48_mm_loc_upd_periodic(struct osmocom_ms *ms, struct msgb *msg); +static int gsm48_mm_loc_upd(struct osmocom_ms *ms, struct msgb *msg); + +/* + * notes + */ + +/* + * Notes on IMSI detach procedure: + * + * At the end of the procedure, the state of MM, RR, cell selection: No SIM. + * + * In MM IDLE state, cell available: RR is establised, IMSI detach specific + * procedure is performed. + * + * In MM IDLE state, no cell: State is silently changed to No SIM. + * + * During any MM connection state, or Wait for network command: All MM + * connections (if any) are released locally, and IMSI detach specific + * procedure is performed. + * + * During IMSI detach processing: Request of IMSI detach is ignored. + * + * Any other state: The special 'delay_detach' flag is set only. If set, at any + * state transition we will clear the flag and restart the procedure again. + * + * The procedure is not spec conform, but always succeeds. + * + */ + +/* Notes on Service states: + * + * There are two PLMN search states: + * + * - PLMN SEARCH NORMAL + * - PLMN SEARCH + * + * They are entered, if: (4.2.1.2) + * - ME is switched on + * - SIM is inserted + * - user has asked PLMN selection in certain Service states + * - coverage is lost in certain Service states + * - roaming is denied + * - (optionally see 4.2.1.2) + * + * PLMN SEARCH NORMAL state is then entered, if all these conditions are met: + * - SIM is valid + * - SIM state is U1 + * - SIM LAI valid + * - cell selected + * - cell == SIM LAI + * + * Otherwhise PLMN SEARCH is entered. + * + * During PLMN SEARCH NORMAL state: (4.2.2.5) + * - on expirery of T3212: Perform periodic location update, when back + * to NORMAL SERVICE state. + * - perform IMSI detach + * - perform MM connections + * - respond to paging (if possible) + * + * During PLMN SEARCH state: (4.2.2.6) + * - reject MM connection except for emergency calls + * + * + * The NO CELL AVAILABLE state is entered, if: + * - no cell found during PLMN search + * + * During NO CELL AVAILABLE state: + * - reject any MM connection + * + * The NO IMSI state is entered if: + * - SIM is invalid + * - and cell is selected during PLMN SEARCH states + * + * During NO IMSO state: (4.2.2.4) + * - reject MM connection except for emergency calls + * + * The LIMITED SERVICE state is entered if: + * - SIM is valid + * - and SIM state is U3 + * - and cell is selected + * + * During LIMITED SERVICE state: (4.2.2.3) + * - reject MM connection except for emergency calls + * - perform location update, if new LAI is entered + * + * + * The LOCATION UPDATE NEEDED state is entered if: + * - SIM is valid + * - and location update must be performed for any reason + * + * During LOCATION UPDATE NEEDED state: + * - reject MM connection except for emergency calls + * + * In all IDLE states: + * - on expirery of T3211 or T3213: Perform location update, when back + * to NORMAL SERVICE state. + * + * This state is left if location update is possible and directly enter + * state ATTEMPTING TO UPDATE and trigger location update. + * The function gsm48_mm_loc_upd_possible() is used to check this on state + * change. + * + * + * The ATTEMPTING TO UPDATE state is entered if: + * - SIM is valid + * - and SIM state is U2 + * - and cell is selected + * + * During ATTEMPTING TO UPDATE state: (4.2.2.2) + * - on expirery of T3211 or T3213: Perform location updated + * - on expirery of T3212: Perform location updated + * - on change of LAI: Perform location update + * - (abnormal cases unsupported) + * - accept MM connection for emergency calls + * - trigger location update on any other MM connection + * - respond to paging (with IMSI only, because in U2 TMSI is not valid) + * + * + * The NORMAL SERVICE state is entered if: + * - SIM is valid + * - and SIM state is U1 + * - and cell is selected + * - and SIM LAI == cell + * + * During NORMAL SERVICE state: (4.2.2.1) + * - on expirery of T3211 or T3213: Perform location updated + * - on expirery of T3212: Perform location updated + * - on change of LAI: Perform location update + * - perform IMSI detach + * - perform MM connections + * - respond to paging + * + * + * gsm48_mm_set_plmn_search() is used enter PLMN SEARCH or PLMN SEARCH NORMAL + * state. Depending on the conditions above, the appropiate state is selected. + * + * + * gsm48_mm_return_idle() is used to select the Service state when returning + * to MM IDLE state after cell reselection. + * + * + * If cell selection process indicates NO_CELL_FOUND: + * + * - NO CELL AVAILABLE state is entered, if not already. + * + * gsm48_mm_no_cell_found() is used to select the Service state. + * + * + * If cell selection process indicates CELL_SELECTED: + * + * - NO IMSI state is entered, if no SIM valid. + * - Otherwise NORMAL SERVICES state is entered, if + * SIM state is U1, SIM LAI == cell, IMSI is attached, T3212 not expired. + * - Otherwise NORMAL SERVICES state is entered, if + * SIM state is U1, SIM LAI == cell, attach not required, T3212 not expired. + * - Otherwise LIMITED SERVICE state is entered, if + * CS mode is automatic, cell is forbidden PLMN or forbidden LA. + * - Otherwise LIMITED SERVICE state is entered, if + * CS mode is manual, cell is not the selected one. + * - Otherwise LOCATION UPDATE NEEDED state is entered. + * + * gsm48_mm_cell_selected() is used to select the Service state. + * + */ + +/* + * support functions + */ + +/* get supported power level of given arfcn */ +uint8_t gsm48_current_pwr_lev(struct gsm_settings *set, uint16_t arfcn) +{ + uint8_t pwr_lev; + + if (arfcn >= (512 | ARFCN_PCS) && arfcn <= (810 | ARFCN_PCS)) + pwr_lev = set->class_pcs - 1; + else if (arfcn >= 512 && arfcn <= 885) + pwr_lev = set->class_dcs - 1; + else if (arfcn >= 259 && arfcn <= 340) + pwr_lev = set->class_400 - 1; + else if (arfcn >= 128 && arfcn <= 251) + pwr_lev = set->class_850 - 1; + else + pwr_lev = set->class_900 - 1; + + return pwr_lev; +} + +/* decode network name */ +static int decode_network_name(char *name, int name_len, + const uint8_t *lv) +{ + uint8_t in_len = lv[0]; + int length, padding; + + name[0] = '\0'; + if (in_len < 1) + return -EINVAL; + + /* must be CB encoded */ + if ((lv[1] & 0x70) != 0x00) + return -ENOTSUP; + + padding = lv[1] & 0x03; + length = ((in_len - 1) * 8 - padding) / 7; + if (length <= 0) + return 0; + if (length >= name_len) + length = name_len - 1; + gsm_7bit_decode(name, lv + 2, length); + name[length] = '\0'; + + return length; +} + +/* encode 'mobile identity' */ +int gsm48_encode_mi(uint8_t *buf, struct msgb *msg, struct osmocom_ms *ms, + uint8_t mi_type) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm_settings *set = &ms->settings; + uint8_t *ie; + + switch(mi_type) { + case GSM_MI_TYPE_TMSI: + gsm48_generate_mid_from_tmsi(buf, subscr->tmsi); + break; + case GSM_MI_TYPE_IMSI: + gsm48_generate_mid_from_imsi(buf, subscr->imsi); + break; + case GSM_MI_TYPE_IMEI: + gsm48_generate_mid_from_imsi(buf, set->imei); + break; + case GSM_MI_TYPE_IMEISV: + gsm48_generate_mid_from_imsi(buf, set->imeisv); + break; + case GSM_MI_TYPE_NONE: + default: + buf[0] = GSM48_IE_MOBILE_ID; + buf[1] = 1; + buf[2] = 0xf0; + break; + } + /* alter MI type */ + buf[2] = (buf[2] & ~GSM_MI_TYPE_MASK) | mi_type; + + if (msg) { + /* MI as LV */ + ie = msgb_put(msg, 1 + buf[1]); + memcpy(ie, buf + 1, 1 + buf[1]); + } + + return 0; +} + +/* encode 'classmark 1' */ +int gsm48_encode_classmark1(struct gsm48_classmark1 *cm, uint8_t rev_lev, + uint8_t es_ind, uint8_t a5_1, uint8_t pwr_lev) +{ + memset(cm, 0, sizeof(*cm)); + cm->rev_lev = rev_lev; + cm->es_ind = es_ind; + cm->a5_1 = !a5_1; + cm->pwr_lev = pwr_lev; + + return 0; +} + +/* + * timers + */ + +static void timeout_mm_t3210(void *arg) +{ + struct gsm48_mmlayer *mm = arg; + + LOGP(DMM, LOGL_INFO, "timer T3210 (loc. upd. timeout) has fired\n"); + gsm48_mm_ev(mm->ms, GSM48_MM_EVENT_TIMEOUT_T3210, NULL); +} + +static void timeout_mm_t3211(void *arg) +{ + struct gsm48_mmlayer *mm = arg; + + LOGP(DSUM, LOGL_INFO, "Location update retry\n"); + LOGP(DMM, LOGL_INFO, "timer T3211 (loc. upd. retry delay) has fired\n"); + gsm48_mm_ev(mm->ms, GSM48_MM_EVENT_TIMEOUT_T3211, NULL); +} + +static void timeout_mm_t3212(void *arg) +{ + struct gsm48_mmlayer *mm = arg; + + LOGP(DSUM, LOGL_INFO, "Periodic location update\n"); + LOGP(DMM, LOGL_INFO, "timer T3212 (periodic loc. upd. delay) has " + "fired\n"); + + /* reset attempt counter when attempting to update (4.4.4.5) */ + if (mm->state == GSM48_MM_ST_MM_IDLE + && mm->substate == GSM48_MM_SST_ATTEMPT_UPDATE) + mm->lupd_attempt = 0; + + gsm48_mm_ev(mm->ms, GSM48_MM_EVENT_TIMEOUT_T3212, NULL); +} + +static void timeout_mm_t3213(void *arg) +{ + struct gsm48_mmlayer *mm = arg; + + LOGP(DSUM, LOGL_INFO, "Location update retry\n"); + LOGP(DMM, LOGL_INFO, "timer T3213 (delay after RA failure) has " + "fired\n"); + gsm48_mm_ev(mm->ms, GSM48_MM_EVENT_TIMEOUT_T3213, NULL); +} + +static void timeout_mm_t3230(void *arg) +{ + struct gsm48_mmlayer *mm = arg; + + LOGP(DMM, LOGL_INFO, "timer T3230 (MM connection timeout) has " + "fired\n"); + gsm48_mm_ev(mm->ms, GSM48_MM_EVENT_TIMEOUT_T3230, NULL); +} + +static void timeout_mm_t3220(void *arg) +{ + struct gsm48_mmlayer *mm = arg; + + LOGP(DMM, LOGL_INFO, "timer T3220 (IMSI detach keepalive) has " + "fired\n"); + gsm48_mm_ev(mm->ms, GSM48_MM_EVENT_TIMEOUT_T3220, NULL); +} + +static void timeout_mm_t3240(void *arg) +{ + struct gsm48_mmlayer *mm = arg; + + LOGP(DMM, LOGL_INFO, "timer T3240 (RR release timeout) has fired\n"); + gsm48_mm_ev(mm->ms, GSM48_MM_EVENT_TIMEOUT_T3240, NULL); +} + +static void start_mm_t3210(struct gsm48_mmlayer *mm) +{ + LOGP(DMM, LOGL_INFO, "starting T3210 (loc. upd. timeout) with %d.%d " + "seconds\n", GSM_T3210_MS); + mm->t3210.cb = timeout_mm_t3210; + mm->t3210.data = mm; + osmo_timer_schedule(&mm->t3210, GSM_T3210_MS); +} + +static void start_mm_t3211(struct gsm48_mmlayer *mm) +{ + LOGP(DMM, LOGL_INFO, "starting T3211 (loc. upd. retry delay) with " + "%d.%d seconds\n", GSM_T3211_MS); + mm->t3211.cb = timeout_mm_t3211; + mm->t3211.data = mm; + osmo_timer_schedule(&mm->t3211, GSM_T3211_MS); +} + +static void start_mm_t3212(struct gsm48_mmlayer *mm, int sec) +{ + /* don't start, if is not available */ + if (!sec) + return; + + LOGP(DMM, LOGL_INFO, "starting T3212 (periodic loc. upd. delay) with " + "%d seconds\n", sec); + mm->t3212.cb = timeout_mm_t3212; + mm->t3212.data = mm; + osmo_timer_schedule(&mm->t3212, sec, 0); +} + +static void start_mm_t3213(struct gsm48_mmlayer *mm) +{ + LOGP(DMM, LOGL_INFO, "starting T3213 (delay after RA failure) with " + "%d.%d seconds\n", GSM_T3213_MS); + mm->t3213.cb = timeout_mm_t3213; + mm->t3213.data = mm; + osmo_timer_schedule(&mm->t3213, GSM_T3213_MS); +} + +static void start_mm_t3220(struct gsm48_mmlayer *mm) +{ + LOGP(DMM, LOGL_INFO, "starting T3220 (IMSI detach keepalive) with " + "%d.%d seconds\n", GSM_T3220_MS); + mm->t3220.cb = timeout_mm_t3220; + mm->t3220.data = mm; + osmo_timer_schedule(&mm->t3220, GSM_T3220_MS); +} + +static void start_mm_t3230(struct gsm48_mmlayer *mm) +{ + LOGP(DMM, LOGL_INFO, "starting T3230 (MM connection timeout) with " + "%d.%d seconds\n", GSM_T3230_MS); + mm->t3230.cb = timeout_mm_t3230; + mm->t3230.data = mm; + osmo_timer_schedule(&mm->t3230, GSM_T3230_MS); +} + +static void start_mm_t3240(struct gsm48_mmlayer *mm) +{ + LOGP(DMM, LOGL_INFO, "starting T3240 (RR release timeout) with %d.%d " + "seconds\n", GSM_T3240_MS); + mm->t3240.cb = timeout_mm_t3240; + mm->t3240.data = mm; + osmo_timer_schedule(&mm->t3240, GSM_T3240_MS); +} + +static void stop_mm_t3210(struct gsm48_mmlayer *mm) +{ + if (osmo_timer_pending(&mm->t3210)) { + LOGP(DMM, LOGL_INFO, "stopping pending (loc. upd. timeout) " + "timer T3210\n"); + osmo_timer_del(&mm->t3210); + } +} + +static void stop_mm_t3211(struct gsm48_mmlayer *mm) +{ + if (osmo_timer_pending(&mm->t3211)) { + LOGP(DMM, LOGL_INFO, "stopping pending (loc. upd. retry " + "delay) timer T3211\n"); + osmo_timer_del(&mm->t3211); + } +} + +static void stop_mm_t3212(struct gsm48_mmlayer *mm) +{ + if (osmo_timer_pending(&mm->t3212)) { + LOGP(DMM, LOGL_INFO, "stopping pending (periodic loc. upd. " + "delay) timer T3212\n"); + osmo_timer_del(&mm->t3212); + } +} + +static void stop_mm_t3213(struct gsm48_mmlayer *mm) +{ + if (osmo_timer_pending(&mm->t3213)) { + LOGP(DMM, LOGL_INFO, "stopping pending (delay after RA " + "failure) timer T3213\n"); + osmo_timer_del(&mm->t3213); + } +} + +static void stop_mm_t3220(struct gsm48_mmlayer *mm) +{ + if (osmo_timer_pending(&mm->t3220)) { + LOGP(DMM, LOGL_INFO, "stopping pending (IMSI detach keepalive) " + "timer T3220\n"); + osmo_timer_del(&mm->t3220); + } +} + +static void stop_mm_t3230(struct gsm48_mmlayer *mm) +{ + if (osmo_timer_pending(&mm->t3230)) { + LOGP(DMM, LOGL_INFO, "stopping pending (MM connection timeout) " + "timer T3230\n"); + osmo_timer_del(&mm->t3230); + } +} + +static void stop_mm_t3240(struct gsm48_mmlayer *mm) +{ + if (osmo_timer_pending(&mm->t3240)) { + LOGP(DMM, LOGL_INFO, "stopping pending (RR release timeout) " + "timer T3240\n"); + osmo_timer_del(&mm->t3240); + } +} + +static void stop_mm_t3241(struct gsm48_mmlayer *mm) +{ + /* not implemented, not required */ +} + +/* + * messages + */ + +/* names of MM events */ +static const struct value_string gsm48_mmevent_names[] = { + { GSM48_MM_EVENT_CELL_SELECTED, "MM_EVENT_CELL_SELECTED" }, + { GSM48_MM_EVENT_NO_CELL_FOUND, "MM_EVENT_NO_CELL_FOUND" }, + { GSM48_MM_EVENT_TIMEOUT_T3210, "MM_EVENT_TIMEOUT_T3210" }, + { GSM48_MM_EVENT_TIMEOUT_T3211, "MM_EVENT_TIMEOUT_T3211" }, + { GSM48_MM_EVENT_TIMEOUT_T3212, "MM_EVENT_TIMEOUT_T3212" }, + { GSM48_MM_EVENT_TIMEOUT_T3213, "MM_EVENT_TIMEOUT_T3213" }, + { GSM48_MM_EVENT_TIMEOUT_T3220, "MM_EVENT_TIMEOUT_T3220" }, + { GSM48_MM_EVENT_TIMEOUT_T3230, "MM_EVENT_TIMEOUT_T3230" }, + { GSM48_MM_EVENT_TIMEOUT_T3240, "MM_EVENT_TIMEOUT_T3240" }, + { GSM48_MM_EVENT_IMSI_DETACH, "MM_EVENT_IMSI_DETACH" }, + { GSM48_MM_EVENT_POWER_OFF, "MM_EVENT_POWER_OFF" }, + { GSM48_MM_EVENT_PAGING, "MM_EVENT_PAGING" }, + { GSM48_MM_EVENT_AUTH_RESPONSE, "MM_EVENT_AUTH_RESPONSE" }, + { GSM48_MM_EVENT_SYSINFO, "MM_EVENT_SYSINFO" }, + { GSM48_MM_EVENT_USER_PLMN_SEL, "MM_EVENT_USER_PLMN_SEL" }, + { GSM48_MM_EVENT_LOST_COVERAGE, "MM_EVENT_LOST_COVERAGE" }, + { 0, NULL } +}; + +const char *get_mmevent_name(int value) +{ + return get_value_string(gsm48_mmevent_names, value); +} + +/* names of MM-SAP */ +static const struct value_string gsm48_mm_msg_names[] = { + { GSM48_MT_MM_IMSI_DETACH_IND, "MT_MM_IMSI_DETACH_IND" }, + { GSM48_MT_MM_LOC_UPD_ACCEPT, "MT_MM_LOC_UPD_ACCEPT" }, + { GSM48_MT_MM_LOC_UPD_REJECT, "MT_MM_LOC_UPD_REJECT" }, + { GSM48_MT_MM_LOC_UPD_REQUEST, "MT_MM_LOC_UPD_REQUEST" }, + { GSM48_MT_MM_AUTH_REJ, "MT_MM_AUTH_REJ" }, + { GSM48_MT_MM_AUTH_REQ, "MT_MM_AUTH_REQ" }, + { GSM48_MT_MM_AUTH_RESP, "MT_MM_AUTH_RESP" }, + { GSM48_MT_MM_ID_REQ, "MT_MM_ID_REQ" }, + { GSM48_MT_MM_ID_RESP, "MT_MM_ID_RESP" }, + { GSM48_MT_MM_TMSI_REALL_CMD, "MT_MM_TMSI_REALL_CMD" }, + { GSM48_MT_MM_TMSI_REALL_COMPL, "MT_MM_TMSI_REALL_COMPL" }, + { GSM48_MT_MM_CM_SERV_ACC, "MT_MM_CM_SERV_ACC" }, + { GSM48_MT_MM_CM_SERV_REJ, "MT_MM_CM_SERV_REJ" }, + { GSM48_MT_MM_CM_SERV_ABORT, "MT_MM_CM_SERV_ABORT" }, + { GSM48_MT_MM_CM_SERV_REQ, "MT_MM_CM_SERV_REQ" }, + { GSM48_MT_MM_CM_SERV_PROMPT, "MT_MM_CM_SERV_PROMPT" }, + { GSM48_MT_MM_CM_REEST_REQ, "MT_MM_CM_REEST_REQ" }, + { GSM48_MT_MM_ABORT, "MT_MM_ABORT" }, + { GSM48_MT_MM_NULL, "MT_MM_NULL" }, + { GSM48_MT_MM_STATUS, "MT_MM_STATUS" }, + { GSM48_MT_MM_INFO, "MT_MM_INFO" }, + { 0, NULL } +}; + +const char *get_mm_name(int value) +{ + return get_value_string(gsm48_mm_msg_names, value); +} + +/* names of MMxx-SAP */ +static const struct value_string gsm48_mmxx_msg_names[] = { + { GSM48_MMCC_EST_REQ, "MMCC_EST_REQ" }, + { GSM48_MMCC_EST_IND, "MMCC_EST_IND" }, + { GSM48_MMCC_EST_CNF, "MMCC_EST_CNF" }, + { GSM48_MMCC_REL_REQ, "MMCC_REL_REQ" }, + { GSM48_MMCC_REL_IND, "MMCC_REL_IND" }, + { GSM48_MMCC_DATA_REQ, "MMCC_DATA_REQ" }, + { GSM48_MMCC_DATA_IND, "MMCC_DATA_IND" }, + { GSM48_MMCC_UNIT_DATA_REQ, "MMCC_UNIT_DATA_REQ" }, + { GSM48_MMCC_UNIT_DATA_IND, "MMCC_UNIT_DATA_IND" }, + { GSM48_MMCC_SYNC_IND, "MMCC_SYNC_IND" }, + { GSM48_MMCC_REEST_REQ, "MMCC_REEST_REQ" }, + { GSM48_MMCC_REEST_CNF, "MMCC_REEST_CNF" }, + { GSM48_MMCC_ERR_IND, "MMCC_ERR_IND" }, + { GSM48_MMCC_PROMPT_IND, "MMCC_PROMPT_IND" }, + { GSM48_MMCC_PROMPT_REJ, "MMCC_PROMPT_REJ" }, + { GSM48_MMSS_EST_REQ, "MMSS_EST_REQ" }, + { GSM48_MMSS_EST_IND, "MMSS_EST_IND" }, + { GSM48_MMSS_EST_CNF, "MMSS_EST_CNF" }, + { GSM48_MMSS_REL_REQ, "MMSS_REL_REQ" }, + { GSM48_MMSS_REL_IND, "MMSS_REL_IND" }, + { GSM48_MMSS_DATA_REQ, "MMSS_DATA_REQ" }, + { GSM48_MMSS_DATA_IND, "MMSS_DATA_IND" }, + { GSM48_MMSS_UNIT_DATA_REQ, "MMSS_UNIT_DATA_REQ" }, + { GSM48_MMSS_UNIT_DATA_IND, "MMSS_UNIT_DATA_IND" }, + { GSM48_MMSS_REEST_REQ, "MMSS_REEST_REQ" }, + { GSM48_MMSS_REEST_CNF, "MMSS_REEST_CNF" }, + { GSM48_MMSS_ERR_IND, "MMSS_ERR_IND" }, + { GSM48_MMSS_PROMPT_IND, "MMSS_PROMPT_IND" }, + { GSM48_MMSS_PROMPT_REJ, "MMSS_PROMPT_REJ" }, + { GSM48_MMSMS_EST_REQ, "MMSMS_EST_REQ" }, + { GSM48_MMSMS_EST_IND, "MMSMS_EST_IND" }, + { GSM48_MMSMS_EST_CNF, "MMSMS_EST_CNF" }, + { GSM48_MMSMS_REL_REQ, "MMSMS_REL_REQ" }, + { GSM48_MMSMS_REL_IND, "MMSMS_REL_IND" }, + { GSM48_MMSMS_DATA_REQ, "MMSMS_DATA_REQ" }, + { GSM48_MMSMS_DATA_IND, "MMSMS_DATA_IND" }, + { GSM48_MMSMS_UNIT_DATA_REQ, "MMSMS_UNIT_DATA_REQ" }, + { GSM48_MMSMS_UNIT_DATA_IND, "MMSMS_UNIT_DATA_IND" }, + { GSM48_MMSMS_REEST_REQ, "MMSMS_REEST_REQ" }, + { GSM48_MMSMS_REEST_CNF, "MMSMS_REEST_CNF" }, + { GSM48_MMSMS_ERR_IND, "MMSMS_ERR_IND" }, + { GSM48_MMSMS_PROMPT_IND, "MMSMS_PROMPT_IND" }, + { GSM48_MMSMS_PROMPT_REJ, "MMSMS_PROMPT_REJ" }, + { 0, NULL } +}; + +const char *get_mmxx_name(int value) +{ + return get_value_string(gsm48_mmxx_msg_names, value); +} + +/* names of MMR-SAP */ +static const struct value_string gsm48_mmr_msg_names[] = { + { GSM48_MMR_REG_REQ, "MMR_REG_REQ" }, + { GSM48_MMR_REG_CNF, "MMR_REG_CNF" }, + { GSM48_MMR_NREG_REQ, "MMR_NREG_REQ" }, + { GSM48_MMR_NREG_IND, "MMR_NREG_IND" }, + { 0, NULL } +}; + +const char *get_mmr_name(int value) +{ + return get_value_string(gsm48_mmr_msg_names, value); +} + +/* allocate GSM 04.08 message (MMxx-SAP) */ +struct msgb *gsm48_mmxx_msgb_alloc(int msg_type, uint32_t ref, + uint8_t transaction_id, uint8_t sapi) +{ + struct msgb *msg; + struct gsm48_mmxx_hdr *mmh; + + msg = msgb_alloc_headroom(MMXX_ALLOC_SIZE+MMXX_ALLOC_HEADROOM, + MMXX_ALLOC_HEADROOM, "GSM 04.08 MMxx"); + if (!msg) + return NULL; + + mmh = (struct gsm48_mmxx_hdr *)msgb_put(msg, sizeof(*mmh)); + mmh->msg_type = msg_type; + mmh->ref = ref; + mmh->transaction_id = transaction_id; + mmh->sapi = sapi; + + return msg; +} + +/* allocate MM event message */ +struct msgb *gsm48_mmevent_msgb_alloc(int msg_type) +{ + struct msgb *msg; + struct gsm48_mm_event *mme; + + msg = msgb_alloc_headroom(sizeof(*mme), 0, "GSM 04.08 MM event"); + if (!msg) + return NULL; + + mme = (struct gsm48_mm_event *)msgb_put(msg, sizeof(*mme)); + mme->msg_type = msg_type; + + return msg; +} + +/* allocate MMR message */ +struct msgb *gsm48_mmr_msgb_alloc(int msg_type) +{ + struct msgb *msg; + struct gsm48_mmr *mmr; + + msg = msgb_alloc_headroom(sizeof(*mmr), 0, "GSM 04.08 MMR"); + if (!msg) + return NULL; + + mmr = (struct gsm48_mmr *)msgb_put(msg, sizeof(*mmr)); + mmr->msg_type = msg_type; + + return msg; +} + +/* queue message (MMxx-SAP) */ +int gsm48_mmxx_upmsg(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + msgb_enqueue(&mm->mmxx_upqueue, msg); + + return 0; +} + +/* queue message (MMR-SAP) */ +int gsm48_mmr_downmsg(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + msgb_enqueue(&mm->mmr_downqueue, msg); + + return 0; +} + +/* queue MM event message */ +int gsm48_mmevent_msg(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + msgb_enqueue(&mm->event_queue, msg); + + return 0; +} + +/* dequeue messages (MMxx-SAP) */ +int gsm48_mmxx_dequeue(struct osmocom_ms *ms) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct msgb *msg; + struct gsm48_mmxx_hdr *mmh; + int work = 0; + + while ((msg = msgb_dequeue(&mm->mmxx_upqueue))) { + mmh = (struct gsm48_mmxx_hdr *) msg->data; + switch (mmh->msg_type & GSM48_MMXX_MASK) { + case GSM48_MMCC_CLASS: + gsm48_rcv_cc(ms, msg); + break; + case GSM48_MMSS_CLASS: + gsm480_rcv_ss(ms, msg); + break; + case GSM48_MMSMS_CLASS: + gsm411_rcv_sms(ms, msg); + break; + } + msgb_free(msg); + work = 1; /* work done */ + } + + return work; +} + +/* dequeue messages (MMR-SAP) */ +int gsm48_mmr_dequeue(struct osmocom_ms *ms) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct msgb *msg; + struct gsm48_mmr *mmr; + int work = 0; + + while ((msg = msgb_dequeue(&mm->mmr_downqueue))) { + mmr = (struct gsm48_mmr *) msg->data; + gsm48_rcv_mmr(ms, msg); + msgb_free(msg); + work = 1; /* work done */ + } + + return work; +} + +/* dequeue messages (RR-SAP) */ +int gsm48_rr_dequeue(struct osmocom_ms *ms) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct msgb *msg; + int work = 0; + + while ((msg = msgb_dequeue(&mm->rr_upqueue))) { + /* msg is freed there */ + gsm48_rcv_rr(ms, msg); + work = 1; /* work done */ + } + + return work; +} + +/* dequeue MM event messages */ +int gsm48_mmevent_dequeue(struct osmocom_ms *ms) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_mm_event *mme; + struct msgb *msg; + int work = 0; + + while ((msg = msgb_dequeue(&mm->event_queue))) { + mme = (struct gsm48_mm_event *) msg->data; + gsm48_mm_ev(ms, mme->msg_type, msg); + msgb_free(msg); + work = 1; /* work done */ + } + + return work; +} + +/* push RR header and send to RR */ +static int gsm48_mm_to_rr(struct osmocom_ms *ms, struct msgb *msg, int msg_type, + uint8_t sapi, uint8_t cause) +{ + struct gsm48_rr_hdr *rrh; + + /* push RR header */ + msgb_push(msg, sizeof(struct gsm48_rr_hdr)); + rrh = (struct gsm48_rr_hdr *) msg->data; + rrh->msg_type = msg_type; + rrh->sapi = sapi; + rrh->cause = cause; + + /* send message to RR */ + return gsm48_rr_downmsg(ms, msg); +} + +/* + * state transition + */ + +const char *gsm48_mm_state_names[] = { + "NULL", + "undefined 1", + "undefined 2", + "location updating initiated", + "undefined 4", + "wait for outgoing MM connection", + "MM connection active", + "IMSI detach initiated", + "process CM service prompt", + "wait for network command", + "location updating reject", + "undefined 11", + "undefined 12", + "wait for RR connection (location updating)", + "wait for RR connection (MM connection)", + "wait for RR connection (IMSI detach)", + "undefined 16", + "wait for re-establishment", + "wait for RR connection active", + "MM idle", + "wait for additional outgoing MM connection", + "MM_CONN_ACTIVE_VGCS", + "WAIT_RR_CONN_VGCS", + "location updating pending", + "IMSI detach pending", + "RR connection release not allowed" +}; + +const char *gsm48_mm_substate_names[] = { + "NULL", + "normal service", + "attempting to update", + "limited service", + "no IMSI", + "no cell available", + "location updating needed", + "PLMN search", + "PLMN search (normal)", + "RX_VGCS_NORMAL", + "RX_VGCS_LIMITED" +}; + +/* change state from LOCATION UPDATE NEEDED to ATTEMPTING TO UPDATE */ +static int gsm48_mm_loc_upd_possible(struct gsm48_mmlayer *mm) +{ + // TODO: check if really possible + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_ATTEMPT_UPDATE); + return gsm48_mm_loc_upd_normal(mm->ms, NULL); +} + +/* Set new MM state, also new substate in case of MM IDLE state. */ +static void new_mm_state(struct gsm48_mmlayer *mm, int state, int substate) +{ + struct osmocom_ms *ms = mm->ms; + struct gsm322_plmn *plmn = &ms->plmn; + + /* IDLE -> IDLE */ + if (mm->state == GSM48_MM_ST_MM_IDLE && state == mm->state) + LOGP(DMM, LOGL_INFO, "new MM IDLE state %s -> %s\n", + gsm48_mm_substate_names[mm->substate], + gsm48_mm_substate_names[substate]); + /* IDLE -> non-IDLE */ + else if (mm->state == GSM48_MM_ST_MM_IDLE) + LOGP(DMM, LOGL_INFO, "new state MM IDLE, %s -> %s\n", + gsm48_mm_substate_names[mm->substate], + gsm48_mm_state_names[state]); + /* non-IDLE -> IDLE */ + else if (state == GSM48_MM_ST_MM_IDLE) + LOGP(DMM, LOGL_INFO, "new state %s -> MM IDLE, %s\n", + gsm48_mm_state_names[mm->state], + gsm48_mm_substate_names[substate]); + /* non-IDLE -> non-IDLE */ + else + LOGP(DMM, LOGL_INFO, "new state %s -> %s\n", + gsm48_mm_state_names[mm->state], + gsm48_mm_state_names[state]); + + /* display service on new IDLE state */ + if (state == GSM48_MM_ST_MM_IDLE + && (mm->state != GSM48_MM_ST_MM_IDLE || mm->substate != substate)) { + switch (substate) { + case GSM48_MM_SST_NORMAL_SERVICE: + vty_notify(ms, NULL); + vty_notify(ms, "On Network, normal service: %s, %s\n", + gsm_get_mcc(plmn->mcc), + gsm_get_mnc(plmn->mcc, plmn->mnc)); + break; + case GSM48_MM_SST_LIMITED_SERVICE: + vty_notify(ms, NULL); + vty_notify(ms, "Limited service, emergency calls are " + "possible.\n"); + break; + case GSM48_MM_SST_PLMN_SEARCH_NORMAL: + case GSM48_MM_SST_PLMN_SEARCH: + vty_notify(ms, NULL); + vty_notify(ms, "Searching network...\n"); + break; + case GSM48_MM_SST_NO_IMSI: + vty_notify(ms, NULL); + vty_notify(ms, "No SIM, emergency calls are " + "possible.\n"); + break; + case GSM48_MM_SST_NO_CELL_AVAIL: + vty_notify(ms, NULL); + vty_notify(ms, "No service.\n"); + break; + case GSM48_MM_SST_ATTEMPT_UPDATE: + vty_notify(ms, NULL); + vty_notify(ms, "Trying to registering with " + "network...\n"); + break; + } + } + + /* remember most recent substate */ + if (mm->state == GSM48_MM_ST_MM_IDLE) + mm->mr_substate = mm->substate; + + mm->state = state; + mm->substate = substate; + + /* resend detach event, if flag is set */ + if (state == GSM48_MM_ST_MM_IDLE && mm->delay_detach) { + struct msgb *nmsg; + + mm->delay_detach = 0; + + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_IMSI_DETACH); + if (!nmsg) + return; + gsm48_mmevent_msg(ms, nmsg); + } + + /* 4.4.2 start T3212 in MM IDLE mode if not started or has expired */ + if (state == GSM48_MM_ST_MM_IDLE + && (substate == GSM48_MM_SST_NORMAL_SERVICE + || substate == GSM48_MM_SST_ATTEMPT_UPDATE)) { + struct gsm48_sysinfo *s = &mm->ms->cellsel.sel_si; + + /* start periodic location update timer */ + if (s->t3212 && !osmo_timer_pending(&mm->t3212)) { + mm->t3212_value = s->t3212; + start_mm_t3212(mm, mm->t3212_value); + } + /* perform pending location update */ + if (mm->lupd_retry) { + LOGP(DMM, LOGL_INFO, "Loc. upd. pending (type %d)\n", + mm->lupd_type); + mm->lupd_retry = 0; + gsm48_mm_loc_upd(ms, NULL); + /* must exit, because this function can be called + * recursively + */ + return; + } + if (mm->lupd_periodic) { + LOGP(DMM, LOGL_INFO, "Periodic loc. upd. pending " + "(type %d)\n", mm->lupd_type); + mm->lupd_periodic = 0; + if (s->t3212) /* still required? */ + gsm48_mm_loc_upd_periodic(ms, NULL); + else + LOGP(DMM, LOGL_INFO, "but not requred\n"); + /* must exit, because this function can be called + * recursively + */ + return; + } + } + + /* check if location update is possible */ + if (state == GSM48_MM_ST_MM_IDLE + && substate == GSM48_MM_SST_LOC_UPD_NEEDED) { + gsm48_mm_loc_upd_possible(mm); + /* must exit, because this function can be called recursively */ + return; + } +} + +/* return PLMN SEARCH or PLMN SEARCH NORMAL state */ +static int gsm48_mm_set_plmn_search(struct osmocom_ms *ms) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm322_cellsel *cs = &ms->cellsel; + + /* SIM not inserted */ + if (!subscr->sim_valid) { + LOGP(DMM, LOGL_INFO, "Selecting PLMN SEARCH state, because " + "no SIM.\n"); + return GSM48_MM_SST_PLMN_SEARCH; + } + + /* SIM not updated */ + if (subscr->ustate != GSM_SIM_U1_UPDATED) { + LOGP(DMM, LOGL_INFO, "Selecting PLMN SEARCH state, because " + "SIM not updated.\n"); + return GSM48_MM_SST_PLMN_SEARCH; + } + if (subscr->lac == 0x0000 || subscr->lac >= 0xfffe) { + LOGP(DMM, LOGL_INFO, "Selecting PLMN SEARCH state, because " + "LAI in SIM not valid.\n"); + return GSM48_MM_SST_PLMN_SEARCH; + } + + /* no cell selected */ + if (!cs->selected) { + LOGP(DMM, LOGL_INFO, "Selecting PLMN SEARCH state, because " + "no cell selected.\n"); + return GSM48_MM_SST_PLMN_SEARCH; + } + + /* selected cell's LAI not equal to LAI stored on the sim */ + if (cs->sel_mcc != subscr->mcc + || cs->sel_mnc != subscr->mnc + || cs->sel_lac != subscr->lac) { + LOGP(DMM, LOGL_INFO, "Selecting PLMN SEARCH state, because " + "LAI of selected cell (MCC %s MNC %s LAC 0x%04x) " + "!= LAI in SIM (MCC %s MNC %s LAC 0x%04x).\n", + gsm_print_mcc(cs->sel_mcc), gsm_print_mnc(cs->sel_mnc), + cs->sel_lac, gsm_print_mcc(subscr->mcc), + gsm_print_mnc(subscr->mnc), subscr->lac); + return GSM48_MM_SST_PLMN_SEARCH; + } + + /* SIM is updated in this LA */ + LOGP(DMM, LOGL_INFO, "Selecting PLMN SEARCH NORMAL state.\n"); + return GSM48_MM_SST_PLMN_SEARCH_NORMAL; +} + +/* 4.2.3 when returning to MM IDLE state, this function is called */ +static int gsm48_mm_return_idle(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + + if (cs->state != GSM322_C3_CAMPED_NORMALLY + && cs->state != GSM322_C7_CAMPED_ANY_CELL) { + LOGP(DMM, LOGL_INFO, "Not camping, wait for CS process to " + "camp, it sends us CELL_SELECTED then.\n"); + return 0; + } + + /* 4.4.4.9 start T3211 when RR is released */ + if (mm->start_t3211) { + LOGP(DMM, LOGL_INFO, "Starting T3211 after RR release.\n"); + mm->start_t3211 = 0; + start_mm_t3211(mm); + } + + /* return from location update with "Roaming not allowed" */ + if (mm->state == GSM48_MM_ST_LOC_UPD_REJ && mm->lupd_rej_cause == 13) { + LOGP(DMM, LOGL_INFO, "Roaming not allowed as returning to " + "MM IDLE\n"); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, + gsm48_mm_set_plmn_search(ms)); + + return 0; + } + + /* no SIM present or invalid */ + if (!subscr->sim_valid) { + LOGP(DMM, LOGL_INFO, "SIM invalid as returning to MM IDLE\n"); + + /* stop periodic location updating */ + mm->lupd_pending = 0; + stop_mm_t3212(mm); /* 4.4.2 */ + + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_NO_IMSI); + + return 0; + } + + /* if we are attached and selected cell equals the registered LAI */ + if (subscr->imsi_attached + && subscr->lac /* valid */ + && cs->sel_mcc == subscr->mcc + && cs->sel_mnc == subscr->mnc + && cs->sel_lac == subscr->lac) { + LOGP(DMM, LOGL_INFO, "We are in registered LAI as returning " + "to MM IDLE\n"); + /* if SIM not updated (abnormal case as described in 4.4.4.9) */ + if (subscr->ustate != GSM_SIM_U1_UPDATED) + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, + GSM48_MM_SST_ATTEMPT_UPDATE); + else + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, + GSM48_MM_SST_NORMAL_SERVICE); + + return 0; + } + + if (cs->state == GSM322_C3_CAMPED_NORMALLY) { + LOGP(DMM, LOGL_INFO, "We are camping normally as returning to " + "MM IDLE\n"); + if (gsm_subscr_is_forbidden_plmn(subscr, cs->sel_mcc, + cs->sel_mnc)) { + /* location update not allowed */ + LOGP(DMM, LOGL_INFO, "Loc. upd. not allowed PLMN.\n"); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, + GSM48_MM_SST_LIMITED_SERVICE); + } else + if (gsm322_is_forbidden_la(ms, cs->sel_mcc, cs->sel_mnc, + cs->sel_lac)) { + /* location update not allowed */ + LOGP(DMM, LOGL_INFO, "Loc. upd. not allowed LA.\n"); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, + GSM48_MM_SST_LIMITED_SERVICE); + } else { + /* location update allowed */ + LOGP(DMM, LOGL_INFO, "Loc. upd. allowed.\n"); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, + GSM48_MM_SST_LOC_UPD_NEEDED); + } + } else { + /* location update not allowed */ + LOGP(DMM, LOGL_INFO, "We are camping on any cell as returning " + "to MM IDLE\n"); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, + GSM48_MM_SST_LIMITED_SERVICE); + } + + return 0; +} + +/* 4.2.1.1 Service state PLMN SEARCH (NORMAL) is left if no cell found */ +static int gsm48_mm_no_cell_found(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_NO_CELL_AVAIL); + + return 0; +} + +/* 4.2.1.1 Service state PLMN SEARCH (NORMAL) / NO CELL AVAILABLE is left + * if cell selected + */ +static int gsm48_mm_cell_selected(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm322_plmn *plmn = &ms->plmn; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = &cs->sel_si; + struct gsm_settings *set = &ms->settings; + + /* no SIM is inserted */ + if (!subscr->sim_valid) { + LOGP(DMM, LOGL_INFO, "SIM invalid as cell is selected.\n"); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_NO_IMSI); + + return 0; + } + + /* SIM not updated in this LA */ + if (subscr->ustate == GSM_SIM_U1_UPDATED + && subscr->lac /* valid */ + && cs->sel_mcc == subscr->mcc + && cs->sel_mnc == subscr->mnc + && cs->sel_lac == subscr->lac + && !mm->lupd_periodic) { + if (subscr->imsi_attached) { + struct msgb *nmsg; + + LOGP(DMM, LOGL_INFO, "Valid in location area.\n"); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, + GSM48_MM_SST_NORMAL_SERVICE); + + /* send message to PLMN search process */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_SUCCESS); + if (!nmsg) + return -ENOMEM; + gsm322_plmn_sendmsg(ms, nmsg); + + return 0; + } + if (!s->att_allowed) { + struct msgb *nmsg; + + LOGP(DMM, LOGL_INFO, "Attachment not required.\n"); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, + GSM48_MM_SST_NORMAL_SERVICE); + + /* send message to PLMN search process */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_SUCCESS); + if (!nmsg) + return -ENOMEM; + gsm322_plmn_sendmsg(ms, nmsg); + + return 0; + } + /* else, continue */ + } + + /* PLMN mode auto and selected cell is forbidden */ + if (set->plmn_mode == PLMN_MODE_AUTO + && (!cs->selected + || gsm_subscr_is_forbidden_plmn(subscr, cs->sel_mcc, cs->sel_mnc) + || gsm322_is_forbidden_la(ms, cs->sel_mcc, cs->sel_mnc, + cs->sel_lac))) { + struct msgb *nmsg; + + LOGP(DMM, LOGL_INFO, "Selected cell is forbidden.\n"); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, + GSM48_MM_SST_LIMITED_SERVICE); + + /* send message to PLMN search process */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_FAILED); + if (!nmsg) + return -ENOMEM; + gsm322_plmn_sendmsg(ms, nmsg); + + return 0; + } + + /* PLMN mode manual and selected cell not selected PLMN. + * in M3 state the PLMN is not selected for registration. */ + if (set->plmn_mode == PLMN_MODE_MANUAL + && (!cs->selected + || plmn->mcc != cs->sel_mcc + || plmn->mnc != cs->sel_mnc + || plmn->state == GSM322_M3_NOT_ON_PLMN)) { + struct msgb *nmsg; + + LOGP(DMM, LOGL_INFO, "Selected cell not found.\n"); + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, + GSM48_MM_SST_LIMITED_SERVICE); + + /* send message to PLMN search process */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_FAILED); + if (!nmsg) + return -ENOMEM; + gsm322_plmn_sendmsg(ms, nmsg); + + return 0; + } + + /* other cases */ + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, GSM48_MM_SST_LOC_UPD_NEEDED); + + return 0; +} + +/* 4.2.1.2 Service state PLMN SEARCH (NORMAL) is entered */ +static int gsm48_mm_plmn_search(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, gsm48_mm_set_plmn_search(ms)); + + return 0; +} + +/* + * init and exit + */ + +/* initialize Mobility Management process */ +int gsm48_mm_init(struct osmocom_ms *ms) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + memset(mm, 0, sizeof(*mm)); + mm->ms = ms; + + LOGP(DMM, LOGL_INFO, "init Mobility Management process\n"); + + /* 4.2.1.1 */ + mm->state = GSM48_MM_ST_MM_IDLE; + mm->substate = gsm48_mm_set_plmn_search(ms); + + /* init lists */ + INIT_LLIST_HEAD(&mm->mm_conn); + INIT_LLIST_HEAD(&mm->rr_upqueue); + INIT_LLIST_HEAD(&mm->mmxx_upqueue); + INIT_LLIST_HEAD(&mm->mmr_downqueue); + INIT_LLIST_HEAD(&mm->event_queue); + + return 0; +} + +/* exit MM process */ +int gsm48_mm_exit(struct osmocom_ms *ms) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_mm_conn *conn; + struct msgb *msg; + + LOGP(DMM, LOGL_INFO, "exit Mobility Management process\n"); + + /* flush lists */ + while (!llist_empty(&mm->mm_conn)) { + conn = llist_entry(mm->mm_conn.next, + struct gsm48_mm_conn, list); + mm_conn_free(conn); + } + while ((msg = msgb_dequeue(&mm->rr_upqueue))) + msgb_free(msg); + while ((msg = msgb_dequeue(&mm->mmxx_upqueue))) + msgb_free(msg); + while ((msg = msgb_dequeue(&mm->mmr_downqueue))) + msgb_free(msg); + while ((msg = msgb_dequeue(&mm->event_queue))) + msgb_free(msg); + + /* stop timers */ + stop_mm_t3210(mm); + stop_mm_t3211(mm); + stop_mm_t3212(mm); + stop_mm_t3213(mm); + stop_mm_t3220(mm); + stop_mm_t3230(mm); + stop_mm_t3240(mm); + + return 0; +} + +/* + * MM connection management + */ + +static const char *gsm48_mmxx_state_names[] = { + "IDLE", + "CONN_PEND", + "DEDICATED", + "CONN_SUSP", + "REESTPEND" +}; + +uint32_t mm_conn_new_ref = 0x80000001; + +/* new MM connection state */ +static void new_conn_state(struct gsm48_mm_conn *conn, int state) +{ + LOGP(DMM, LOGL_INFO, "(ref %x) new state %s -> %s\n", conn->ref, + gsm48_mmxx_state_names[conn->state], + gsm48_mmxx_state_names[state]); + conn->state = state; +} + +/* find MM connection by protocol+ID */ +struct gsm48_mm_conn *mm_conn_by_id(struct gsm48_mmlayer *mm, + uint8_t proto, uint8_t transaction_id) +{ + struct gsm48_mm_conn *conn; + + llist_for_each_entry(conn, &mm->mm_conn, list) { + if (conn->protocol == proto && + conn->transaction_id == transaction_id) + return conn; + } + return NULL; +} + +/* find MM connection by reference */ +struct gsm48_mm_conn *mm_conn_by_ref(struct gsm48_mmlayer *mm, + uint32_t ref) +{ + struct gsm48_mm_conn *conn; + + llist_for_each_entry(conn, &mm->mm_conn, list) { + if (conn->ref == ref) + return conn; + } + return NULL; +} + +/* create MM connection instance */ +static struct gsm48_mm_conn* mm_conn_new(struct gsm48_mmlayer *mm, + int proto, uint8_t transaction_id, uint8_t sapi, uint32_t ref) +{ + struct gsm48_mm_conn *conn = talloc_zero(l23_ctx, struct gsm48_mm_conn); + + if (!conn) + return NULL; + + LOGP(DMM, LOGL_INFO, "New MM Connection (proto 0x%02x trans_id %d " + "sapi %d ref %x)\n", proto, transaction_id, sapi, ref); + + conn->mm = mm; + conn->state = GSM48_MMXX_ST_IDLE; + conn->transaction_id = transaction_id; + conn->protocol = proto; + conn->sapi = sapi; + conn->ref = ref; + + llist_add(&conn->list, &mm->mm_conn); + + return conn; +} + +/* destroy MM connection instance */ +void mm_conn_free(struct gsm48_mm_conn *conn) +{ + LOGP(DMM, LOGL_INFO, "Freeing MM Connection\n"); + + new_conn_state(conn, GSM48_MMXX_ST_IDLE); + + llist_del(&conn->list); + + talloc_free(conn); +} + +/* support function to release pending/all ongoing MM connections */ +static int gsm48_mm_release_mm_conn(struct osmocom_ms *ms, int abort_any, + uint8_t cause, int error, uint8_t sapi) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_mm_conn *conn, *conn2; + struct msgb *nmsg; + struct gsm48_mmxx_hdr *nmmh; + + /* Note: For SAPI 0 all connections are released */ + + if (abort_any) + LOGP(DMM, LOGL_INFO, "Release any MM Connection " + "(sapi = %d)\n", sapi); + else + LOGP(DMM, LOGL_INFO, "Release pending MM Connections " + "(sapi = %d)\n", sapi); + + /* release MM connection(s) */ + llist_for_each_entry_safe(conn, conn2, &mm->mm_conn, list) { + /* abort any OR the pending connection */ + if ((abort_any || conn->state == GSM48_MMXX_ST_CONN_PEND) + && (sapi == conn->sapi || sapi == 0)) { + /* send MMxx-REL-IND */ + nmsg = NULL; + switch(conn->protocol) { + case GSM48_PDISC_CC: + nmsg = gsm48_mmxx_msgb_alloc( + error ? GSM48_MMCC_ERR_IND + : GSM48_MMCC_REL_IND, conn->ref, + conn->transaction_id, + conn->sapi); + break; + case GSM48_PDISC_NC_SS: + nmsg = gsm48_mmxx_msgb_alloc( + error ? GSM48_MMSS_ERR_IND + : GSM48_MMSS_REL_IND, conn->ref, + conn->transaction_id, + conn->sapi); + break; + case GSM48_PDISC_SMS: + nmsg = gsm48_mmxx_msgb_alloc( + error ? GSM48_MMSMS_ERR_IND + : GSM48_MMSMS_REL_IND, conn->ref, + conn->transaction_id, + conn->sapi); + break; + } + if (!nmsg) { + /* this should not happen */ + mm_conn_free(conn); + continue; /* skip if not of CC type */ + } + nmmh = (struct gsm48_mmxx_hdr *)nmsg->data; + nmmh->cause = cause; + gsm48_mmxx_upmsg(ms, nmsg); + + mm_conn_free(conn); + } + } + return 0; +} + +/* + * process handlers (Common procedures) + */ + +/* sending MM STATUS message */ +static int gsm48_mm_tx_mm_status(struct osmocom_ms *ms, uint8_t cause) +{ + struct msgb *nmsg; + struct gsm48_hdr *ngh; + uint8_t *reject_cause; + + LOGP(DMM, LOGL_INFO, "MM STATUS (cause #%d)\n", cause); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh)); + reject_cause = msgb_put(nmsg, 1); + + ngh->proto_discr = GSM48_PDISC_MM; + ngh->msg_type = GSM48_MT_MM_STATUS; + *reject_cause = cause; + + /* push RR header and send down */ + return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_DATA_REQ, 0, 0); +} + +/* 4.3.1.2 sending TMSI REALLOCATION COMPLETE message */ +static int gsm48_mm_tx_tmsi_reall_cpl(struct osmocom_ms *ms) +{ + struct msgb *nmsg; + struct gsm48_hdr *ngh; + + LOGP(DMM, LOGL_INFO, "TMSI REALLOCATION COMPLETE\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh)); + + ngh->proto_discr = GSM48_PDISC_MM; + ngh->msg_type = GSM48_MT_MM_TMSI_REALL_COMPL; + + /* push RR header and send down */ + return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_DATA_REQ, 0, 0); +} + +/* 4.3.1 TMSI REALLOCATION COMMAND is received */ +static int gsm48_mm_rx_tmsi_realloc_cmd(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct gsm48_loc_area_id *lai = (struct gsm48_loc_area_id *) gh->data; + uint8_t mi_type, *mi; + uint32_t tmsi; + + if (payload_len < sizeof(struct gsm48_loc_area_id) + 2) { + short_read: + LOGP(DMM, LOGL_NOTICE, "Short read of TMSI REALLOCATION " + "COMMAND message error.\n"); + return -EINVAL; + } + /* LAI */ + gsm48_decode_lai_hex(lai, &subscr->mcc, &subscr->mnc, &subscr->lac); + /* MI */ + mi = gh->data + sizeof(struct gsm48_loc_area_id); + mi_type = mi[1] & GSM_MI_TYPE_MASK; + switch (mi_type) { + case GSM_MI_TYPE_TMSI: + if (payload_len + sizeof(struct gsm48_loc_area_id) < 6 + || mi[0] < 5) + goto short_read; + memcpy(&tmsi, mi+2, 4); + subscr->tmsi = ntohl(tmsi); + LOGP(DMM, LOGL_INFO, "TMSI 0x%08x (%u) assigned.\n", + subscr->tmsi, subscr->tmsi); + gsm48_mm_tx_tmsi_reall_cpl(ms); + break; + case GSM_MI_TYPE_IMSI: + subscr->tmsi = 0xffffffff; + LOGP(DMM, LOGL_INFO, "TMSI removed.\n"); + gsm48_mm_tx_tmsi_reall_cpl(ms); + break; + default: + subscr->tmsi = 0xffffffff; + LOGP(DMM, LOGL_NOTICE, "TMSI reallocation with unknown MI " + "type %d.\n", mi_type); + gsm48_mm_tx_mm_status(ms, GSM48_REJECT_INCORRECT_MESSAGE); + } + + /* store LOCI on sim */ + gsm_subscr_write_loci(ms); + + return 0; +} + +/* 4.3.2.2 AUTHENTICATION REQUEST is received */ +static int gsm48_mm_rx_auth_req(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm_settings *set = &ms->settings; + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct gsm48_auth_req *ar = (struct gsm48_auth_req *) gh->data; + uint8_t no_sim = 0; + + if (payload_len < sizeof(struct gsm48_auth_req)) { + LOGP(DMM, LOGL_NOTICE, "Short read of AUTHENTICATION REQUEST " + "message error.\n"); + return -EINVAL; + } + + /* SIM is not available */ + if (!subscr->sim_valid) { + LOGP(DMM, LOGL_INFO, "AUTHENTICATION REQUEST without SIM\n"); + return gsm48_mm_tx_mm_status(ms, + GSM48_REJECT_MSG_NOT_COMPATIBLE); + } + + LOGP(DMM, LOGL_INFO, "AUTHENTICATION REQUEST (seq %d)\n", ar->key_seq); + + /* key_seq and random + * in case of test card, there is a dummy response. + * authentication request is possible during emergency call, if + * IMSI is known to the network. in case of emergency IMSI, we need to + * send a dummy response also. + */ + if (mm->est_cause == RR_EST_CAUSE_EMERGENCY && set->emergency_imsi[0]) + no_sim = 1; + gsm_subscr_generate_kc(ms, ar->key_seq, ar->rand, no_sim); + + /* wait for auth response event from SIM */ + return 0; +} + +/* 4.3.2.2 sending AUTHENTICATION RESPONSE */ +static int gsm48_mm_tx_auth_rsp(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mm_event *mme = (struct gsm48_mm_event *) msg->data; + struct msgb *nmsg; + struct gsm48_hdr *ngh; + uint8_t *sres; + + LOGP(DMM, LOGL_INFO, "AUTHENTICATION RESPONSE\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh)); + + ngh->proto_discr = GSM48_PDISC_MM; + ngh->msg_type = GSM48_MT_MM_AUTH_RESP; + + /* SRES */ + sres = msgb_put(nmsg, 4); + memcpy(sres, mme->sres, 4); + + /* push RR header and send down */ + return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_DATA_REQ, 0, 0); +} + +/* 4.3.2.5 AUTHENTICATION REJECT is received */ +static int gsm48_mm_rx_auth_rej(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_mmlayer *mm = &ms->mmlayer; + + LOGP(DMM, LOGL_INFO, "AUTHENTICATION REJECT\n"); + + stop_mm_t3212(mm); /* 4.4.2 */ + + /* SIM invalid */ + subscr->sim_valid = 0; + + /* TMSI and LAI invalid */ + subscr->tmsi = 0xffffffff; + subscr->lac = 0x0000; + + /* key is invalid */ + subscr->key_seq = 7; + + /* update status */ + new_sim_ustate(subscr, GSM_SIM_U3_ROAMING_NA); + + /* store LOCI on sim */ + gsm_subscr_write_loci(ms); + + /* abort IMSI detach procedure */ + if (mm->state == GSM48_MM_ST_IMSI_DETACH_INIT) { + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + + /* abort RR connection */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_ABORT_REQ); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *) nmsg->data; + nrrh->cause = GSM48_RR_CAUSE_NORMAL; + gsm48_rr_downmsg(ms, nmsg); + + /* return to MM IDLE */ + return gsm48_mm_return_idle(ms, NULL); + } + + return 0; +} + +/* 4.3.3.1 IDENTITY REQUEST is received */ +static int gsm48_mm_rx_id_req(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + uint8_t mi_type; + + if (payload_len < 1) { + LOGP(DMM, LOGL_NOTICE, "Short read of IDENTITY REQUEST message " + "error.\n"); + return -EINVAL; + } + + /* id type */ + mi_type = *gh->data; + LOGP(DMM, LOGL_INFO, "IDENTITY REQUEST (mi_type %d)\n", mi_type); + + /* check if request can be fulfilled */ + if (!subscr->sim_valid && mi_type != GSM_MI_TYPE_IMEI + && mi_type != GSM_MI_TYPE_IMEISV) { + LOGP(DMM, LOGL_INFO, "IDENTITY REQUEST without SIM\n"); + return gsm48_mm_tx_mm_status(ms, + GSM48_REJECT_MSG_NOT_COMPATIBLE); + } + if (mi_type == GSM_MI_TYPE_TMSI && subscr->tmsi == 0xffffffff) { + LOGP(DMM, LOGL_INFO, "IDENTITY REQUEST of TMSI, but we have no " + "TMSI\n"); + return gsm48_mm_tx_mm_status(ms, + GSM48_REJECT_MSG_NOT_COMPATIBLE); + } + + return gsm48_mm_tx_id_rsp(ms, mi_type); +} + +/* send IDENTITY RESPONSE message */ +static int gsm48_mm_tx_id_rsp(struct osmocom_ms *ms, uint8_t mi_type) +{ + struct msgb *nmsg; + struct gsm48_hdr *ngh; + uint8_t buf[11]; + + LOGP(DMM, LOGL_INFO, "IDENTITY RESPONSE\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh)); + + ngh->proto_discr = GSM48_PDISC_MM; + ngh->msg_type = GSM48_MT_MM_ID_RESP; + + /* MI */ + gsm48_encode_mi(buf, nmsg, ms, mi_type); + + /* push RR header and send down */ + return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_DATA_REQ, 0, 0); +} + +/* 4.3.4.1 sending IMSI DETACH INDICATION message */ +static int gsm48_mm_tx_imsi_detach(struct osmocom_ms *ms, int rr_prim) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm_support *sup = &ms->support; + struct gsm_settings *set = &ms->settings; + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct msgb *nmsg; + struct gsm48_hdr *ngh; + uint8_t pwr_lev; + uint8_t buf[11]; + struct gsm48_classmark1 cm; + + + LOGP(DMM, LOGL_INFO, "IMSI DETACH INDICATION\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh)); + + ngh->proto_discr = GSM48_PDISC_MM; + ngh->msg_type = GSM48_MT_MM_IMSI_DETACH_IND; + + /* classmark 1 */ + if (rr_prim == GSM48_RR_EST_REQ) + pwr_lev = gsm48_current_pwr_lev(set, cs->sel_arfcn); + else + pwr_lev = gsm48_current_pwr_lev(set, rr->cd_now.arfcn); + gsm48_encode_classmark1(&cm, sup->rev_lev, sup->es_ind, set->a5_1, + pwr_lev); + msgb_v_put(nmsg, *((uint8_t *)&cm)); + /* MI */ + if (subscr->tmsi != 0xffffffff) { /* have TMSI ? */ + gsm48_encode_mi(buf, nmsg, ms, GSM_MI_TYPE_TMSI); + LOGP(DMM, LOGL_INFO, " using TMSI 0x%08x\n", subscr->tmsi); + } else { + gsm48_encode_mi(buf, nmsg, ms, GSM_MI_TYPE_IMSI); + LOGP(DMM, LOGL_INFO, " using IMSI %s\n", subscr->imsi); + } + + /* push RR header and send down */ + mm->est_cause = RR_EST_CAUSE_OTHER_SDCCH; + return gsm48_mm_to_rr(ms, nmsg, rr_prim, 0, mm->est_cause); +} + +/* detach has ended */ +static int gsm48_mm_imsi_detach_end(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm_subscriber *subscr = &ms->subscr; + struct msgb *nmsg; + + LOGP(DMM, LOGL_INFO, "IMSI has been detached.\n"); + + /* stop IMSI detach timer (if running) */ + stop_mm_t3220(mm); + + /* SIM invalid */ + subscr->sim_valid = 0; + + /* wait for RR idle and then power off when IMSI is detached */ + if (ms->shutdown) { + if (mm->state == GSM48_MM_ST_MM_IDLE) { + mobile_exit(ms, 1); + return 0; + } + /* power off when MM idle */ + mm->power_off_idle = 1; + + /* return to MM IDLE */ + return gsm48_mm_return_idle(ms, NULL); + } + + /* send SIM remove event to gsm322 */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_SIM_REMOVE); + if (!nmsg) + return -ENOMEM; + gsm322_plmn_sendmsg(ms, nmsg); + + /* return to MM IDLE */ + return gsm48_mm_return_idle(ms, NULL); +} + +/* abort radio connection */ +static int gsm48_mm_imsi_detach_abort(struct osmocom_ms *ms, struct msgb *msg) +{ + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + + /* abort RR if timer fired */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_ABORT_REQ); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *) nmsg->data; + nrrh->cause = GSM48_RR_CAUSE_NORMAL; + gsm48_rr_downmsg(ms, nmsg); + + /* imsi detach has ended now */ + return gsm48_mm_imsi_detach_end(ms, msg); +} + +/* start an IMSI detach in MM IDLE */ +static int gsm48_mm_imsi_detach_start(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_sysinfo *s = &ms->cellsel.sel_si; + + /* we may silently finish IMSI detach */ + if (!s->att_allowed || !subscr->imsi_attached) { + LOGP(DMM, LOGL_INFO, "IMSI detach not required.\n"); + + return gsm48_mm_imsi_detach_end(ms, msg); + } + LOGP(DMM, LOGL_INFO, "IMSI detach started (MM IDLE)\n"); + + new_mm_state(mm, GSM48_MM_ST_WAIT_RR_CONN_IMSI_D, 0); + + /* establish RR and send IMSI detach */ + return gsm48_mm_tx_imsi_detach(ms, GSM48_RR_EST_REQ); +} + +/* IMSI detach has been sent, wait for RR release */ +static int gsm48_mm_imsi_detach_sent(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + /* start T3220 (4.3.4.1) */ + start_mm_t3220(mm); + + LOGP(DMM, LOGL_INFO, "IMSI detach started (Wait for RR release)\n"); + + new_mm_state(mm, GSM48_MM_ST_IMSI_DETACH_INIT, 0); + + return 0; +} + +/* release MM connection and proceed with IMSI detach */ +static int gsm48_mm_imsi_detach_release(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_sysinfo *s = &ms->cellsel.sel_si; + + /* stop MM connection timer */ + stop_mm_t3230(mm); + + /* release all connections */ + gsm48_mm_release_mm_conn(ms, 1, 16, 0, 0); + + /* wait for release of RR */ + if (!s->att_allowed || !subscr->imsi_attached) { + LOGP(DMM, LOGL_INFO, "IMSI detach not required.\n"); + new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0); + + /* power off */ + if (ms->shutdown) { + mobile_exit(ms, 1); + return 0; + } + + return 0; + } + + /* send IMSI detach */ + gsm48_mm_tx_imsi_detach(ms, GSM48_RR_DATA_REQ); + + /* go to sent state */ + return gsm48_mm_imsi_detach_sent(ms, msg); +} + +/* ignore ongoing IMSI detach */ +static int gsm48_mm_imsi_detach_ignore(struct osmocom_ms *ms, struct msgb *msg) +{ + return 0; +} + +/* delay until state change (and then retry) */ +static int gsm48_mm_imsi_detach_delay(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + LOGP(DMM, LOGL_INFO, "IMSI detach delayed.\n"); + + /* remember to detach later */ + mm->delay_detach = 1; + + return 0; +} + +/* 4.3.5.2 ABORT is received */ +static int gsm48_mm_rx_abort(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + uint8_t reject_cause; + + if (payload_len < 1) { + LOGP(DMM, LOGL_NOTICE, "Short read of ABORT message error.\n"); + return -EINVAL; + } + + reject_cause = *gh->data; + + if (llist_empty(&mm->mm_conn)) { + LOGP(DMM, LOGL_NOTICE, "ABORT (cause #%d) while no MM " + "connection is established.\n", reject_cause); + return gsm48_mm_tx_mm_status(ms, + GSM48_REJECT_MSG_NOT_COMPATIBLE); + } else { + LOGP(DMM, LOGL_NOTICE, "ABORT (cause #%d) while MM connection " + "is established.\n", reject_cause); + /* stop MM connection timer */ + stop_mm_t3230(mm); + + gsm48_mm_release_mm_conn(ms, 1, 16, 0, 0); + } + + if (reject_cause == GSM48_REJECT_ILLEGAL_ME) { + /* SIM invalid */ + subscr->sim_valid = 0; + + /* TMSI and LAI invalid */ + subscr->tmsi = 0xffffffff; + subscr->lac = 0x0000; + + /* key is invalid */ + subscr->key_seq = 7; + + /* update status */ + new_sim_ustate(subscr, GSM_SIM_U3_ROAMING_NA); + + /* store LOCI on sim */ + gsm_subscr_write_loci(ms); + + /* return to MM IDLE */ + return gsm48_mm_return_idle(ms, NULL); + } + + return 0; +} + +/* 4.3.6.2 MM INFORMATION is received */ +static int gsm48_mm_rx_info(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + + if (payload_len < 0) { + LOGP(DMM, LOGL_NOTICE, "Short read of MM INFORMATION message " + "error.\n"); + return -EINVAL; + } + tlv_parse(&tp, &gsm48_mm_att_tlvdef, gh->data, payload_len, 0, 0); + + /* long name */ + if (TLVP_PRESENT(&tp, GSM48_IE_NAME_LONG)) { + decode_network_name(mm->name_long, sizeof(mm->name_long), + TLVP_VAL(&tp, GSM48_IE_NAME_LONG)-1); + } + /* short name */ + if (TLVP_PRESENT(&tp, GSM48_IE_NAME_SHORT)) { + decode_network_name(mm->name_short, sizeof(mm->name_short), + TLVP_VAL(&tp, GSM48_IE_NAME_SHORT)-1); + } + + return 0; +} + +/* + * process handlers for Location Update + IMSI attach (MM specific procedures) + */ + +/* 4.4.2 received sysinfo change event */ +static int gsm48_mm_sysinfo(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_sysinfo *s = &ms->cellsel.sel_si; + + /* t3212 not changed in these states */ + if (mm->state == GSM48_MM_ST_MM_IDLE + && (mm->substate == GSM48_MM_SST_NO_CELL_AVAIL + || mm->substate == GSM48_MM_SST_LIMITED_SERVICE + || mm->substate == GSM48_MM_SST_PLMN_SEARCH + || mm->substate == GSM48_MM_SST_PLMN_SEARCH_NORMAL)) + return 0; + + /* new periodic location update timer timeout */ + if (s->t3212 && s->t3212 != mm->t3212_value) { + if (osmo_timer_pending(&mm->t3212)) { + int t; + struct timeval current_time; + + /* get rest time */ + gettimeofday(¤t_time, NULL); + t = mm->t3212.timeout.tv_sec - current_time.tv_sec; + if (t < 0) + t = 0; + LOGP(DMM, LOGL_INFO, "New T3212 while timer is running " + "(value %d rest %d)\n", s->t3212, t); + + /* rest time modulo given value */ + mm->t3212.timeout.tv_sec = current_time.tv_sec + + (t % s->t3212); + } else { + uint32_t rand = random(); + + LOGP(DMM, LOGL_INFO, "New T3212 while timer is not " + "running (value %d)\n", s->t3212); + + /* value between 0 and given value */ + start_mm_t3212(mm, rand % (s->t3212 + 1)); + } + mm->t3212_value = s->t3212; + } + + return 0; +} + +/* 4.4.4.1 (re)start location update + * + * this function is called by + * - normal location update + * - periodic location update + * - imsi attach (normal loc. upd. function) + * - retry timers (T3211 and T3213) + */ +static int gsm48_mm_loc_upd(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = &cs->sel_si; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm_settings *set = &ms->settings; + struct msgb *nmsg; + int msg_type; + + /* (re)start only if we still require location update */ + if (!mm->lupd_pending) { + LOGP(DMM, LOGL_INFO, "No loc. upd. pending.\n"); + /* use MM IDLE to selecte the idle state */ + return gsm48_mm_return_idle(ms, NULL); + } + + /* must camp normally */ + if (cs->state != GSM322_C3_CAMPED_NORMALLY) { + LOGP(DMM, LOGL_INFO, "Loc. upd. not camping normally.\n"); + msg_type = GSM322_EVENT_REG_FAILED; + stop: + LOGP(DSUM, LOGL_INFO, "Location updating not possible\n"); + _stop: + mm->lupd_pending = 0; + +#if 0 + /* don't send message, if we got not triggered by PLMN search */ + if (!msg) + return 0; +#endif + + /* send message to PLMN search process */ + nmsg = gsm322_msgb_alloc(msg_type); + if (!nmsg) + return -ENOMEM; + gsm322_plmn_sendmsg(ms, nmsg); + /* use MM IDLE to selecte the idle state */ + return gsm48_mm_return_idle(ms, NULL); + } + + /* deny network, if disabled */ + if (set->no_lupd) { + LOGP(DMM, LOGL_INFO, "Loc. upd. disabled, adding " + "forbidden PLMN.\n"); + LOGP(DSUM, LOGL_INFO, "Location updating is disabled by " + "configuration\n"); + gsm_subscr_add_forbidden_plmn(subscr, cs->sel_mcc, + cs->sel_mnc, GSM48_REJECT_PLMN_NOT_ALLOWED); + msg_type = GSM322_EVENT_REG_FAILED; + goto _stop; + } + + /* if LAI is forbidden, don't start */ + if (gsm_subscr_is_forbidden_plmn(subscr, cs->sel_mcc, cs->sel_mnc)) { + LOGP(DMM, LOGL_INFO, "Loc. upd. not allowed PLMN.\n"); + msg_type = GSM322_EVENT_REG_FAILED; + goto stop; + } + if (gsm322_is_forbidden_la(ms, cs->sel_mcc, + cs->sel_mnc, cs->sel_lac)) { + LOGP(DMM, LOGL_INFO, "Loc. upd. not allowed LA.\n"); + msg_type = GSM322_EVENT_REG_FAILED; + goto stop; + } + + /* 4.4.4.9 if cell is barred, don't start */ + if ((!subscr->acc_barr && s->cell_barr) + || (!subscr->acc_barr && !((subscr->acc_class & 0xfbff) & + (s->class_barr ^ 0xffff)))) { + LOGP(DMM, LOGL_INFO, "Loc. upd. no access.\n"); + msg_type = GSM322_EVENT_REG_FAILED; + goto stop; + } + + mm->lupd_mcc = cs->sel_mcc; + mm->lupd_mnc = cs->sel_mnc; + mm->lupd_lac = cs->sel_lac; + + LOGP(DSUM, LOGL_INFO, "Perform location update (MCC %s, MNC %s " + "LAC 0x%04x)\n", gsm_print_mcc(mm->lupd_mcc), + gsm_print_mnc(mm->lupd_mnc), mm->lupd_lac); + + return gsm48_mm_tx_loc_upd_req(ms); +} + +/* initiate a normal location update / imsi attach */ +static int gsm48_mm_loc_upd_normal(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = &cs->sel_si; + struct msgb *nmsg; + + /* in case we already have a location update going on */ + if (mm->lupd_pending) { + LOGP(DMM, LOGL_INFO, "Loc. upd. already pending.\n"); + + return -EBUSY; + } + + /* no location update, if limited service */ + if (cs->state != GSM322_C3_CAMPED_NORMALLY) { + LOGP(DMM, LOGL_INFO, "Loc. upd. not allowed.\n"); + +#if 0 + /* don't send message, if we got not triggered by PLMN search */ + if (!msg) + return 0; +#endif + + /* send message to PLMN search process */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_FAILED); + if (!nmsg) + return -ENOMEM; + gsm322_plmn_sendmsg(ms, nmsg); + + return 0; + } + + /* if location update is not required */ + if (subscr->ustate == GSM_SIM_U1_UPDATED + && cs->selected + && cs->sel_mcc == subscr->mcc + && cs->sel_mnc == subscr->mnc + && cs->sel_lac == subscr->lac + && (subscr->imsi_attached + || !s->att_allowed)) { + LOGP(DMM, LOGL_INFO, "Loc. upd. not required.\n"); + subscr->imsi_attached = 1; + + /* go straight to normal service state */ + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, + GSM48_MM_SST_NORMAL_SERVICE); + +#if 0 + /* don't send message, if we got not triggered by PLMN search */ + if (!msg) + return 0; +#endif + + /* send message to PLMN search process */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_SUCCESS); + if (!nmsg) + return -ENOMEM; + gsm322_plmn_sendmsg(ms, nmsg); + + return 0; + } + + /* 4.4.3 is attachment required? */ + if (subscr->ustate == GSM_SIM_U1_UPDATED + && cs->selected + && cs->sel_mcc == subscr->mcc + && cs->sel_mnc == subscr->mnc + && cs->sel_lac == subscr->lac + && !subscr->imsi_attached + && s->att_allowed) { + /* do location update for IMSI attach */ + LOGP(DMM, LOGL_INFO, "Do Loc. upd. for IMSI attach.\n"); + mm->lupd_type = 2; + } else { + /* do normal location update */ + LOGP(DMM, LOGL_INFO, "Do normal Loc. upd.\n"); + mm->lupd_type = 0; + } + + /* start location update */ + mm->lupd_attempt = 0; + mm->lupd_pending = 1; + mm->lupd_ra_failure = 0; + + return gsm48_mm_loc_upd(ms, msg); +} + +/* initiate a periodic location update */ +static int gsm48_mm_loc_upd_periodic(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + /* in case we already have a location update going on */ + if (mm->lupd_pending) { + LOGP(DMM, LOGL_INFO, "Loc. upd. already pending.\n"); + return -EBUSY; + } + + /* start periodic location update */ + mm->lupd_type = 1; + mm->lupd_pending = 1; + mm->lupd_ra_failure = 0; + + return gsm48_mm_loc_upd(ms, msg); +} + +/* ignore location update */ +static int gsm48_mm_loc_upd_ignore(struct osmocom_ms *ms, struct msgb *msg) +{ + return 0; +} + +/* 9.2.15 send LOCATION UPDATING REQUEST message */ +static int gsm48_mm_tx_loc_upd_req(struct osmocom_ms *ms) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm_settings *set = &ms->settings; + struct gsm_support *sup = &ms->support; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm322_cellsel *cs = &ms->cellsel; + struct msgb *nmsg; + struct gsm48_hdr *ngh; + struct gsm48_loc_upd_req *nlu; /* NOTE: mi_len is part of struct */ + uint8_t pwr_lev; + uint8_t buf[11]; + + LOGP(DMM, LOGL_INFO, "LOCATION UPDATING REQUEST\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh)); + nlu = (struct gsm48_loc_upd_req *)msgb_put(nmsg, sizeof(*nlu)); + + ngh->proto_discr = GSM48_PDISC_MM; + ngh->msg_type = GSM48_MT_MM_LOC_UPD_REQUEST; + + /* location updating type */ + nlu->type = mm->lupd_type; + /* cipering key */ + nlu->key_seq = gsm_subscr_get_key_seq(ms, subscr); + /* LAI (last SIM stored LAI) + * + * NOTE: The TMSI is only valid within a LAI! + */ + gsm48_encode_lai_hex(&nlu->lai, subscr->mcc, subscr->mnc, subscr->lac); + LOGP(DMM, LOGL_INFO, " using LAI (mcc %s mnc %s " "lac 0x%04x)\n", + gsm_print_mcc(subscr->mcc), + gsm_print_mnc(subscr->mnc), subscr->lac); + /* classmark 1 */ + pwr_lev = gsm48_current_pwr_lev(set, cs->sel_arfcn); + gsm48_encode_classmark1(&nlu->classmark1, sup->rev_lev, sup->es_ind, + set->a5_1, pwr_lev); + /* MI */ + if (subscr->tmsi != 0xffffffff) { /* have TMSI ? */ + gsm48_encode_mi(buf, NULL, ms, GSM_MI_TYPE_TMSI); + LOGP(DMM, LOGL_INFO, " using TMSI 0x%08x\n", subscr->tmsi); + } else { + gsm48_encode_mi(buf, NULL, ms, GSM_MI_TYPE_IMSI); + LOGP(DMM, LOGL_INFO, " using IMSI %s\n", subscr->imsi); + } + msgb_put(nmsg, buf[1]); /* length is part of nlu */ + memcpy(&nlu->mi_len, buf + 1, 1 + buf[1]); + + new_mm_state(mm, GSM48_MM_ST_WAIT_RR_CONN_LUPD, 0); + + /* push RR header and send down */ + mm->est_cause = RR_EST_CAUSE_LOC_UPD; + return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_EST_REQ, 0, mm->est_cause); +} + +/* 4.4.4.1 RR is esablised during location update */ +static int gsm48_mm_est_loc_upd(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + /* start location update timer */ + start_mm_t3210(mm); + + new_mm_state(mm, GSM48_MM_ST_LOC_UPD_INIT, 0); + + return 0; +} + +/* 4.4.4.6 LOCATION UPDATING ACCEPT is received */ +static int gsm48_mm_rx_loc_upd_acc(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_loc_area_id *lai = (struct gsm48_loc_area_id *) gh->data; + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct msgb *nmsg; + + if (payload_len < sizeof(struct gsm48_loc_area_id)) { + short_read: + LOGP(DMM, LOGL_NOTICE, "Short read of LOCATION UPDATING ACCEPT " + "message error.\n"); + return -EINVAL; + } + tlv_parse(&tp, &gsm48_mm_att_tlvdef, + gh->data + sizeof(struct gsm48_loc_area_id), + payload_len - sizeof(struct gsm48_loc_area_id), 0, 0); + + /* update has finished */ + mm->lupd_pending = 0; + + /* RA was successfull */ + mm->lupd_ra_failure = 0; + + /* stop periodic location updating timer */ + stop_mm_t3212(mm); /* 4.4.2 */ + + /* LAI */ + gsm48_decode_lai_hex(lai, &subscr->mcc, &subscr->mnc, &subscr->lac); + + /* stop location update timer */ + stop_mm_t3210(mm); + + /* reset attempt counter */ + mm->lupd_attempt = 0; + + /* mark SIM as attached */ + subscr->imsi_attached = 1; + + /* set the status in the sim to updated */ + new_sim_ustate(subscr, GSM_SIM_U1_UPDATED); + + /* store LOCI on sim */ + gsm_subscr_write_loci(ms); + + /* set last registered PLMN */ + if (subscr->lac > 0x0000 && subscr->lac < 0xfffe) { + subscr->plmn_valid = 1; + subscr->plmn_mcc = subscr->mcc; + subscr->plmn_mnc = subscr->mnc; + } + + LOGP(DSUM, LOGL_INFO, "Location update accepted\n"); + LOGP(DMM, LOGL_INFO, "LOCATION UPDATING ACCEPT (mcc %s mnc %s " + "lac 0x%04x)\n", gsm_print_mcc(subscr->mcc), + gsm_print_mnc(subscr->mnc), subscr->lac); + + /* remove LA from forbidden list */ + gsm322_del_forbidden_la(ms, subscr->mcc, subscr->mnc, subscr->lac); + + /* MI */ + if (TLVP_PRESENT(&tp, GSM48_IE_MOBILE_ID)) { + const uint8_t *mi; + uint8_t mi_type; + uint32_t tmsi; + + mi = TLVP_VAL(&tp, GSM48_IE_MOBILE_ID)-1; + if (mi[0] < 1) + goto short_read; + mi_type = mi[1] & GSM_MI_TYPE_MASK; + switch (mi_type) { + case GSM_MI_TYPE_TMSI: + if (payload_len + sizeof(struct gsm48_loc_area_id) < 6 + || mi[0] < 5) + goto short_read; + memcpy(&tmsi, mi+2, 4); + subscr->tmsi = ntohl(tmsi); + LOGP(DMM, LOGL_INFO, "got TMSI 0x%08x (%u)\n", + subscr->tmsi, subscr->tmsi); + + /* store LOCI on sim */ + gsm_subscr_write_loci(ms); + + /* send TMSI REALLOCATION COMPLETE */ + gsm48_mm_tx_tmsi_reall_cpl(ms); + break; + case GSM_MI_TYPE_IMSI: + LOGP(DMM, LOGL_INFO, "TMSI removed\n"); + subscr->tmsi = 0xffffffff; + + /* store LOCI on sim */ + gsm_subscr_write_loci(ms); + + /* send TMSI REALLOCATION COMPLETE */ + gsm48_mm_tx_tmsi_reall_cpl(ms); + break; + default: + LOGP(DMM, LOGL_NOTICE, "TMSI reallocation with unknown " + "MI type %d.\n", mi_type); + } + } + + /* send message to PLMN search process */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_SUCCESS); + if (!nmsg) + return -ENOMEM; + gsm322_plmn_sendmsg(ms, nmsg); + + /* follow on proceed */ + if (TLVP_PRESENT(&tp, GSM48_IE_MOBILE_ID)) + LOGP(DMM, LOGL_NOTICE, "follow-on proceed not supported.\n"); + + /* start RR release timer */ + start_mm_t3240(mm); + + new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0); + + return 0; +} + +/* 4.4.4.7 LOCATION UPDATING REJECT is received */ +static int gsm48_mm_rx_loc_upd_rej(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + + if (payload_len < 1) { + LOGP(DMM, LOGL_NOTICE, "Short read of LOCATION UPDATING REJECT " + "message error.\n"); + return -EINVAL; + } + + /* RA was successfull */ + mm->lupd_ra_failure = 0; + + /* stop periodic location updating timer */ + stop_mm_t3212(mm); /* 4.4.2 */ + + /* stop location update timer */ + stop_mm_t3210(mm); + + /* store until RR is released */ + mm->lupd_rej_cause = *gh->data; + + /* start RR release timer */ + start_mm_t3240(mm); + + new_mm_state(mm, GSM48_MM_ST_LOC_UPD_REJ, 0); + + return 0; +} + +/* 4.4.4.7 RR is released after location update reject */ +static int gsm48_mm_rel_loc_upd_rej(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm_subscriber *subscr = &ms->subscr; + struct msgb *nmsg; + struct gsm322_msg *ngm; + + LOGP(DMM, LOGL_INFO, "Loc. upd. rejected (cause %d)\n", + mm->lupd_rej_cause); + + /* stop RR release timer */ + stop_mm_t3240(mm); + + /* new status */ + switch (mm->lupd_rej_cause) { + case GSM48_REJECT_IMSI_UNKNOWN_IN_HLR: + case GSM48_REJECT_ILLEGAL_MS: + case GSM48_REJECT_ILLEGAL_ME: + /* reset attempt counter */ + mm->lupd_attempt = 0; + + /* SIM invalid */ + subscr->sim_valid = 0; + + // fall through + case GSM48_REJECT_PLMN_NOT_ALLOWED: + case GSM48_REJECT_LOC_NOT_ALLOWED: + case GSM48_REJECT_ROAMING_NOT_ALLOWED: + /* TMSI and LAI invalid */ + subscr->tmsi = 0xffffffff; + subscr->lac = 0x0000; + + /* key is invalid */ + subscr->key_seq = 7; + + /* update status */ + new_sim_ustate(subscr, GSM_SIM_U3_ROAMING_NA); + + /* store LOCI on sim */ + gsm_subscr_write_loci(ms); + + /* update has finished */ + mm->lupd_pending = 0; + } + + /* send event to PLMN search process */ + switch(mm->lupd_rej_cause) { + case GSM48_REJECT_ROAMING_NOT_ALLOWED: + case GSM48_REJECT_PLMN_NOT_ALLOWED: + case GSM48_REJECT_LOC_NOT_ALLOWED: + nmsg = gsm322_msgb_alloc(GSM322_EVENT_ROAMING_NA); + break; + case GSM48_REJECT_IMSI_UNKNOWN_IN_HLR: + case GSM48_REJECT_ILLEGAL_MS: + case GSM48_REJECT_ILLEGAL_ME: + nmsg = gsm322_msgb_alloc(GSM322_EVENT_INVALID_SIM); + break; + default: + nmsg = gsm322_msgb_alloc(GSM322_EVENT_REG_FAILED); + } + if (!nmsg) + return -ENOMEM; + ngm = (struct gsm322_msg *)nmsg->data; + ngm->reject = mm->lupd_rej_cause; + gsm322_plmn_sendmsg(ms, nmsg); + + /* forbidden list */ + switch (mm->lupd_rej_cause) { + case GSM48_REJECT_IMSI_UNKNOWN_IN_HLR: + LOGP(DSUM, LOGL_INFO, "Location update failed (IMSI unknown " + "in HLR)\n"); + break; + case GSM48_REJECT_ILLEGAL_MS: + LOGP(DSUM, LOGL_INFO, "Location update failed (Illegal MS)\n"); + break; + case GSM48_REJECT_ILLEGAL_ME: + LOGP(DSUM, LOGL_INFO, "Location update failed (Illegal ME)\n"); + break; + case GSM48_REJECT_PLMN_NOT_ALLOWED: + gsm_subscr_add_forbidden_plmn(subscr, mm->lupd_mcc, + mm->lupd_mnc, mm->lupd_rej_cause); + LOGP(DSUM, LOGL_INFO, "Location update failed (PLMN not " + "allowed)\n"); + break; + case GSM48_REJECT_LOC_NOT_ALLOWED: + case GSM48_REJECT_ROAMING_NOT_ALLOWED: + gsm322_add_forbidden_la(ms, mm->lupd_mcc, mm->lupd_mnc, + mm->lupd_lac, mm->lupd_rej_cause); + LOGP(DSUM, LOGL_INFO, "Location update failed (LAI not " + "allowed)\n"); + break; + default: + /* 4.4.4.9 continue with failure handling */ + return gsm48_mm_loc_upd_failed(ms, NULL); + } + + /* return to MM IDLE */ + return gsm48_mm_return_idle(ms, NULL); +} + +/* 4.2.2 delay a location update */ +static int gsm48_mm_loc_upd_delay_per(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + LOGP(DMM, LOGL_INFO, "Schedule a pending periodic loc. upd.\n"); + mm->lupd_periodic = 1; + + return 0; +} + +/* delay a location update retry */ +static int gsm48_mm_loc_upd_delay_retry(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + LOGP(DMM, LOGL_INFO, "Schedule a pending periodic loc. upd.\n"); + mm->lupd_retry = 1; + + return 0; +} + +/* process failues as described in the lower part of 4.4.4.9 */ +static int gsm48_mm_loc_upd_failed(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm_subscriber *subscr = &ms->subscr; + + LOGP(DSUM, LOGL_INFO, "Location update failed\n"); + + /* stop location update timer, if running */ + stop_mm_t3210(mm); + + if (subscr->ustate == GSM_SIM_U1_UPDATED + && mm->lupd_mcc == subscr->mcc + && mm->lupd_mnc == subscr->mnc + && mm->lupd_lac == subscr->lac) { + if (mm->lupd_attempt < 4) { + LOGP(DSUM, LOGL_INFO, "Try location update later\n"); + LOGP(DMM, LOGL_INFO, "Loc. upd. failed, retry #%d\n", + mm->lupd_attempt); + + /* start update retry timer */ + start_mm_t3211(mm); + + /* return to MM IDLE */ + return gsm48_mm_return_idle(ms, NULL); + } else + LOGP(DMM, LOGL_INFO, "Loc. upd. failed too often.\n"); + } + + /* TMSI and LAI invalid */ + subscr->tmsi = 0xffffffff; + subscr->lac = 0x0000; + + /* key is invalid */ + subscr->key_seq = 7; + + /* update status */ + new_sim_ustate(subscr, GSM_SIM_U2_NOT_UPDATED); + + /* store LOCI on sim */ + gsm_subscr_write_loci(ms); + + /* start update retry timer (RR connection is released) */ + if (mm->lupd_attempt < 4) { + mm->start_t3211 = 1; + LOGP(DSUM, LOGL_INFO, "Try location update later\n"); + } + + /* return to MM IDLE */ + return gsm48_mm_return_idle(ms, NULL); +} + +/* abort a location update due to radio failure or release */ +static int gsm48_mm_rel_loc_upd_abort(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *)msg->data; + + /* stop RR release timer */ + stop_mm_t3240(mm); + + if (rrh->msg_type == GSM48_RR_REL_IND) { + LOGP(DMM, LOGL_INFO, "RR link released after loc. upd.\n"); + + /* continue with failure handling */ + return gsm48_mm_loc_upd_failed(ms, NULL); + } + + LOGP(DMM, LOGL_INFO, "Loc. upd. aborted by radio (cause #%d)\n", + rrh->cause); + + /* random access failure, but not two successive failures */ + if (rrh->cause == RR_REL_CAUSE_RA_FAILURE && !mm->lupd_ra_failure) { + mm->lupd_ra_failure = 1; + + /* start RA failure timer */ + start_mm_t3213(mm); + + return 0; + } + + /* RA was successfull or sent twice */ + mm->lupd_ra_failure = 0; + + /* continue with failure handling */ + return gsm48_mm_loc_upd_failed(ms, NULL); +} + +/* location update has timed out */ +static int gsm48_mm_loc_upd_timeout(struct osmocom_ms *ms, struct msgb *msg) +{ + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + + /* abort RR connection */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_ABORT_REQ); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *) nmsg->data; + nrrh->cause = GSM48_RR_CAUSE_ABNORMAL_TIMER; + gsm48_rr_downmsg(ms, nmsg); + + /* continue with failure handling */ + return gsm48_mm_loc_upd_failed(ms, NULL); +} + +/* + * process handlers for MM connections + */ + +/* cm reestablish request message from upper layer */ +static int gsm48_mm_tx_cm_serv_req(struct osmocom_ms *ms, int rr_prim, + uint8_t cm_serv) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm_settings *set = &ms->settings; + struct msgb *nmsg; + struct gsm48_hdr *ngh; + struct gsm48_service_request *nsr; /* NOTE: includes MI length */ + uint8_t *cm2lv; + uint8_t buf[11]; + + LOGP(DMM, LOGL_INFO, "CM SERVICE REQUEST (cause %d)\n", mm->est_cause); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh)); + nsr = (struct gsm48_service_request *)msgb_put(nmsg, sizeof(*nsr)); + cm2lv = (uint8_t *)&nsr->classmark; + + ngh->proto_discr = GSM48_PDISC_MM; + ngh->msg_type = GSM48_MT_MM_CM_SERV_REQ; + + /* type and key */ + nsr->cm_service_type = cm_serv; + nsr->cipher_key_seq = gsm_subscr_get_key_seq(ms, subscr); + /* classmark 2 */ + cm2lv[0] = sizeof(struct gsm48_classmark2); + gsm48_rr_enc_cm2(ms, (struct gsm48_classmark2 *)(cm2lv + 1), + (rr_prim == GSM48_RR_EST_REQ) ? cs->sel_arfcn + : rr->cd_now.arfcn); + /* MI */ + if (mm->est_cause == RR_EST_CAUSE_EMERGENCY && set->emergency_imsi[0]) { + LOGP(DMM, LOGL_INFO, "-> Using IMSI %s for emergency\n", + set->emergency_imsi); + gsm48_generate_mid_from_imsi(buf, set->emergency_imsi); + } else + if (!subscr->sim_valid) { /* have no SIM ? */ + LOGP(DMM, LOGL_INFO, "-> Using IMEI %s\n", + set->imei); + gsm48_encode_mi(buf, NULL, ms, GSM_MI_TYPE_IMEI); + } else + if (subscr->tmsi != 0xffffffff) { /* have TMSI ? */ + gsm48_encode_mi(buf, NULL, ms, GSM_MI_TYPE_TMSI); + LOGP(DMM, LOGL_INFO, "-> Using TMSI\n"); + } else { + gsm48_encode_mi(buf, NULL, ms, GSM_MI_TYPE_IMSI); + LOGP(DMM, LOGL_INFO, "-> Using IMSI %s\n", + subscr->imsi); + } + msgb_put(nmsg, buf[1]); /* length is part of nsr */ + memcpy(&nsr->mi_len, buf + 1, 1 + buf[1]); + /* prio is optional for eMLPP */ + + /* push RR header and send down */ + return gsm48_mm_to_rr(ms, nmsg, rr_prim, 0, mm->est_cause); +} + +/* cm service abort message from upper layer + * NOTE: T3240 is started by the calling function + */ +static int gsm48_mm_tx_cm_service_abort(struct osmocom_ms *ms) +{ + struct msgb *nmsg; + struct gsm48_hdr *ngh; + + LOGP(DMM, LOGL_INFO, "CM SERVICE ABORT\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + ngh = (struct gsm48_hdr *)msgb_put(nmsg, sizeof(*ngh)); + + ngh->proto_discr = GSM48_PDISC_MM; + ngh->msg_type = GSM48_MT_MM_CM_SERV_ABORT; + + /* push RR header and send down */ + return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_DATA_REQ, 0, 0); +} + +/* cm service acknowledge is received from lower layer */ +static int gsm48_mm_rx_cm_service_acc(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + /* stop MM connection timer */ + stop_mm_t3230(mm); + + new_mm_state(mm, GSM48_MM_ST_MM_CONN_ACTIVE, 0); + + return gsm48_mm_conn_go_dedic(ms); +} + +/* 9.2.6 CM SERVICE REJECT message received */ +static int gsm48_mm_rx_cm_service_rej(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + uint8_t abort_any = 0; + uint8_t reject_cause; + + if (payload_len < 1) { + LOGP(DMM, LOGL_NOTICE, "Short read of cm service reject " + "message error.\n"); + return -EINVAL; + } + + /* reject cause */ + reject_cause = *gh->data; + + LOGP(DMM, LOGL_INFO, "CM SERVICE REJECT (cause %d)\n", reject_cause); + + /* stop MM connection timer */ + stop_mm_t3230(mm); + + /* selection action on cause value */ + switch (reject_cause) { + case GSM48_REJECT_IMSI_UNKNOWN_IN_VLR: + case GSM48_REJECT_ILLEGAL_ME: + abort_any = 1; + + /* TMSI and LAI invalid */ + subscr->tmsi = 0xffffffff; + subscr->lac = 0x0000; + + /* key is invalid */ + subscr->key_seq = 7; + + /* update status */ + new_sim_ustate(subscr, GSM_SIM_U2_NOT_UPDATED); + + /* store LOCI on sim */ + gsm_subscr_write_loci(ms); + + /* change to WAIT_NETWORK_CMD state impied by abort_any == 1 */ + + if (reject_cause == GSM48_REJECT_ILLEGAL_ME) + subscr->sim_valid = 0; + + break; + default: + /* state implied by the number of remaining connections */ + ; + } + + /* release MM connection(s) */ + gsm48_mm_release_mm_conn(ms, abort_any, 16, 0, 0); + + /* state depends on the existance of remaining MM connections */ + if (llist_empty(&mm->mm_conn)) + new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0); + else + new_mm_state(mm, GSM48_MM_ST_MM_CONN_ACTIVE, 0); + + return 0; +} + +/* initiate an MM connection 4.5.1.1 + * + * this function is called when: + * - no RR connection exists + * - an RR connection exists, but this is the first MM connection + * - an RR connection exists, and there are already MM connection(s) + */ +static int gsm48_mm_init_mm(struct osmocom_ms *ms, struct msgb *msg, + int rr_prim) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + int msg_type = mmh->msg_type; + int emergency = 0; + uint8_t cause = 0, cm_serv = 0, proto = 0; + struct msgb *nmsg; + struct gsm48_mmxx_hdr *nmmh; + struct gsm48_mm_conn *conn, *conn_found = NULL; + uint8_t sapi = mmh->sapi; + + /* reset loc. upd. counter on CM service request */ + mm->lupd_attempt = 0; + + /* find if there is already a pending connection */ + llist_for_each_entry(conn, &mm->mm_conn, list) { + if (conn->state == GSM48_MMXX_ST_CONN_PEND) { + conn_found = conn; + break; + } + } + + /* if pending connection */ + if (conn_found) { + LOGP(DMM, LOGL_INFO, "Init MM Connection, but already have " + "pending MM Connection.\n"); + cause = 17; + /* use sapi from connection. if no connection, use sapi from + * message. + */ + sapi = conn_found->sapi; + reject: + nmsg = NULL; + switch(msg_type) { + case GSM48_MMCC_EST_REQ: + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_REL_IND, + mmh->ref, mmh->transaction_id, sapi); + break; + case GSM48_MMSS_EST_REQ: + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSS_REL_IND, + mmh->ref, mmh->transaction_id, sapi); + break; + case GSM48_MMSMS_EST_REQ: + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSMS_REL_IND, + mmh->ref, mmh->transaction_id, sapi); + break; + } + if (!nmsg) + return -ENOMEM; + nmmh = (struct gsm48_mmxx_hdr *)nmsg->data; + nmmh->cause = cause; + gsm48_mmxx_upmsg(ms, nmsg); + + return -EBUSY; + } + /* in case of an emergency setup */ + if (msg_type == GSM48_MMCC_EST_REQ && mmh->emergency) + emergency = 1; + + /* if sim is not updated */ + if (!emergency && subscr->ustate != GSM_SIM_U1_UPDATED) { + LOGP(DMM, LOGL_INFO, "Init MM Connection, but SIM not " + "updated.\n"); + cause = 21; + goto reject; + } + + if (mm->state == GSM48_MM_ST_MM_IDLE) { + /* current MM idle state */ + switch (mm->substate) { + case GSM48_MM_SST_NORMAL_SERVICE: + case GSM48_MM_SST_PLMN_SEARCH_NORMAL: + LOGP(DMM, LOGL_INFO, "Init MM Connection.\n"); + break; /* allow when normal */ + case GSM48_MM_SST_LOC_UPD_NEEDED: + case GSM48_MM_SST_ATTEMPT_UPDATE: + /* store mm request if attempting to update */ + if (!emergency) { + LOGP(DMM, LOGL_INFO, "Init MM Connection, but " + "attempting to update.\n"); + cause = 21; + goto reject; + /* TODO: implement delay and start loc upd. */ + } + break; + default: + /* reject if not emergency */ + if (!emergency) { + LOGP(DMM, LOGL_INFO, "Init MM Connection, not " + "in normal state.\n"); + cause = 21; + goto reject; + } + break; + } + } else + LOGP(DMM, LOGL_INFO, "Init another MM Connection.\n"); + + /* set cause, service, proto */ + switch(msg_type) { + case GSM48_MMCC_EST_REQ: + if (emergency) { + cause = RR_EST_CAUSE_EMERGENCY; + cm_serv = GSM48_CMSERV_EMERGENCY; + } else { + cause = RR_EST_CAUSE_ORIG_TCHF; + cm_serv = GSM48_CMSERV_MO_CALL_PACKET; + } + proto = GSM48_PDISC_CC; + break; + case GSM48_MMSS_EST_REQ: + cause = RR_EST_CAUSE_OTHER_SDCCH; + cm_serv = GSM48_CMSERV_SUP_SERV; + proto = GSM48_PDISC_NC_SS; + break; + case GSM48_MMSMS_EST_REQ: + cause = RR_EST_CAUSE_OTHER_SDCCH; + cm_serv = GSM48_CMSERV_SMS; + proto = GSM48_PDISC_SMS; + break; + } + + /* create MM connection instance */ + conn = mm_conn_new(mm, proto, mmh->transaction_id, mmh->sapi, mmh->ref); + if (!conn) + return -ENOMEM; + + new_conn_state(conn, GSM48_MMXX_ST_CONN_PEND); + + /* send CM SERVICE REQUEST */ + if (rr_prim) { + mm->est_cause = cause; + return gsm48_mm_tx_cm_serv_req(ms, rr_prim, cm_serv); + } else + return 0; +} + +/* 4.5.1.1 a) MM connection request triggers RR connection */ +static int gsm48_mm_init_mm_no_rr(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + int rc; + + /* start MM connection by requesting RR connection */ + rc = gsm48_mm_init_mm(ms, msg, GSM48_RR_EST_REQ); + if (rc) + return rc; + + new_mm_state(mm, GSM48_MM_ST_WAIT_RR_CONN_MM_CON, 0); + + return 0; +} + +/* 4.5.1.1 a) RR is esablised during mm connection, wait for CM accepted */ +static int gsm48_mm_est_mm_con(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + /* 4.5.1.7 if there is no more MM connection */ + if (llist_empty(&mm->mm_conn)) { + LOGP(DMM, LOGL_INFO, "MM Connection, are already gone.\n"); + + /* start RR release timer */ + start_mm_t3240(mm); + + new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0); + + /* send abort */ + return gsm48_mm_tx_cm_service_abort(ms); + } + + /* start MM connection timer */ + start_mm_t3230(mm); + + new_mm_state(mm, GSM48_MM_ST_WAIT_OUT_MM_CONN, 0); + + return 0; +} + +/* 4.5.1.1 b) MM connection request on existing RR connection */ +static int gsm48_mm_init_mm_first(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + int rc; + + /* start MM connection by sending data */ + rc = gsm48_mm_init_mm(ms, msg, GSM48_RR_DATA_REQ); + if (rc) + return rc; + + /* stop "RR connection release not allowed" timer */ + stop_mm_t3241(mm); + + /* start MM connection timer */ + start_mm_t3230(mm); + + new_mm_state(mm, GSM48_MM_ST_WAIT_OUT_MM_CONN, 0); + + return 0; +} + +/* 4.5.1.1 b) another MM connection request on existing RR connection */ +static int gsm48_mm_init_mm_more(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + int rc; + + /* start MM connection by sending data */ + rc = gsm48_mm_init_mm(ms, msg, GSM48_RR_DATA_REQ); + if (rc) + return rc; + + /* start MM connection timer */ + start_mm_t3230(mm); + + new_mm_state(mm, GSM48_MM_ST_WAIT_ADD_OUT_MM_CON, 0); + + return 0; +} + +/* 4.5.1.1 b) delay on WAIT FOR NETWORK COMMAND state */ +static int gsm48_mm_init_mm_wait(struct osmocom_ms *ms, struct msgb *msg) +{ + /* reject */ + gsm48_mm_init_mm_reject(ms, msg); +#if 0 + this requires handling when leaving this state... + + struct gsm48_mmlayer *mm = &ms->mmlayer; + int rc; + + /* just create the MM connection in pending state */ + rc = gsm48_mm_init_mm(ms, msg, 0); + if (rc) + return rc; + + /* start MM connection timer */ + start_mm_t3230(mm); + + new_mm_state(mm, GSM48_MM_ST_WAIT_ADD_OUT_MM_CON, 0); +#endif + + return 0; +} + +/* initiate an mm connection other cases */ +static int gsm48_mm_init_mm_reject(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + int msg_type = mmh->msg_type; + int sapi = mmh->sapi; + struct msgb *nmsg; + struct gsm48_mmxx_hdr *nmmh; + + /* reject */ + nmsg = NULL; + switch(msg_type) { + case GSM48_MMCC_EST_REQ: + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_REL_IND, mmh->ref, + mmh->transaction_id, sapi); + break; + case GSM48_MMSS_EST_REQ: + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSS_REL_IND, mmh->ref, + mmh->transaction_id, sapi); + break; + case GSM48_MMSMS_EST_REQ: + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSMS_REL_IND, mmh->ref, + mmh->transaction_id, sapi); + break; + } + if (!nmsg) + return -ENOMEM; + nmmh = (struct gsm48_mmxx_hdr *)nmsg->data; + nmmh->cause = 17; + gsm48_mmxx_upmsg(ms, nmsg); + + return 0; +} + +/* accepting pending connection, got dedicated mode + * + * this function is called: + * - when ciphering command is received + * - when cm service is accepted + */ +static int gsm48_mm_conn_go_dedic(struct osmocom_ms *ms) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_mm_conn *conn, *conn_found = NULL; + struct msgb *nmsg; + struct gsm48_mmxx_hdr *nmmh; + + /* the first and only pending connection is the recent requested */ + llist_for_each_entry(conn, &mm->mm_conn, list) { + if (conn->state == GSM48_MMXX_ST_CONN_PEND) { + conn_found = conn; + break; + } + } + + /* if no pending connection (anymore) */ + if (!conn_found) { + LOGP(DMM, LOGL_INFO, "No pending MM Connection.\n"); + + return 0; + } + + new_conn_state(conn, GSM48_MMXX_ST_DEDICATED); + + /* send establishment confirm */ + nmsg = NULL; + switch(conn_found->protocol) { + case GSM48_PDISC_CC: + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_EST_CNF, + conn_found->ref, conn_found->transaction_id, + conn_found->sapi); + break; + case GSM48_PDISC_NC_SS: + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSS_EST_CNF, + conn_found->ref, conn_found->transaction_id, + conn_found->sapi); + break; + case GSM48_PDISC_SMS: + if (!mm->sapi3_link) { + LOGP(DMM, LOGL_INFO, "Sapi 3 link down, requesting " + "link, waiting for confirm.\n"); + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + return gsm48_mm_to_rr(ms, nmsg, GSM48_RR_EST_REQ, + conn_found->sapi, 0); + } + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMSMS_EST_CNF, + conn_found->ref, conn_found->transaction_id, + conn_found->sapi); + break; + } + if (!nmsg) + return -ENOMEM; + nmmh = (struct gsm48_mmxx_hdr *)nmsg->data; + gsm48_mmxx_upmsg(ms, nmsg); + + return 0; +} + +/* a RR-SYNC-IND is received during MM connection establishment */ +static int gsm48_mm_sync_ind_wait(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *)msg->data; + + if (rrh->cause != RR_SYNC_CAUSE_CIPHERING) { + LOGP(DMM, LOGL_NOTICE, "Ignore sync indication, not waiting " + "for CM service\n"); + return -EINVAL; + } + + /* stop MM connection timer */ + stop_mm_t3230(mm); + + new_mm_state(mm, GSM48_MM_ST_MM_CONN_ACTIVE, 0); + + return gsm48_mm_conn_go_dedic(ms); +} + +/* a RR-SYNC-IND is received during MM connection active */ +static int gsm48_mm_sync_ind_active(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_mm_conn *conn; + struct msgb *nmsg; + struct gsm48_mmxx_hdr *nmmh; + + /* stop MM connection timer */ + stop_mm_t3230(mm); + + /* broadcast all MMCC connection(s) */ + llist_for_each_entry(conn, &mm->mm_conn, list) { + /* send MMCC-SYNC-IND */ + nmsg = NULL; + switch(conn->protocol) { + case GSM48_PDISC_CC: + nmsg = gsm48_mmxx_msgb_alloc(GSM48_MMCC_SYNC_IND, + conn->ref, conn->transaction_id, conn->sapi); + break; + } + if (!nmsg) + continue; /* skip if not of CC type */ + nmmh = (struct gsm48_mmxx_hdr *)nmsg->data; + /* copy L3 message */ + nmsg->l3h = msgb_put(nmsg, msgb_l3len(msg)); + memcpy(nmsg->l3h, msg->l3h, msgb_l3len(msg)); + gsm48_mmxx_upmsg(ms, nmsg); + } + + return 0; +} + +/* 4.5.1.2 RR abort/release is received during MM connection establishment */ +static int gsm48_mm_abort_mm_con(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *)msg->data; + int cause; + + /* stop RR release timer */ + stop_mm_t3240(mm); + + /* this conversion is not of any standard */ + switch(rrh->cause) { + case RR_REL_CAUSE_NOT_AUTHORIZED: + case RR_REL_CAUSE_EMERGENCY_ONLY: + case RR_REL_CAUSE_TRY_LATER: + cause = 21; + break; + case RR_REL_CAUSE_NORMAL: + cause = 16; + break; + default: + cause = 47; + } + + /* stop MM connection timer */ + stop_mm_t3230(mm); + + /* release all connections */ + gsm48_mm_release_mm_conn(ms, 1, cause, 1, 0); + + /* return to MM IDLE */ + return gsm48_mm_return_idle(ms, NULL); +} + +/* 4.5.1.2 timeout is received during MM connection establishment */ +static int gsm48_mm_timeout_mm_con(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + /* release pending connection */ + gsm48_mm_release_mm_conn(ms, 0, 102, 0, 0); + + /* state depends on the existance of remaining MM connections */ + if (llist_empty(&mm->mm_conn)) { + /* start RR release timer */ + start_mm_t3240(mm); + + new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0); + } else + new_mm_state(mm, GSM48_MM_ST_MM_CONN_ACTIVE, 0); + + return 0; +} + +/* respond to paging */ +static int gsm48_mm_est(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + mm->est_cause = RR_EST_CAUSE_ANS_PAG_ANY; + new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0); + + return 0; +} + +/* send CM data */ +static int gsm48_mm_data(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + struct gsm48_mm_conn *conn; + int msg_type = mmh->msg_type; + + /* get connection, if not exist (anymore), release */ + conn = mm_conn_by_ref(mm, mmh->ref); + if (!conn) { + LOGP(DMM, LOGL_INFO, "MMXX_DATA_REQ with unknown (already " + "released) ref=%x, sending MMXX_REL_IND\n", mmh->ref); + switch(msg_type & GSM48_MMXX_MASK) { + case GSM48_MMCC_CLASS: + mmh->msg_type = GSM48_MMCC_REL_IND; + break; + case GSM48_MMSS_CLASS: + mmh->msg_type = GSM48_MMSS_REL_IND; + break; + case GSM48_MMSMS_CLASS: + mmh->msg_type = GSM48_MMSMS_REL_IND; + break; + } + mmh->cause = 31; + + /* mirror message with REL_IND + cause */ + return gsm48_mmxx_upmsg(ms, msg); + } + + /* set SAPI, if upper layer does not do it correctly */ + mmh->sapi = conn->sapi; + + /* pull MM header */ + msgb_pull(msg, sizeof(struct gsm48_mmxx_hdr)); + + /* push RR header and send down */ + return gsm48_mm_to_rr(ms, msg, GSM48_RR_DATA_REQ, conn->sapi, 0); +} + +/* release of MM connection (active state) */ +static int gsm48_mm_release_active(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + struct gsm48_mm_conn *conn; + + /* get connection, if not exist (anymore), release */ + conn = mm_conn_by_ref(mm, mmh->ref); + if (conn) + mm_conn_free(conn); + + /* state depends on the existance of remaining MM connections */ + if (llist_empty(&mm->mm_conn)) { + /* start RR release timer */ + start_mm_t3240(mm); + + new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0); + } else + new_mm_state(mm, GSM48_MM_ST_MM_CONN_ACTIVE, 0); + + return 0; +} + +/* release of MM connection (wait for additional state) */ +static int gsm48_mm_release_wait_add(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + struct gsm48_mm_conn *conn; + + /* get connection, if not exist (anymore), release */ + conn = mm_conn_by_ref(mm, mmh->ref); + if (conn) + mm_conn_free(conn); + + return 0; +} + +/* release of MM connection (wait for active state) */ +static int gsm48_mm_release_wait_active(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + struct gsm48_mm_conn *conn; + + /* get connection, if not exist (anymore), release */ + conn = mm_conn_by_ref(mm, mmh->ref); + if (conn) + mm_conn_free(conn); + + /* 4.5.1.7 if there is no MM connection during wait for active state */ + if (llist_empty(&mm->mm_conn)) { + LOGP(DMM, LOGL_INFO, "No MM Connection during 'wait for " + "active' state.\n"); + + /* start RR release timer */ + start_mm_t3240(mm); + + new_mm_state(mm, GSM48_MM_ST_WAIT_NETWORK_CMD, 0); + + /* send abort */ + return gsm48_mm_tx_cm_service_abort(ms); + } + + return 0; +} + +/* release of MM connection (wait for RR state) */ +static int gsm48_mm_release_wait_rr(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + struct gsm48_mm_conn *conn; + + /* get connection, if not exist (anymore), release */ + conn = mm_conn_by_ref(mm, mmh->ref); + if (conn) + mm_conn_free(conn); + + /* later, if RR connection is established, the CM SERIVE ABORT + * message will be sent + */ + return 0; +} + +/* abort RR connection (due to T3240) */ +static int gsm48_mm_abort_rr(struct osmocom_ms *ms, struct msgb *msg) +{ + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + + /* send abort to RR */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_ABORT_REQ); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *) nmsg->data; + nrrh->cause = GSM48_RR_CAUSE_ABNORMAL_TIMER; + gsm48_rr_downmsg(ms, nmsg); + + /* return to MM IDLE */ + return gsm48_mm_return_idle(ms, NULL); +} + +/* + * other processes + */ + +/* RR is released in other states */ +static int gsm48_mm_rel_other(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + /* stop RR release timer (if running) */ + stop_mm_t3240(mm); + + /* return to MM IDLE */ + return gsm48_mm_return_idle(ms, NULL); +} + +/* + * sapi 3 + */ + +static int gsm48_rcv_rr_sapi3(struct osmocom_ms *ms, struct msgb *msg, + int msg_type, uint8_t sapi) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_mm_conn *conn; + + switch (msg_type) { + case GSM48_RR_EST_CNF: + LOGP(DMM, LOGL_INFO, "SAPI 3 link up, confirming conns.\n"); + mm->sapi3_link = 1; + /* indicate establishment to sapi 3 connections */ + llist_for_each_entry(conn, &mm->mm_conn, list) { + if (conn->sapi == sapi + && conn->state == GSM48_MMXX_ST_DEDICATED) { + struct gsm48_mmxx_hdr *nmmh; + struct msgb *nmsg; + + nmsg = gsm48_mmxx_msgb_alloc( + GSM48_MMSMS_EST_CNF, conn->ref, + conn->transaction_id, conn->sapi); + if (!nmsg) + return -ENOMEM; + nmmh = (struct gsm48_mmxx_hdr *)nmsg->data; + gsm48_mmxx_upmsg(ms, nmsg); + } + } + break; + case GSM48_RR_DATA_IND: + return gsm48_mm_data_ind(ms, msg); + case GSM48_RR_REL_IND: + LOGP(DMM, LOGL_INFO, "SAPI 3 link down, releasing conns.\n"); + mm->sapi3_link = 0; + gsm48_mm_release_mm_conn(ms, 1, 16, 0, sapi); + break; + } + msgb_free(msg); + + return 0; +} + +/* + * state machines + */ + +/* state trasitions for MMxx-SAP messages from upper layers */ +static struct downstate { + uint32_t states; + uint32_t substates; + int type; + int (*rout) (struct osmocom_ms *ms, struct msgb *msg); +} downstatelist[] = { + /* 4.2.2.1 Normal service */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE), + GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE), + GSM48_MMSS_EST_REQ, gsm48_mm_init_mm_no_rr}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE), + GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_no_rr}, + + /* 4.2.2.2 Attempt to update / Loc. Upd. needed */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE) | + SBIT(GSM48_MM_SST_LOC_UPD_NEEDED), + GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr}, /* emergency only */ + + /* 4.2.2.3 Limited service */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE), + GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr}, + + /* 4.2.2.4 No IMSI */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NO_IMSI), + GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr}, + + /* 4.2.2.5 PLMN search, normal service */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL), + GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL), + GSM48_MMSS_EST_REQ, gsm48_mm_init_mm_no_rr}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL), + GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_no_rr}, + + /* 4.2.2.6 PLMN search */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH), + GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_no_rr}, + + /* 4.5.1.1 MM Connection (EST) */ + {SBIT(GSM48_MM_ST_RR_CONN_RELEASE_NA), ALL_STATES, + GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_first}, + + {SBIT(GSM48_MM_ST_RR_CONN_RELEASE_NA), ALL_STATES, + GSM48_MMSS_EST_REQ, gsm48_mm_init_mm_first}, + + {SBIT(GSM48_MM_ST_RR_CONN_RELEASE_NA), ALL_STATES, + GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_first}, + + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES, + GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_more}, + + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES, + GSM48_MMSS_EST_REQ, gsm48_mm_init_mm_more}, + + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES, + GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_more}, + + {SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES, + GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_wait}, + + {SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES, + GSM48_MMSS_EST_REQ, gsm48_mm_init_mm_wait}, + + {SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES, + GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_wait}, + + {ALL_STATES, ALL_STATES, + GSM48_MMCC_EST_REQ, gsm48_mm_init_mm_reject}, + + {ALL_STATES, ALL_STATES, + GSM48_MMSS_EST_REQ, gsm48_mm_init_mm_reject}, + + {ALL_STATES, ALL_STATES, + GSM48_MMSMS_EST_REQ, gsm48_mm_init_mm_reject}, + + /* 4.5.2.1 MM Connection (DATA) */ + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) | + SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES, + GSM48_MMCC_DATA_REQ, gsm48_mm_data}, + + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) | + SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES, + GSM48_MMSS_DATA_REQ, gsm48_mm_data}, + + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) | + SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES, + GSM48_MMSMS_DATA_REQ, gsm48_mm_data}, + + /* 4.5.2.1 MM Connection (REL) */ + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES, + GSM48_MMCC_REL_REQ, gsm48_mm_release_active}, + + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES, + GSM48_MMSS_REL_REQ, gsm48_mm_release_active}, + + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), ALL_STATES, + GSM48_MMSMS_REL_REQ, gsm48_mm_release_active}, + + {SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES, + GSM48_MMCC_REL_REQ, gsm48_mm_release_wait_add}, + + {SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES, + GSM48_MMSS_REL_REQ, gsm48_mm_release_wait_add}, + + {SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES, + GSM48_MMSMS_REL_REQ, gsm48_mm_release_wait_add}, + + {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN), ALL_STATES, + GSM48_MMCC_REL_REQ, gsm48_mm_release_wait_active}, + + {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN), ALL_STATES, + GSM48_MMSS_REL_REQ, gsm48_mm_release_wait_active}, + + {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN), ALL_STATES, + GSM48_MMSMS_REL_REQ, gsm48_mm_release_wait_active}, + + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON), ALL_STATES, + GSM48_MMCC_REL_REQ, gsm48_mm_release_wait_rr}, + + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON), ALL_STATES, + GSM48_MMSS_REL_REQ, gsm48_mm_release_wait_rr}, + + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON), ALL_STATES, + GSM48_MMSMS_REL_REQ, gsm48_mm_release_wait_rr}, +}; + +#define DOWNSLLEN \ + (sizeof(downstatelist) / sizeof(struct downstate)) + +int gsm48_mmxx_downmsg(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_mmxx_hdr *mmh = (struct gsm48_mmxx_hdr *)msg->data; + int msg_type = mmh->msg_type; + struct gsm48_mm_conn *conn; + int i, rc; + + /* keep up to date with the transaction ID */ + conn = mm_conn_by_ref(mm, mmh->ref); + if (conn) + conn->transaction_id = mmh->transaction_id; + + LOGP(DMM, LOGL_INFO, "(ms %s) Received '%s' event in state %s\n", + ms->name, get_mmxx_name(msg_type), + gsm48_mm_state_names[mm->state]); + if (mm->state == GSM48_MM_ST_MM_IDLE) + LOGP(DMM, LOGL_INFO, "-> substate %s\n", + gsm48_mm_substate_names[mm->substate]); + LOGP(DMM, LOGL_INFO, "-> callref %x, transaction_id %d\n", + mmh->ref, mmh->transaction_id); + + /* Find function for current state and message */ + for (i = 0; i < DOWNSLLEN; i++) + if ((msg_type == downstatelist[i].type) + && ((1 << mm->state) & downstatelist[i].states) + && ((1 << mm->substate) & downstatelist[i].substates)) + break; + if (i == DOWNSLLEN) { + LOGP(DMM, LOGL_NOTICE, "Message unhandled at this state.\n"); + msgb_free(msg); + return 0; + } + + rc = downstatelist[i].rout(ms, msg); + + if (downstatelist[i].rout != gsm48_mm_data) + msgb_free(msg); + + return rc; +} + +/* state trasitions for radio ressource messages (lower layer) */ +static struct rrdatastate { + uint32_t states; + int type; + int (*rout) (struct osmocom_ms *ms, struct msgb *msg); +} rrdatastatelist[] = { + /* paging */ + {SBIT(GSM48_MM_ST_MM_IDLE), + GSM48_RR_EST_IND, gsm48_mm_est}, + + /* imsi detach */ + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_IMSI_D), /* 4.3.4.4 */ + GSM48_RR_EST_CNF, gsm48_mm_imsi_detach_sent}, + + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_IMSI_D), /* 4.3.4.4 (unsuc.) */ + GSM48_RR_REL_IND, gsm48_mm_imsi_detach_end}, + /* also this may happen if SABM is ackwnowledged with DISC */ + + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_IMSI_D), /* 4.3.4.4 (lost) */ + GSM48_RR_ABORT_IND, gsm48_mm_imsi_detach_end}, + + {SBIT(GSM48_MM_ST_IMSI_DETACH_INIT), /* 4.3.4.4 (unsuc.) */ + GSM48_RR_REL_IND, gsm48_mm_imsi_detach_end}, + + {SBIT(GSM48_MM_ST_IMSI_DETACH_INIT), /* 4.3.4.4 (lost) */ + GSM48_RR_ABORT_IND, gsm48_mm_imsi_detach_end}, + + /* location update */ + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_LUPD), /* 4.4.4.1 */ + GSM48_RR_EST_CNF, gsm48_mm_est_loc_upd}, + + {SBIT(GSM48_MM_ST_LOC_UPD_INIT) | + SBIT(GSM48_MM_ST_WAIT_RR_CONN_LUPD), /* 4.4.4.9 */ + GSM48_RR_REL_IND, gsm48_mm_rel_loc_upd_abort}, + + {SBIT(GSM48_MM_ST_LOC_UPD_INIT) | + SBIT(GSM48_MM_ST_WAIT_RR_CONN_LUPD), /* 4.4.4.9 */ + GSM48_RR_ABORT_IND, gsm48_mm_rel_loc_upd_abort}, + + {SBIT(GSM48_MM_ST_LOC_UPD_REJ), /* 4.4.4.7 */ + GSM48_RR_REL_IND, gsm48_mm_rel_loc_upd_rej}, + + {SBIT(GSM48_MM_ST_LOC_UPD_REJ), /* 4.4.4.7 */ + GSM48_RR_ABORT_IND, gsm48_mm_rel_loc_upd_rej}, + + /* MM connection (EST) */ + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON), /* 4.5.1.1 */ + GSM48_RR_EST_CNF, gsm48_mm_est_mm_con}, + + /* MM connection (DATA) */ + {ALL_STATES, + GSM48_RR_DATA_IND, gsm48_mm_data_ind}, + + /* MM connection (SYNC) */ + {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN) | + SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), /* 4.5.1.1 */ + GSM48_RR_SYNC_IND, gsm48_mm_sync_ind_wait}, + + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), + GSM48_RR_SYNC_IND, gsm48_mm_sync_ind_active}, + + /* MM connection (REL/ABORT) */ + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON) | + SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN) | + SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), /* 4.5.1.2 */ + GSM48_RR_REL_IND, gsm48_mm_abort_mm_con}, + + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_MM_CON) | + SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN) | + SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), /* 4.5.1.2 */ + GSM48_RR_ABORT_IND, gsm48_mm_abort_mm_con}, + + /* MM connection (REL/ABORT with re-establishment possibility) */ + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE), /* not supported */ + GSM48_RR_REL_IND, gsm48_mm_abort_mm_con}, + + {SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) | + SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), /* not supported */ + GSM48_RR_ABORT_IND, gsm48_mm_abort_mm_con}, + + /* other (also wait for network command) */ + {ALL_STATES, + GSM48_RR_REL_IND, gsm48_mm_rel_other}, + + {ALL_STATES, + GSM48_RR_ABORT_IND, gsm48_mm_rel_other}, +}; + +#define RRDATASLLEN \ + (sizeof(rrdatastatelist) / sizeof(struct rrdatastate)) + +static int gsm48_rcv_rr(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *)msg->data; + int msg_type = rrh->msg_type; + int sapi = rrh->sapi; + int i, rc; + + LOGP(DMM, LOGL_INFO, "(ms %s) Received '%s' from RR in state %s " + "(sapi %d)\n", ms->name, get_rr_name(msg_type), + gsm48_mm_state_names[mm->state], sapi); + + if (sapi) + return gsm48_rcv_rr_sapi3(ms, msg, msg_type, sapi); + + /* find function for current state and message */ + for (i = 0; i < RRDATASLLEN; i++) + if ((msg_type == rrdatastatelist[i].type) + && ((1 << mm->state) & rrdatastatelist[i].states)) + break; + if (i == RRDATASLLEN) { + LOGP(DMM, LOGL_NOTICE, "Message unhandled at this state.\n"); + msgb_free(msg); + return 0; + } + + rc = rrdatastatelist[i].rout(ms, msg); + + if (rrdatastatelist[i].rout != gsm48_mm_data_ind) + msgb_free(msg); + + return rc; +} + +/* state trasitions for mobile managemnt messages (lower layer) */ +static struct mmdatastate { + uint32_t states; + int type; + int (*rout) (struct osmocom_ms *ms, struct msgb *msg); +} mmdatastatelist[] = { + {ALL_STATES, /* 4.3.1.2 */ + GSM48_MT_MM_TMSI_REALL_CMD, gsm48_mm_rx_tmsi_realloc_cmd}, + + {ALL_STATES, /* 4.3.2.2 */ + GSM48_MT_MM_AUTH_REQ, gsm48_mm_rx_auth_req}, + + {ALL_STATES, /* 4.3.2.5 */ + GSM48_MT_MM_AUTH_REJ, gsm48_mm_rx_auth_rej}, + + {ALL_STATES, /* 4.3.3.2 */ + GSM48_MT_MM_ID_REQ, gsm48_mm_rx_id_req}, + + {ALL_STATES, /* 4.3.5.2 */ + GSM48_MT_MM_ABORT, gsm48_mm_rx_abort}, + + {ALL_STATES, /* 4.3.6.2 */ + GSM48_MT_MM_INFO, gsm48_mm_rx_info}, + + {SBIT(GSM48_MM_ST_LOC_UPD_INIT), /* 4.4.4.6 */ + GSM48_MT_MM_LOC_UPD_ACCEPT, gsm48_mm_rx_loc_upd_acc}, + + {SBIT(GSM48_MM_ST_LOC_UPD_INIT), /* 4.4.4.7 */ + GSM48_MT_MM_LOC_UPD_REJECT, gsm48_mm_rx_loc_upd_rej}, + + {ALL_STATES, /* 4.5.1.1 */ + GSM48_MT_MM_CM_SERV_ACC, gsm48_mm_rx_cm_service_acc}, + + {ALL_STATES, /* 4.5.1.1 */ + GSM48_MT_MM_CM_SERV_REJ, gsm48_mm_rx_cm_service_rej}, +}; + +#define MMDATASLLEN \ + (sizeof(mmdatastatelist) / sizeof(struct mmdatastate)) + +static int gsm48_mm_data_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *)msg->data; + int sapi = rrh->sapi; + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t pdisc = gh->proto_discr & 0x0f; + uint8_t msg_type = gh->msg_type & 0xbf; + struct gsm48_mmxx_hdr *mmh; + int msg_supported = 0; /* determine, if message is supported at all */ + int rr_prim = -1, rr_est = -1; /* no prim set */ + uint8_t skip_ind; + int i, rc; + + /* 9.2.19 */ + if (msg_type == GSM48_MT_MM_NULL) { + msgb_free(msg); + return 0; + } + + if (mm->state == GSM48_MM_ST_IMSI_DETACH_INIT) { + LOGP(DMM, LOGL_NOTICE, "DATA IND ignored during IMSI " + "detach.\n"); + msgb_free(msg); + return 0; + } + /* pull the RR header */ + msgb_pull(msg, sizeof(struct gsm48_rr_hdr)); + + /* create transaction (if not exists) and push message */ + switch (pdisc) { + case GSM48_PDISC_CC: + rr_prim = GSM48_MMCC_DATA_IND; + rr_est = GSM48_MMCC_EST_IND; + break; + case GSM48_PDISC_NC_SS: + rr_prim = GSM48_MMSS_DATA_IND; + rr_est = GSM48_MMSS_EST_IND; + break; + case GSM48_PDISC_SMS: + rr_prim = GSM48_MMSMS_DATA_IND; + rr_est = GSM48_MMSMS_EST_IND; + break; + } + if (rr_prim != -1) { + uint8_t transaction_id = ((gh->proto_discr & 0xf0) ^ 0x80) >> 4; + /* flip */ + struct gsm48_mm_conn *conn; + + /* find transaction, if any */ + conn = mm_conn_by_id(mm, pdisc, transaction_id); + + /* create MM connection instance */ + if (!conn) { + conn = mm_conn_new(mm, pdisc, transaction_id, sapi, + mm_conn_new_ref++); + rr_prim = rr_est; + } + if (!conn) { + msgb_free(msg); + return -ENOMEM; + } + + /* push new header */ + msgb_push(msg, sizeof(struct gsm48_mmxx_hdr)); + mmh = (struct gsm48_mmxx_hdr *)msg->data; + mmh->msg_type = rr_prim; + mmh->ref = conn->ref; + mmh->transaction_id = conn->transaction_id; + mmh->sapi = conn->sapi; + + /* go MM CONN ACTIVE state */ + if (mm->state == GSM48_MM_ST_WAIT_NETWORK_CMD + || mm->state == GSM48_MM_ST_RR_CONN_RELEASE_NA) { + /* stop RR release timer */ + stop_mm_t3240(mm); + + /* stop "RR connection release not allowed" timer */ + stop_mm_t3241(mm); + + new_mm_state(mm, GSM48_MM_ST_MM_CONN_ACTIVE, 0); + } + } + + /* forward message */ + switch (pdisc) { + case GSM48_PDISC_MM: + skip_ind = (gh->proto_discr & 0xf0) >> 4; + + /* ignore if skip indicator is not B'0000' */ + if (skip_ind) { + msgb_free(msg); + return 0; + } + break; /* follow the selection proceedure below */ + + case GSM48_PDISC_CC: + rc = gsm48_rcv_cc(ms, msg); + msgb_free(msg); + return rc; + + case GSM48_PDISC_NC_SS: + rc = gsm480_rcv_ss(ms, msg); + msgb_free(msg); + return rc; + + case GSM48_PDISC_SMS: + rc = gsm411_rcv_sms(ms, msg); + msgb_free(msg); + return rc; + + case 0x0f: /* test TS 04.14 */ + LOGP(DMM, LOGL_NOTICE, "Test protocol 0x%02x according to " + "TS 04.14 is not supported.\n", pdisc); + goto status; + default: + LOGP(DMM, LOGL_NOTICE, "Protocol type 0x%02x unsupported.\n", + pdisc); +status: + msgb_free(msg); + return gsm48_mm_tx_mm_status(ms, + GSM48_REJECT_MSG_TYPE_NOT_IMPLEMENTED); + } + + LOGP(DMM, LOGL_INFO, "(ms %s) Received '%s' in MM state %s\n", ms->name, + get_mm_name(msg_type), gsm48_mm_state_names[mm->state]); + + stop_mm_t3212(mm); /* 4.4.2 */ + + /* 11.2 re-start pending RR release timer */ + if (osmo_timer_pending(&mm->t3240)) { + stop_mm_t3240(mm); + start_mm_t3240(mm); + } + + /* find function for current state and message */ + for (i = 0; i < MMDATASLLEN; i++) { + if (msg_type == mmdatastatelist[i].type) + msg_supported = 1; + if ((msg_type == mmdatastatelist[i].type) + && ((1 << mm->state) & mmdatastatelist[i].states)) + break; + } + if (i == MMDATASLLEN) { + msgb_free(msg); + if (msg_supported) { + LOGP(DMM, LOGL_NOTICE, "Message unhandled at this " + "state.\n"); + return gsm48_mm_tx_mm_status(ms, + GSM48_REJECT_MSG_TYPE_NOT_COMPATIBLE); + } else { + LOGP(DMM, LOGL_NOTICE, "Message not supported.\n"); + return gsm48_mm_tx_mm_status(ms, + GSM48_REJECT_MSG_TYPE_NOT_IMPLEMENTED); + } + } + + rc = mmdatastatelist[i].rout(ms, msg); + + msgb_free(msg); + + return rc; +} + +/* state trasitions for mobile management events */ +static struct eventstate { + uint32_t states; + uint32_t substates; + int type; + int (*rout) (struct osmocom_ms *ms, struct msgb *msg); +} eventstatelist[] = { + /* 4.2.3 return to MM IDLE */ + {ALL_STATES - SBIT(GSM48_MM_ST_MM_IDLE), ALL_STATES, + GSM48_MM_EVENT_NO_CELL_FOUND, gsm48_mm_no_cell_found}, + + {ALL_STATES - SBIT(GSM48_MM_ST_MM_IDLE), ALL_STATES, + GSM48_MM_EVENT_CELL_SELECTED, gsm48_mm_return_idle}, + + /* 4.2.2.1 Normal service */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE), + GSM48_MM_EVENT_USER_PLMN_SEL, gsm48_mm_plmn_search}, /* 4.2.1.2 */ + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE), + GSM48_MM_EVENT_LOST_COVERAGE, gsm48_mm_plmn_search}, /* 4.2.1.2 */ + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE), + GSM48_MM_EVENT_CELL_SELECTED, gsm48_mm_loc_upd_normal}, /* change */ + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE), + GSM48_MM_EVENT_TIMEOUT_T3211, gsm48_mm_loc_upd}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE), + GSM48_MM_EVENT_TIMEOUT_T3213, gsm48_mm_loc_upd}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE), + GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_periodic}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NORMAL_SERVICE), + GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_start}, + + /* 4.2.2.2 Attempt to update / Loc. upd. needed */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE) | + SBIT(GSM48_MM_SST_LOC_UPD_NEEDED), + GSM48_MM_EVENT_USER_PLMN_SEL, gsm48_mm_plmn_search}, /* 4.2.1.2 */ + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE) | + SBIT(GSM48_MM_SST_LOC_UPD_NEEDED), + GSM48_MM_EVENT_LOST_COVERAGE, gsm48_mm_plmn_search}, /* 4.2.1.2 */ + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE) | + SBIT(GSM48_MM_SST_LOC_UPD_NEEDED), + GSM48_MM_EVENT_CELL_SELECTED, gsm48_mm_loc_upd_normal}, /* change */ + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE), + GSM48_MM_EVENT_TIMEOUT_T3211, gsm48_mm_loc_upd}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE), + GSM48_MM_EVENT_TIMEOUT_T3213, gsm48_mm_loc_upd}, + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_ATTEMPT_UPDATE), + GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_periodic}, + + /* 4.2.2.3 Limited service */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE), + GSM48_MM_EVENT_USER_PLMN_SEL, gsm48_mm_plmn_search}, /* 4.2.1.2 */ + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE), + GSM48_MM_EVENT_LOST_COVERAGE, gsm48_mm_plmn_search}, /* 4.2.1.2 */ + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE), + GSM48_MM_EVENT_CELL_SELECTED, gsm48_mm_loc_upd_normal}, /* if allow. */ + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_LIMITED_SERVICE), + GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_delay_per}, /* 4.4.2 */ + + /* 4.2.2.4 No IMSI */ + /* 4.2.2.5 PLMN search, normal service */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL), + GSM48_MM_EVENT_NO_CELL_FOUND, gsm48_mm_no_cell_found}, /* 4.2.1.1 */ + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL), + GSM48_MM_EVENT_CELL_SELECTED, gsm48_mm_cell_selected}, /* 4.2.1.1 */ + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL), + GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_delay_per}, /* 4.4.2 */ + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH_NORMAL), + GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_start}, + + /* 4.2.2.6 PLMN search */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH), + GSM48_MM_EVENT_NO_CELL_FOUND, gsm48_mm_no_cell_found}, /* 4.2.1.1 */ + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH), + GSM48_MM_EVENT_CELL_SELECTED, gsm48_mm_cell_selected}, /* 4.2.1.1 */ + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_PLMN_SEARCH), + GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_delay_per}, /* 4.4.2 */ + + /* No cell available */ + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NO_CELL_AVAIL), + GSM48_MM_EVENT_CELL_SELECTED, gsm48_mm_cell_selected}, /* 4.2.1.1 */ + + {SBIT(GSM48_MM_ST_MM_IDLE), SBIT(GSM48_MM_SST_NO_CELL_AVAIL), + GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_delay_per}, /* 4.4.2 */ + + /* IMSI detach in other cases */ + {SBIT(GSM48_MM_ST_MM_IDLE), ALL_STATES, /* silently detach */ + GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_end}, + + {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN) | + SBIT(GSM48_MM_ST_MM_CONN_ACTIVE) | + SBIT(GSM48_MM_ST_PROCESS_CM_SERV_P) | + SBIT(GSM48_MM_ST_WAIT_REEST) | + SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON) | + SBIT(GSM48_MM_ST_MM_CONN_ACTIVE_VGCS) | + SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD), ALL_STATES, /* we can release */ + GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_release}, + + {SBIT(GSM48_MM_ST_WAIT_RR_CONN_IMSI_D) | + SBIT(GSM48_MM_ST_IMSI_DETACH_INIT) | + SBIT(GSM48_MM_ST_IMSI_DETACH_PEND), ALL_STATES, /* ignore */ + GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_ignore}, + + {ALL_STATES, ALL_STATES, + GSM48_MM_EVENT_IMSI_DETACH, gsm48_mm_imsi_detach_delay}, + + {SBIT(GSM48_MM_ST_IMSI_DETACH_INIT), ALL_STATES, + GSM48_MM_EVENT_TIMEOUT_T3220, gsm48_mm_imsi_detach_abort}, + + /* location update in other cases */ + {SBIT(GSM48_MM_ST_MM_IDLE), ALL_STATES, + GSM48_MM_EVENT_TIMEOUT_T3211, gsm48_mm_loc_upd_delay_retry}, + + {SBIT(GSM48_MM_ST_MM_IDLE), ALL_STATES, + GSM48_MM_EVENT_TIMEOUT_T3213, gsm48_mm_loc_upd_delay_retry}, + + {ALL_STATES, ALL_STATES, + GSM48_MM_EVENT_TIMEOUT_T3212, gsm48_mm_loc_upd_ignore}, + + {ALL_STATES - SBIT(GSM48_MM_ST_MM_IDLE), ALL_STATES, + GSM48_MM_EVENT_TIMEOUT_T3210, gsm48_mm_loc_upd_timeout}, + + {ALL_STATES - SBIT(GSM48_MM_ST_MM_IDLE), ALL_STATES, + GSM48_MM_EVENT_TIMEOUT_T3213, gsm48_mm_loc_upd_failed}, + /* 4.4.4.9 c) (but without retry) */ + + /* SYSINFO event */ + {ALL_STATES, ALL_STATES, + GSM48_MM_EVENT_SYSINFO, gsm48_mm_sysinfo}, + + /* T3240 timed out */ + {SBIT(GSM48_MM_ST_WAIT_NETWORK_CMD) | + SBIT(GSM48_MM_ST_LOC_UPD_REJ), ALL_STATES, /* 4.4.4.8 */ + GSM48_MM_EVENT_TIMEOUT_T3240, gsm48_mm_abort_rr}, + + /* T3230 timed out */ + {SBIT(GSM48_MM_ST_WAIT_OUT_MM_CONN) | + SBIT(GSM48_MM_ST_WAIT_ADD_OUT_MM_CON), ALL_STATES, + GSM48_MM_EVENT_TIMEOUT_T3230, gsm48_mm_timeout_mm_con}, + + /* SIM reports SRES */ + {ALL_STATES, ALL_STATES, /* 4.3.2.2 */ + GSM48_MM_EVENT_AUTH_RESPONSE, gsm48_mm_tx_auth_rsp}, + +#if 0 + /* change in classmark is reported */ + {ALL_STATES, ALL_STATES, + GSM48_MM_EVENT_CLASSMARK_CHG, gsm48_mm_classm_chg}, +#endif +}; + +#define EVENTSLLEN \ + (sizeof(eventstatelist) / sizeof(struct eventstate)) + +static int gsm48_mm_ev(struct osmocom_ms *ms, int msg_type, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + int i, rc; + + if (mm->state == GSM48_MM_ST_MM_IDLE) { + if (msg_type != GSM48_MM_EVENT_SYSINFO) + LOGP(DMM, LOGL_INFO, "(ms %s) Received '%s' event in " + "state MM IDLE, %s\n", ms->name, + get_mmevent_name(msg_type), + gsm48_mm_substate_names[mm->substate]); + } else + LOGP(DMM, LOGL_INFO, "(ms %s) Received '%s' event in state " + "%s\n", ms->name, get_mmevent_name(msg_type), + gsm48_mm_state_names[mm->state]); + + /* Find function for current state and message */ + for (i = 0; i < EVENTSLLEN; i++) + if ((msg_type == eventstatelist[i].type) + && ((1 << mm->state) & eventstatelist[i].states) + && ((1 << mm->substate) & eventstatelist[i].substates)) + break; + if (i == EVENTSLLEN) { + LOGP(DMM, LOGL_NOTICE, "Message unhandled at this state.\n"); + return 0; + } + + rc = eventstatelist[i].rout(ms, msg); + + return rc; +} + +/* + * MM Register (SIM insert and remove) + */ + +/* register new SIM card and trigger attach */ +static int gsm48_mmr_reg_req(struct osmocom_ms *ms) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct msgb *nmsg; + + /* schedule insertion of SIM */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_SIM_INSERT); + if (!nmsg) + return -ENOMEM; + gsm322_plmn_sendmsg(ms, nmsg); + + /* 4.2.1.2 SIM is inserted in state NO IMSI */ + if (mm->state == GSM48_MM_ST_MM_IDLE + && mm->substate == GSM48_MM_SST_NO_IMSI) + new_mm_state(mm, GSM48_MM_ST_MM_IDLE, + gsm48_mm_set_plmn_search(ms)); + + return 0; +} + +/* trigger detach of sim card */ +static int gsm48_mmr_nreg_req(struct osmocom_ms *ms) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct msgb *nmsg; + + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_IMSI_DETACH); + if (!nmsg) + return -ENOMEM; + gsm48_mmevent_msg(mm->ms, nmsg); + + return 0; +} + +static int gsm48_rcv_mmr(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmr *mmr = (struct gsm48_mmr *)msg->data; + int msg_type = mmr->msg_type; + int rc = 0; + + LOGP(DMM, LOGL_INFO, "(ms %s) Received '%s' event\n", ms->name, + get_mmr_name(msg_type)); + switch(msg_type) { + case GSM48_MMR_REG_REQ: + rc = gsm48_mmr_reg_req(ms); + break; + case GSM48_MMR_NREG_REQ: + rc = gsm48_mmr_nreg_req(ms); + break; + default: + LOGP(DMM, LOGL_NOTICE, "Message unhandled.\n"); + } + + return rc; +} + + diff --git a/src/host/layer23/src/mobile/gsm48_rr.c b/src/host/layer23/src/mobile/gsm48_rr.c new file mode 100644 index 00000000..36488606 --- /dev/null +++ b/src/host/layer23/src/mobile/gsm48_rr.c @@ -0,0 +1,5663 @@ +/* + * (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +/* Very short description of some of the procedures: + * + * A radio ressource request causes sendig a channel request on RACH. + * After receiving of an immediate assignment the link will be establised. + * After the link is established, the dedicated mode is entered and confirmed. + * + * A Paging request also triggers the channel request as above... + * After the link is established, the dedicated mode is entered and indicated. + * + * During dedicated mode, messages are transferred. + * + * When an assignment command or a handover command is received, the current + * link is released. After release, the new channel is activated and the + * link is established again. After link is establised, pending messages from + * radio ressource are sent. + * + * When the assignment or handover fails, the old channel is activate and the + * link is established again. Also pending messages are sent. + * + */ + +/* Testing delayed (immediate) assigment / handover + * + * When enabled, the starting time will be set by given frames in the future. + * If a starting time is given by the network, this time is ignored. + */ +//#define TEST_STARTING_TIMER 140 + +/* Testing if frequency modification works correctly "after time". + * + * When enabled, the starting time will be set in the future. + * A wrong channel is defined "before time", so noise is received until + * starting time elapses. + * If a starting time is given by the network, this time is ignored. + * Also channel definitions "before time" are ignored. + * + * NOTE: TEST_STARTING_TIMER MUST be defined also. + */ +//#define TEST_FREQUENCY_MOD + +#include <stdint.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <arpa/inet.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/rsl.h> +#include <osmocom/gsm/gsm48.h> +#include <osmocom/core/bitvec.h> + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/l1l2_interface.h> +#include <osmocom/bb/common/l23_app.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/networks.h> +#include <osmocom/bb/common/l1ctl.h> +#include <osmocom/bb/mobile/vty.h> + +#include <l1ctl_proto.h> + +static void start_rr_t_meas(struct gsm48_rrlayer *rr, int sec, int micro); +static void stop_rr_t_starting(struct gsm48_rrlayer *rr); +static void stop_rr_t3124(struct gsm48_rrlayer *rr); +static int gsm48_rcv_rsl(struct osmocom_ms *ms, struct msgb *msg); +static int gsm48_rr_dl_est(struct osmocom_ms *ms); +static int gsm48_rr_tx_meas_rep(struct osmocom_ms *ms); +static int gsm48_rr_set_mode(struct osmocom_ms *ms, uint8_t chan_nr, + uint8_t mode); +static int gsm48_rr_rel_cnf(struct osmocom_ms *ms, struct msgb *msg); + +/* + * support + */ + +#define MIN(a, b) ((a < b) ? a : b) + +/* decode "Power Command" (10.5.2.28) and (10.5.2.28a) */ +static int gsm48_decode_power_cmd_acc(struct gsm48_power_cmd *pc, + uint8_t *power_level, uint8_t *atc) +{ + *power_level = pc->power_level; + if (atc) /* only in case of 10.5.2.28a */ + *atc = pc->atc; + + return 0; +} + +/* 10.5.2.38 decode Starting time IE */ +static int gsm48_decode_start_time(struct gsm48_rr_cd *cd, + struct gsm48_start_time *st) +{ + cd->start = 1; + cd->start_tm.t1 = st->t1; + cd->start_tm.t2 = st->t2; + cd->start_tm.t3 = (st->t3_high << 3) | st->t3_low; + cd->start_tm.fn = gsm_gsmtime2fn(&cd->start_tm); + + return 0; +} + +/* decode "BA Range" (10.5.2.1a) */ +static int gsm48_decode_ba_range(const uint8_t *ba, uint8_t ba_len, + uint32_t *range, uint8_t *ranges, int max_ranges) +{ + /* ba = pointer to IE without IE type and length octets + * ba_len = number of octets + * range = pointer to store decoded range + * ranges = number of ranges decoded + * max_ranges = maximum number of decoded ranges that can be stored + */ + uint16_t lower, higher; + int i, n, required_octets; + + /* find out how much ba ranges will be decoded */ + n = *ba++; + ba_len --; + required_octets = 5 * (n >> 1) + 3 * (n & 1); + if (required_octets > ba_len) { + LOGP(DRR, LOGL_NOTICE, "BA range IE too short: %d ranges " + "require %d octets, but only %d octets remain.\n", + n, required_octets, ba_len); + *ranges = 0; + return -EINVAL; + } + if (max_ranges > n) + LOGP(DRR, LOGL_NOTICE, "BA range %d exceed the maximum number " + "of ranges supported by this mobile (%d).\n", + n, max_ranges); + n = max_ranges; + + /* decode ranges */ + for (i = 0; i < n; i++) { + if (!(i & 1)) { + /* decode even range number */ + lower = *ba++ << 2; + lower |= (*ba >> 6); + higher = (*ba++ & 0x3f) << 4; + higher |= *ba >> 4; + } else { + lower = (*ba++ & 0x0f) << 6; + lower |= *ba >> 2; + higher = (*ba++ & 0x03) << 8; + higher |= *ba++; + /* decode odd range number */ + } + *range++ = (higher << 16) | lower; + } + *ranges = n; + + return 0; +} + +/* decode "Cell Description" (10.5.2.2) */ +static int gsm48_decode_cell_desc(struct gsm48_cell_desc *cd, uint16_t *arfcn, + uint8_t *ncc, uint8_t *bcc) +{ + *arfcn = (cd->arfcn_hi << 8) + cd->arfcn_lo; + *ncc = cd->ncc; + *bcc = cd->bcc; + + return 0; +} + +/* decode "Synchronization Indication" (10.5.2.39) */ +static int gsm48_decode_sync_ind(struct gsm48_rrlayer *rr, + struct gsm48_sync_ind *si) +{ + rr->hando_sync_ind = si->si; + rr->hando_rot = si->rot; + rr->hando_nci = si->nci; + + return 0; +} + +/* 3.1.4.3 set sequence number and increment */ +static int gsm48_apply_v_sd(struct gsm48_rrlayer *rr, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t pdisc = gh->proto_discr & 0x0f; + uint8_t v_sd; + + switch (pdisc) { + case GSM48_PDISC_MM: + case GSM48_PDISC_CC: + case GSM48_PDISC_NC_SS: + /* all thre pdiscs share the same V(SD) */ + pdisc = GSM48_PDISC_MM; + // fall through + case GSM48_PDISC_GROUP_CC: + case GSM48_PDISC_BCAST_CC: + case GSM48_PDISC_PDSS1: + case GSM48_PDISC_PDSS2: + /* extract v_sd(pdisc) */ + v_sd = (rr->v_sd >> pdisc) & 1; + + /* replace bit 7 vy v_sd */ + gh->msg_type &= 0xbf; + gh->msg_type |= (v_sd << 6); + + /* increment V(SD) */ + rr->v_sd ^= (1 << pdisc); + LOGP(DRR, LOGL_INFO, "Using and incrementing V(SD) = %d " + "(pdisc %x)\n", v_sd, pdisc); + break; + case GSM48_PDISC_RR: + case GSM48_PDISC_SMS: + /* no V(VSD) is required */ + break; + default: + LOGP(DRR, LOGL_ERROR, "Error, V(SD) of pdisc %x not handled\n", + pdisc); + return -ENOTSUP; + } + + return 0; +} + +/* set channel mode if supported, or return error cause */ +static uint8_t gsm48_rr_check_mode(struct osmocom_ms *ms, uint8_t chan_nr, + uint8_t mode) +{ + struct gsm_settings *set = &ms->settings; + uint8_t ch_type, ch_subch, ch_ts; + + /* only complain if we use TCH/F or TCH/H */ + rsl_dec_chan_nr(chan_nr, &ch_type, &ch_subch, &ch_ts); + if (ch_type != RSL_CHAN_Bm_ACCHs + && ch_type != RSL_CHAN_Lm_ACCHs) + return 0; + + switch (mode) { + case GSM48_CMODE_SIGN: + LOGP(DRR, LOGL_INFO, "Mode: signalling\n"); + break; + case GSM48_CMODE_SPEECH_V1: + if (ch_type == RSL_CHAN_Bm_ACCHs) { + if (!set->full_v1) { + LOGP(DRR, LOGL_NOTICE, "Not supporting " + "full-rate speech V1\n"); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } + LOGP(DRR, LOGL_INFO, "Mode: full-rate speech V1\n"); + } else { + if (!set->half_v1) { + LOGP(DRR, LOGL_NOTICE, "Not supporting " + "half-rate speech V1\n"); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } + LOGP(DRR, LOGL_INFO, "Mode: half-rate speech V1\n"); + } + break; + case GSM48_CMODE_SPEECH_EFR: + if (ch_type == RSL_CHAN_Bm_ACCHs) { + if (!set->full_v2) { + LOGP(DRR, LOGL_NOTICE, "Not supporting " + "full-rate speech V2\n"); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } + LOGP(DRR, LOGL_INFO, "Mode: full-rate speech V2\n"); + } else { + LOGP(DRR, LOGL_NOTICE, "Not supporting " + "half-rate speech V2\n"); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } + break; + case GSM48_CMODE_SPEECH_AMR: + if (ch_type == RSL_CHAN_Bm_ACCHs) { + if (!set->full_v3) { + LOGP(DRR, LOGL_NOTICE, "Not supporting " + "full-rate speech V3\n"); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } + LOGP(DRR, LOGL_INFO, "Mode: full-rate speech V3\n"); + } else { + if (!set->half_v3) { + LOGP(DRR, LOGL_NOTICE, "Not supporting " + "half-rate speech V3\n"); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } + LOGP(DRR, LOGL_INFO, "Mode: half-rate speech V3\n"); + } + break; + default: + LOGP(DRR, LOGL_ERROR, "Mode 0x%02x not supported!\n", mode); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCT; + } + + return 0; +} + +/* apply new "alter_delay" in dedicated mode */ +int gsm48_rr_alter_delay(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm_settings *set = &rr->ms->settings; + + if (rr->state != GSM48_RR_ST_DEDICATED) + return -EINVAL; + l1ctl_tx_param_req(ms, rr->cd_now.ind_ta - set->alter_delay, + (set->alter_tx_power) ? set->alter_tx_power_value + : rr->cd_now.ind_tx_power); + + return 0; +} + +/* + * state transition + */ + +const char *gsm48_rr_state_names[] = { + "idle", + "connection pending", + "dedicated", + "release pending", +}; + +static void new_rr_state(struct gsm48_rrlayer *rr, int state) +{ + if (state < 0 || state >= + (sizeof(gsm48_rr_state_names) / sizeof(char *))) + return; + + /* must check against equal state */ + if (rr->state == state) { + LOGP(DRR, LOGL_INFO, "equal state ? %s\n", + gsm48_rr_state_names[rr->state]); + return; + } + + LOGP(DRR, LOGL_INFO, "new state %s -> %s\n", + gsm48_rr_state_names[rr->state], gsm48_rr_state_names[state]); + + /* abort handover, in case of release of dedicated mode */ + if (rr->state == GSM48_RR_ST_DEDICATED) { + /* disable handover / assign state */ + rr->modify_state = GSM48_RR_MOD_NONE; + /* stop start_time_timer */ + stop_rr_t_starting(rr); + /* stop handover timer */ + stop_rr_t3124(rr); + } + + rr->state = state; + + if (state == GSM48_RR_ST_IDLE) { + struct msgb *msg, *nmsg; + struct gsm322_msg *em; + + /* release dedicated mode, if any */ + l1ctl_tx_dm_rel_req(rr->ms); + rr->ms->meas.rl_fail = 0; + rr->dm_est = 0; + l1ctl_tx_reset_req(rr->ms, L1CTL_RES_T_FULL); + /* free establish message, if any */ + rr->rr_est_req = 0; + if (rr->rr_est_msg) { + msgb_free(rr->rr_est_msg); + rr->rr_est_msg = NULL; + } + /* free all pending messages */ + while((msg = msgb_dequeue(&rr->downqueue))) + msgb_free(msg); + /* clear all descriptions of last channel */ + memset(&rr->cd_now, 0, sizeof(rr->cd_now)); + /* reset ciphering */ + rr->cipher_on = 0; + /* reset audio mode */ + /* tell cell selection process to return to idle mode + * NOTE: this must be sent unbuffered, because it will + * leave camping state, so it locks against subsequent + * establishment of dedicated channel, before the + * cell selection process returned to camping state + * again. (after cell reselection) + */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_RET_IDLE); + if (!nmsg) + return; + /* return to same cell after LOC.UPD. */ + if (rr->est_cause == RR_EST_CAUSE_LOC_UPD) { + em = (struct gsm322_msg *) nmsg->data; + em->same_cell = 1; + } + gsm322_c_event(rr->ms, nmsg); + msgb_free(nmsg); + /* reset any BA range */ + rr->ba_ranges = 0; + } +} + +const char *gsm48_sapi3_state_names[] = { + "idle", + "wait establishment", + "established", + "wait release", +}; + +static void new_sapi3_state(struct gsm48_rrlayer *rr, int state) +{ + if (state < 0 || state >= + (sizeof(gsm48_sapi3_state_names) / sizeof(char *))) + return; + + LOGP(DRR, LOGL_INFO, "new SAPI 3 link state %s -> %s\n", + gsm48_sapi3_state_names[rr->sapi3_state], + gsm48_sapi3_state_names[state]); + + rr->sapi3_state = state; +} + +/* + * messages + */ + +/* names of RR-SAP */ +static const struct value_string gsm48_rr_msg_names[] = { + { GSM48_RR_EST_REQ, "RR_EST_REQ" }, + { GSM48_RR_EST_IND, "RR_EST_IND" }, + { GSM48_RR_EST_CNF, "RR_EST_CNF" }, + { GSM48_RR_REL_IND, "RR_REL_IND" }, + { GSM48_RR_SYNC_IND, "RR_SYNC_IND" }, + { GSM48_RR_DATA_REQ, "RR_DATA_REQ" }, + { GSM48_RR_DATA_IND, "RR_DATA_IND" }, + { GSM48_RR_UNIT_DATA_IND, "RR_UNIT_DATA_IND" }, + { GSM48_RR_ABORT_REQ, "RR_ABORT_REQ" }, + { GSM48_RR_ABORT_IND, "RR_ABORT_IND" }, + { GSM48_RR_ACT_REQ, "RR_ACT_REQ" }, + { 0, NULL } +}; + +const char *get_rr_name(int value) +{ + return get_value_string(gsm48_rr_msg_names, value); +} + +/* allocate GSM 04.08 layer 3 message */ +struct msgb *gsm48_l3_msgb_alloc(void) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(L3_ALLOC_SIZE+L3_ALLOC_HEADROOM, + L3_ALLOC_HEADROOM, "GSM 04.08 L3"); + if (!msg) + return NULL; + msg->l3h = msg->data; + + return msg; +} + +/* allocate GSM 04.06 layer 2 RSL message */ +struct msgb *gsm48_rsl_msgb_alloc(void) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(RSL_ALLOC_SIZE+RSL_ALLOC_HEADROOM, + RSL_ALLOC_HEADROOM, "GSM 04.06 RSL"); + if (!msg) + return NULL; + msg->l2h = msg->data; + + return msg; +} + +/* allocate GSM 04.08 message (RR-SAP) */ +struct msgb *gsm48_rr_msgb_alloc(int msg_type) +{ + struct msgb *msg; + struct gsm48_rr_hdr *rrh; + + msg = msgb_alloc_headroom(RR_ALLOC_SIZE+RR_ALLOC_HEADROOM, + RR_ALLOC_HEADROOM, "GSM 04.08 RR"); + if (!msg) + return NULL; + + rrh = (struct gsm48_rr_hdr *) msgb_put(msg, sizeof(*rrh)); + rrh->msg_type = msg_type; + + return msg; +} + +/* queue message (RR-SAP) */ +int gsm48_rr_upmsg(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + + msgb_enqueue(&mm->rr_upqueue, msg); + + return 0; +} + +/* push rsl header and send (RSL-SAP) */ +static int gsm48_send_rsl(struct osmocom_ms *ms, uint8_t msg_type, + struct msgb *msg, uint8_t link_id) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + if (!msg->l3h) { + LOGP(DRR, LOGL_ERROR, "FIX l3h\n"); + return -EINVAL; + } + rsl_rll_push_l3(msg, msg_type, rr->cd_now.chan_nr, link_id, 1); + + return lapdm_rslms_recvmsg(msg, &ms->lapdm_channel); +} + +/* push rsl header without L3 info and send (RSL-SAP) */ +static int gsm48_send_rsl_nol3(struct osmocom_ms *ms, uint8_t msg_type, + struct msgb *msg, uint8_t link_id) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + rsl_rll_push_hdr(msg, msg_type, rr->cd_now.chan_nr, + link_id, 1); + + return lapdm_rslms_recvmsg(msg, &ms->lapdm_channel); +} + +/* enqueue messages (RSL-SAP) */ +static int rcv_rsl(struct msgb *msg, struct lapdm_entity *le, void *l3ctx) +{ + struct osmocom_ms *ms = l3ctx; + struct gsm48_rrlayer *rr = &ms->rrlayer; + + msgb_enqueue(&rr->rsl_upqueue, msg); + + return 0; +} + +/* dequeue messages (RSL-SAP) */ +int gsm48_rsl_dequeue(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *msg; + int work = 0; + + while ((msg = msgb_dequeue(&rr->rsl_upqueue))) { + /* msg is freed there */ + gsm48_rcv_rsl(ms, msg); + work = 1; /* work done */ + } + + return work; +} + +int gsm48_rr_start_monitor(struct osmocom_ms *ms) +{ + ms->rrlayer.monitor = 1; + + return 0; +} + +int gsm48_rr_stop_monitor(struct osmocom_ms *ms) +{ + ms->rrlayer.monitor = 0; + + return 0; +} + +/* release L3 link in both directions in case of main link release */ +static int gsm48_release_sapi3_link(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_rr_hdr *nrrh; + struct msgb *nmsg; + uint8_t *mode; + + if (rr->sapi3_state == GSM48_RR_SAPI3ST_IDLE) + return 0; + + LOGP(DRR, LOGL_INFO, "Main signallin link is down, so release SAPI 3 " + "link locally.\n"); + + new_sapi3_state(rr, GSM48_RR_SAPI3ST_IDLE); + + /* disconnect the SAPI 3 signalling link */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + mode = msgb_put(nmsg, 2); + mode[0] = RSL_IE_RELEASE_MODE; + mode[1] = 1; /* local release */ + gsm48_send_rsl_nol3(ms, RSL_MT_REL_REQ, nmsg, rr->sapi3_link_id); + + /* send inication to upper layer */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = RR_REL_CAUSE_NORMAL; + nrrh->sapi = rr->sapi3_link_id & 7; + gsm48_rr_upmsg(ms, nmsg); + + return 0; +} + +/* + * timers handling + */ + +/* special timer to monitor measurements */ +static void timeout_rr_meas(void *arg) +{ + struct gsm48_rrlayer *rr = arg; + struct gsm322_cellsel *cs = &rr->ms->cellsel; + struct rx_meas_stat *meas = &rr->ms->meas; + struct gsm_settings *set = &rr->ms->settings; + int rxlev, berr, snr; + uint8_t ch_type, ch_subch, ch_ts; + char text[256]; + + /* don't monitor if no cell is selcted or if we scan neighbour cells */ + if (!cs->selected || cs->neighbour) { + sprintf(text, "MON: not camping on serving cell"); + goto restart; + } else if (!meas->frames) { + sprintf(text, "MON: no cell info"); + } else { + rxlev = (meas->rxlev + meas->frames / 2) / meas->frames; + berr = (meas->berr + meas->frames / 2) / meas->frames; + snr = (meas->snr + meas->frames / 2) / meas->frames; + sprintf(text, "MON: f=%d lev=%s snr=%2d ber=%3d " + "LAI=%s %s %04x ID=%04x", cs->sel_arfcn, + gsm_print_rxlev(rxlev), berr, snr, + gsm_print_mcc(cs->sel_mcc), + gsm_print_mnc(cs->sel_mnc), cs->sel_lac, cs->sel_id); + if (rr->state == GSM48_RR_ST_DEDICATED) { + rsl_dec_chan_nr(rr->cd_now.chan_nr, &ch_type, + &ch_subch, &ch_ts); + sprintf(text + strlen(text), " TA=%d pwr=%d TS=%d", + rr->cd_now.ind_ta - set->alter_delay, + (set->alter_tx_power) ? set->alter_tx_power_value + : rr->cd_now.ind_tx_power, ch_ts); + if (ch_type == RSL_CHAN_SDCCH8_ACCH + || ch_type == RSL_CHAN_SDCCH4_ACCH) + sprintf(text + strlen(text), "/%d", ch_subch); + } else + gsm322_meas(rr->ms, rxlev); + } + LOGP(DRR, LOGL_INFO, "%s\n", text); + if (rr->monitor) + vty_notify(rr->ms, "%s\n", text); + + if (rr->dm_est) + gsm48_rr_tx_meas_rep(rr->ms); + +restart: + meas->frames = meas->snr = meas->berr = meas->rxlev = 0; + start_rr_t_meas(rr, 1, 0); +} + +/* special timer to assign / handover when starting time is reached */ +static void timeout_rr_t_starting(void *arg) +{ + struct gsm48_rrlayer *rr = arg; + struct msgb *nmsg; + + LOGP(DRR, LOGL_INFO, "starting timer has fired\n"); + + /* open channel when starting timer of IMM.ASS has fired */ + if (rr->modify_state == GSM48_RR_MOD_IMM_ASS) { + rr->modify_state = GSM48_RR_MOD_NONE; + gsm48_rr_dl_est(rr->ms); + return; + } + + /* start suspension of current link */ + LOGP(DRR, LOGL_INFO, "request suspension of data link\n"); + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return; + gsm48_send_rsl(rr->ms, RSL_MT_SUSP_REQ, nmsg, 0); + + /* release SAPI 3 link, if exits + * FIXME: suspend and resume afterward */ + gsm48_release_sapi3_link(rr->ms); +} + +/* special timer to ensure that UA is sent before disconnecting channel */ +static void timeout_rr_t_rel_wait(void *arg) +{ + struct gsm48_rrlayer *rr = arg; + + LOGP(DRR, LOGL_INFO, "L2 release timer has fired, done waiting\n"); + + /* return to idle now */ + new_rr_state(rr, GSM48_RR_ST_IDLE); +} + +/* 3.4.13.1.1: Timeout of T3110 */ +static void timeout_rr_t3110(void *arg) +{ + struct gsm48_rrlayer *rr = arg; + struct osmocom_ms *ms = rr->ms; + struct msgb *nmsg; + uint8_t *mode; + + LOGP(DRR, LOGL_INFO, "timer T3110 has fired, release locally\n"); + + new_rr_state(rr, GSM48_RR_ST_REL_PEND); + + /* disconnect the main signalling link */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return; + mode = msgb_put(nmsg, 2); + mode[0] = RSL_IE_RELEASE_MODE; + mode[1] = 1; /* local release */ + gsm48_send_rsl_nol3(ms, RSL_MT_REL_REQ, nmsg, 0); + + /* release SAPI 3 link, if exits */ + gsm48_release_sapi3_link(ms); + + return; +} + +static void timeout_rr_t3122(void *arg) +{ + LOGP(DRR, LOGL_INFO, "timer T3122 has fired\n"); +} + +static void timeout_rr_t3124(void *arg) +{ + LOGP(DRR, LOGL_INFO, "timer T3124 has fired\n"); +} + +static void timeout_rr_t3126(void *arg) +{ + struct gsm48_rrlayer *rr = arg; + struct osmocom_ms *ms = rr->ms; + + LOGP(DRR, LOGL_INFO, "timer T3126 has fired\n"); + if (rr->rr_est_req) { + struct msgb *msg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + struct gsm48_rr_hdr *rrh; + + LOGP(DSUM, LOGL_INFO, "Requesting channel failed\n"); + if (!msg) + return; + rrh = (struct gsm48_rr_hdr *)msg->data; + rrh->cause = RR_REL_CAUSE_RA_FAILURE; + gsm48_rr_upmsg(ms, msg); + } + + new_rr_state(rr, GSM48_RR_ST_IDLE); +} + +static void start_rr_t_meas(struct gsm48_rrlayer *rr, int sec, int micro) +{ + rr->t_meas.cb = timeout_rr_meas; + rr->t_meas.data = rr; + osmo_timer_schedule(&rr->t_meas, sec, micro); +} + +static void start_rr_t_rel_wait(struct gsm48_rrlayer *rr, int sec, int micro) +{ + LOGP(DRR, LOGL_INFO, "starting T_rel_wait with %d.%03d seconds\n", sec, + micro / 1000); + rr->t_rel_wait.cb = timeout_rr_t_rel_wait; + rr->t_rel_wait.data = rr; + osmo_timer_schedule(&rr->t_rel_wait, sec, micro); +} + +static void start_rr_t_starting(struct gsm48_rrlayer *rr, int sec, int micro) +{ + LOGP(DRR, LOGL_INFO, "starting T_starting with %d.%03d seconds\n", sec, + micro / 1000); + rr->t_starting.cb = timeout_rr_t_starting; + rr->t_starting.data = rr; + osmo_timer_schedule(&rr->t_starting, sec, micro); +} + +static void start_rr_t3110(struct gsm48_rrlayer *rr, int sec, int micro) +{ + LOGP(DRR, LOGL_INFO, "starting T3110 with %d.%03d seconds\n", sec, + micro / 1000); + rr->t3110.cb = timeout_rr_t3110; + rr->t3110.data = rr; + osmo_timer_schedule(&rr->t3110, sec, micro); +} + +static void start_rr_t3122(struct gsm48_rrlayer *rr, int sec, int micro) +{ + LOGP(DRR, LOGL_INFO, "starting T3122 with %d.%03d seconds\n", sec, + micro / 1000); + rr->t3122.cb = timeout_rr_t3122; + rr->t3122.data = rr; + osmo_timer_schedule(&rr->t3122, sec, micro); +} + +static void start_rr_t3124(struct gsm48_rrlayer *rr, int sec, int micro) +{ + LOGP(DRR, LOGL_INFO, "starting T3124 with %d.%03d seconds\n", sec, + micro / 1000); + rr->t3124.cb = timeout_rr_t3124; + rr->t3124.data = rr; + osmo_timer_schedule(&rr->t3124, sec, micro); +} + +static void start_rr_t3126(struct gsm48_rrlayer *rr, int sec, int micro) +{ + LOGP(DRR, LOGL_INFO, "starting T3126 with %d.%03d seconds\n", sec, + micro / 1000); + rr->t3126.cb = timeout_rr_t3126; + rr->t3126.data = rr; + osmo_timer_schedule(&rr->t3126, sec, micro); +} + +static void stop_rr_t_meas(struct gsm48_rrlayer *rr) +{ + if (osmo_timer_pending(&rr->t_meas)) { + LOGP(DRR, LOGL_INFO, "stopping pending timer T_meas\n"); + osmo_timer_del(&rr->t_meas); + } +} + +static void stop_rr_t_starting(struct gsm48_rrlayer *rr) +{ + if (osmo_timer_pending(&rr->t_starting)) { + LOGP(DRR, LOGL_INFO, "stopping pending timer T_starting\n"); + osmo_timer_del(&rr->t_starting); + } +} + +static void stop_rr_t_rel_wait(struct gsm48_rrlayer *rr) +{ + if (osmo_timer_pending(&rr->t_rel_wait)) { + LOGP(DRR, LOGL_INFO, "stopping pending timer T_rel_wait\n"); + osmo_timer_del(&rr->t_rel_wait); + } +} + +static void stop_rr_t3110(struct gsm48_rrlayer *rr) +{ + if (osmo_timer_pending(&rr->t3110)) { + LOGP(DRR, LOGL_INFO, "stopping pending timer T3110\n"); + osmo_timer_del(&rr->t3110); + } +} + +static void stop_rr_t3122(struct gsm48_rrlayer *rr) +{ + if (osmo_timer_pending(&rr->t3122)) { + LOGP(DRR, LOGL_INFO, "stopping pending timer T3122\n"); + osmo_timer_del(&rr->t3122); + } +} + +static void stop_rr_t3124(struct gsm48_rrlayer *rr) +{ + if (osmo_timer_pending(&rr->t3124)) { + LOGP(DRR, LOGL_INFO, "stopping pending timer T3124\n"); + osmo_timer_del(&rr->t3124); + } +} + +static void stop_rr_t3126(struct gsm48_rrlayer *rr) +{ + if (osmo_timer_pending(&rr->t3126)) { + LOGP(DRR, LOGL_INFO, "stopping pending timer T3126\n"); + osmo_timer_del(&rr->t3126); + } +} + +/* + * status + */ + +/* send rr status request */ +static int gsm48_rr_tx_rr_status(struct osmocom_ms *ms, uint8_t cause) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_rr_status *st; + + LOGP(DRR, LOGL_INFO, "RR STATUS (cause #%d)\n", cause); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + st = (struct gsm48_rr_status *) msgb_put(nmsg, sizeof(*st)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_CIPH_M_COMPL; + + /* rr cause */ + st->rr_cause = cause; + + return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg, 0); +} + +/* + * ciphering + */ + +/* send chiperhing mode complete */ +static int gsm48_rr_tx_cip_mode_cpl(struct osmocom_ms *ms, uint8_t cr) +{ + struct gsm_settings *set = &ms->settings; + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_rr_hdr *nrrh; + uint8_t buf[11], *tlv; + + LOGP(DRR, LOGL_INFO, "CIPHERING MODE COMPLETE (cr %d)\n", cr); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_CIPH_M_COMPL; + + /* MI */ + if (cr) { + gsm48_generate_mid_from_imsi(buf, set->imeisv); + /* alter MI type */ + buf[2] = (buf[2] & ~GSM_MI_TYPE_MASK) | GSM_MI_TYPE_IMEISV; + tlv = msgb_put(nmsg, 2 + buf[1]); + memcpy(tlv, buf, 2 + buf[1]); + } + + gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg, 0); + + /* send RR_SYNC_IND(ciphering) */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_SYNC_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = RR_SYNC_CAUSE_CIPHERING; + return gsm48_rr_upmsg(ms, nmsg); +} + +/* receive ciphering mode command */ +static int gsm48_rr_rx_cip_mode_cmd(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm_settings *set = &ms->settings; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_cip_mode_cmd *cm = (struct gsm48_cip_mode_cmd *)gh->data; + int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*cm); + uint8_t sc, alg_id, cr; + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of CIPHERING MODE COMMAND " + "message.\n"); + return gsm48_rr_tx_rr_status(ms, + GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + + /* cipher mode setting */ + sc = cm->sc; + alg_id = cm->alg_id; + /* cipher mode response */ + cr = cm->cr; + + if (!sc) + LOGP(DRR, LOGL_INFO, "CIPHERING MODE COMMAND (sc=%u, cr=%u)\n", + sc, cr); + else + LOGP(DRR, LOGL_INFO, "CIPHERING MODE COMMAND (sc=%u, " + "algo=A5/%d cr=%u)\n", sc, alg_id + 1, cr); + + /* 3.4.7.2 */ + if (rr->cipher_on && sc) { + LOGP(DRR, LOGL_NOTICE, "chiphering already applied\n"); + return gsm48_rr_tx_rr_status(ms, + GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + + /* check if we actually support this cipher */ + if (sc && ((alg_id == GSM_CIPHER_A5_1 && !set->a5_1) + || (alg_id == GSM_CIPHER_A5_2 && !set->a5_2) + || (alg_id == GSM_CIPHER_A5_3 && !set->a5_3) + || (alg_id == GSM_CIPHER_A5_4 && !set->a5_4) + || (alg_id == GSM_CIPHER_A5_5 && !set->a5_5) + || (alg_id == GSM_CIPHER_A5_6 && !set->a5_6) + || (alg_id == GSM_CIPHER_A5_7 && !set->a5_7))) { + LOGP(DRR, LOGL_NOTICE, "algo not supported\n"); + return gsm48_rr_tx_rr_status(ms, + GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + + /* check if we have no key */ + if (sc && subscr->key_seq == 7) { + LOGP(DRR, LOGL_NOTICE, "no key available\n"); + return gsm48_rr_tx_rr_status(ms, + GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + + /* change to ciphering */ + rr->cipher_on = sc; + rr->cipher_type = alg_id; + if (rr->cipher_on) + l1ctl_tx_crypto_req(ms, rr->cipher_type + 1, subscr->key, 8); + else + l1ctl_tx_crypto_req(ms, 0, NULL, 0); + + /* response (using the new mode) */ + return gsm48_rr_tx_cip_mode_cpl(ms, cr); +} + +/* + * classmark + */ + +/* Encode "Classmark 3" (10.5.1.7) */ +static int gsm48_rr_enc_cm3(struct osmocom_ms *ms, uint8_t *buf, uint8_t *len) +{ + struct gsm_support *sup = &ms->support; + struct gsm_settings *set = &ms->settings; + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data = buf; + bv.data_len = 12; + + /* spare bit */ + bitvec_set_bit(&bv, 0); + /* band 3 supported */ + if (set->dcs) + bitvec_set_bit(&bv, ONE); + else + bitvec_set_bit(&bv, ZERO); + /* band 2 supported */ + if (set->e_gsm || set->r_gsm) + bitvec_set_bit(&bv, ONE); + else + bitvec_set_bit(&bv, ZERO); + /* band 1 supported */ + if (set->p_gsm && !(set->e_gsm || set->r_gsm)) + bitvec_set_bit(&bv, ONE); + else + bitvec_set_bit(&bv, ZERO); + /* a5 bits */ + if (set->a5_7) + bitvec_set_bit(&bv, ONE); + else + bitvec_set_bit(&bv, ZERO); + if (set->a5_6) + bitvec_set_bit(&bv, ONE); + else + bitvec_set_bit(&bv, ZERO); + if (set->a5_5) + bitvec_set_bit(&bv, ONE); + else + bitvec_set_bit(&bv, ZERO); + if (set->a5_4) + bitvec_set_bit(&bv, ONE); + else + bitvec_set_bit(&bv, ZERO); + /* radio capability */ + if (!set->dcs && !set->p_gsm && !(set->e_gsm || set->r_gsm)) { + /* Fig. 10.5.7 / TS 24.0008: none of dcs, p, e, r */ + } else + if (set->dcs && !set->p_gsm && !(set->e_gsm || set->r_gsm)) { + /* dcs only */ + bitvec_set_uint(&bv, 0, 4); + bitvec_set_uint(&bv, set->class_dcs, 4); + } else + if (set->dcs && (set->p_gsm || (set->e_gsm || set->r_gsm))) { + /* dcs */ + bitvec_set_uint(&bv, set->class_dcs, 4); + /* low band */ + bitvec_set_uint(&bv, set->class_900, 4); + } else { + /* low band only */ + bitvec_set_uint(&bv, 0, 4); + bitvec_set_uint(&bv, set->class_900, 4); + } + /* r support */ + if (set->r_gsm) { + bitvec_set_bit(&bv, ONE); + bitvec_set_uint(&bv, set->class_900, 3); + } else { + bitvec_set_bit(&bv, ZERO); + } + /* multi slot support */ + if (sup->ms_sup) { + bitvec_set_bit(&bv, ONE); + bitvec_set_uint(&bv, sup->ms_sup, 5); + } else { + bitvec_set_bit(&bv, ZERO); + } + /* ucs2 treatment */ + if (sup->ucs2_treat) { + bitvec_set_bit(&bv, ONE); + } else { + bitvec_set_bit(&bv, ZERO); + } + /* support extended measurements */ + if (sup->ext_meas) { + bitvec_set_bit(&bv, ONE); + } else { + bitvec_set_bit(&bv, ZERO); + } + /* support measurement capability */ + if (sup->meas_cap) { + bitvec_set_bit(&bv, ONE); + bitvec_set_uint(&bv, sup->sms_val, 4); + bitvec_set_uint(&bv, sup->sm_val, 4); + } else { + bitvec_set_bit(&bv, ZERO); + } + /* positioning method capability */ + if (sup->loc_serv) { + bitvec_set_bit(&bv, ONE); + bitvec_set_bit(&bv, sup->e_otd_ass == 1); + bitvec_set_bit(&bv, sup->e_otd_based == 1); + bitvec_set_bit(&bv, sup->gps_ass == 1); + bitvec_set_bit(&bv, sup->gps_based == 1); + bitvec_set_bit(&bv, sup->gps_conv == 1); + } else { + bitvec_set_bit(&bv, ZERO); + } + /* The following bits are described in TS 24.008 */ + /* EDGE multi slot support */ + if (set->edge_ms_sup) { + bitvec_set_bit(&bv, ONE); + bitvec_set_uint(&bv, set->edge_ms_sup, 5); + } else { + bitvec_set_bit(&bv, ZERO); + } + /* EDGE support */ + if (set->edge_psk_sup) { + bitvec_set_bit(&bv, ONE); + bitvec_set_bit(&bv, set->edge_psk_uplink == 1); + if (set->p_gsm || (set->e_gsm || set->r_gsm)) { + bitvec_set_bit(&bv, ONE); + bitvec_set_uint(&bv, set->class_900_edge, 2); + } else { + bitvec_set_bit(&bv, ZERO); + } + if (set->dcs || set->pcs) { + bitvec_set_bit(&bv, ONE); + bitvec_set_uint(&bv, set->class_dcs_pcs_edge, 2); + } else { + bitvec_set_bit(&bv, ZERO); + } + } else { + bitvec_set_bit(&bv, ZERO); + } + /* GSM 400 Bands */ + if (set->gsm_480 || set->gsm_450) { + bitvec_set_bit(&bv, ONE); + bitvec_set_bit(&bv, set->gsm_480 == 1); + bitvec_set_bit(&bv, set->gsm_450 == 1); + bitvec_set_uint(&bv, set->class_400, 4); + } else { + bitvec_set_bit(&bv, ZERO); + } + /* GSM 850 Band */ + if (set->gsm_850) { + bitvec_set_bit(&bv, ONE); + bitvec_set_uint(&bv, set->class_850, 4); + } else { + bitvec_set_bit(&bv, ZERO); + } + /* PCS Band */ + if (set->pcs) { + bitvec_set_bit(&bv, ONE); + bitvec_set_uint(&bv, set->class_pcs, 4); + } else { + bitvec_set_bit(&bv, ZERO); + } + /* RAT Capability */ + bitvec_set_bit(&bv, set->umts_fdd == 1); + bitvec_set_bit(&bv, set->umts_tdd == 1); + bitvec_set_bit(&bv, set->cdma_2000 == 1); + /* DTM */ + if (set->dtm) { + bitvec_set_bit(&bv, ONE); + bitvec_set_uint(&bv, set->class_dtm, 2); + bitvec_set_bit(&bv, set->dtm_mac == 1); + bitvec_set_bit(&bv, set->dtm_egprs == 1); + } else { + bitvec_set_bit(&bv, ZERO); + } + /* info: The max number of bits are about 80. */ + + /* partitial bytes will be completed */ + *len = (bv.cur_bit + 7) >> 3; + bitvec_spare_padding(&bv, (*len * 8) - 1); + + return 0; +} + +/* encode classmark 2 */ +int gsm48_rr_enc_cm2(struct osmocom_ms *ms, struct gsm48_classmark2 *cm, + uint16_t arfcn) +{ + struct gsm_support *sup = &ms->support; + struct gsm_settings *set = &ms->settings; + + cm->pwr_lev = gsm48_current_pwr_lev(set, arfcn); + cm->a5_1 = !set->a5_1; + cm->es_ind = sup->es_ind; + cm->rev_lev = sup->rev_lev; + cm->fc = (set->r_gsm || set->e_gsm); + cm->vgcs = sup->vgcs; + cm->vbs = sup->vbs; + cm->sm_cap = set->sms_ptp; + cm->ss_scr = sup->ss_ind; + cm->ps_cap = sup->ps_cap; + cm->a5_2 = set->a5_2; + cm->a5_3 = set->a5_3; + cm->cmsp = sup->cmsp; + cm->solsa = sup->solsa; + cm->lcsva_cap = sup->lcsva; + + return 0; +} + +/* send classmark change */ +static int gsm48_rr_tx_cm_change(struct osmocom_ms *ms) +{ + struct gsm_support *sup = &ms->support; + struct gsm_settings *set = &ms->settings; + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_cm_change *cc; + uint8_t cm3[14], *tlv; + + LOGP(DRR, LOGL_INFO, "CLASSMARK CHANGE\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + cc = (struct gsm48_cm_change *) msgb_put(nmsg, sizeof(*cc)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_CLSM_CHG; + + /* classmark 2 */ + cc->cm2_len = sizeof(cc->cm2); + gsm48_rr_enc_cm2(ms, &cc->cm2, rr->cd_now.arfcn); + + /* classmark 3 */ + if (set->dcs || set->pcs || set->e_gsm || set->r_gsm || set->gsm_850 + || set->a5_7 || set->a5_6 || set->a5_5 || set->a5_4 + || sup->ms_sup + || sup->ucs2_treat + || sup->ext_meas || sup->meas_cap + || sup->loc_serv) { + cc->cm2.cm3 = 1; + cm3[0] = GSM48_IE_CLASSMARK3; + gsm48_rr_enc_cm3(ms, cm3 + 2, &cm3[1]); + tlv = msgb_put(nmsg, 2 + cm3[1]); + memcpy(tlv, cm3, 2 + cm3[1]); + } + + return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg, 0); +} + +/* receiving classmark enquiry */ +static int gsm48_rr_rx_cm_enq(struct osmocom_ms *ms, struct msgb *msg) +{ + /* send classmark */ + return gsm48_rr_tx_cm_change(ms); +} + +/* + * random access + */ + +/* start random access */ +static int gsm48_rr_chan_req(struct osmocom_ms *ms, int cause, int paging, + int paging_mi_type) +{ + struct gsm_settings *set = &ms->settings; + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = cs->si; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + uint8_t chan_req_val, chan_req_mask; + int rc; + + LOGP(DSUM, LOGL_INFO, "Establish radio link due to %s request\n", + (paging) ? "paging" : "mobility management"); + + /* ignore paging, if not camping */ + if (paging + && (!cs->selected || (cs->state != GSM322_C3_CAMPED_NORMALLY + && cs->state != GSM322_C7_CAMPED_ANY_CELL))) { + LOGP(DRR, LOGL_INFO, "Paging, but not camping, ignore.\n"); + return -EINVAL; + } + + /* ignore channel request while not camping on a cell */ + if (!cs->selected) { + LOGP(DRR, LOGL_INFO, "Channel request rejected, we did not " + "properly select the serving cell.\n"); + + goto rel_ind; + } + + /* tell cell selection process to leave idle mode + * NOTE: this must be sent unbuffered, because the state may not + * change until idle mode is left + */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_LEAVE_IDLE); + if (!nmsg) + return -ENOMEM; + rc = gsm322_c_event(ms, nmsg); + msgb_free(nmsg); + if (rc) { + if (paging) + return rc; + LOGP(DRR, LOGL_INFO, "Failed to leave IDLE mode.\n"); + goto undefined; + } + + /* 3.3.1.1.2 */ + new_rr_state(rr, GSM48_RR_ST_CONN_PEND); + + /* set assignment state */ + rr->wait_assign = 0; + + /* number of retransmissions (with first transmission) */ + rr->n_chan_req = s->max_retrans + 1; + + /* generate CHAN REQ (9.1.8) */ + switch (cause) { + case RR_EST_CAUSE_EMERGENCY: + /* 101xxxxx */ + chan_req_mask = 0x1f; + chan_req_val = 0xa0; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (Emergency call)\n", + chan_req_val); + break; + case RR_EST_CAUSE_REESTAB_TCH_F: + chan_req_mask = 0x1f; + chan_req_val = 0xc0; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (re-establish " + "TCH/F)\n", chan_req_val); + break; + case RR_EST_CAUSE_REESTAB_TCH_H: + if (s->neci) { + chan_req_mask = 0x03; + chan_req_val = 0x68; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x " + "(re-establish TCH/H with NECI)\n", + chan_req_val); + } else { + chan_req_mask = 0x1f; + chan_req_val = 0xc0; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x " + "(re-establish TCH/H no NECI)\n", chan_req_val); + } + break; + case RR_EST_CAUSE_REESTAB_2_TCH_H: + if (s->neci) { + chan_req_mask = 0x03; + chan_req_val = 0x6c; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x " + "(re-establish TCH/H+TCH/H with NECI)\n", + chan_req_val); + } else { + chan_req_mask = 0x1f; + chan_req_val = 0xc0; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x " + "(re-establish TCH/H+TCH/H no NECI)\n", + chan_req_val); + } + break; + case RR_EST_CAUSE_ANS_PAG_ANY: + chan_req_mask = 0x1f; + chan_req_val = 0x80; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (PAGING " + "Any channel)\n", chan_req_val); + break; + case RR_EST_CAUSE_ANS_PAG_SDCCH: + chan_req_mask = 0x0f; + chan_req_val = 0x10; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (PAGING SDCCH)\n", + chan_req_val); + break; + case RR_EST_CAUSE_ANS_PAG_TCH_F: + switch (set->ch_cap) { + case GSM_CAP_SDCCH: + chan_req_mask = 0x0f; + chan_req_val = 0x10; + break; + case GSM_CAP_SDCCH_TCHF: + chan_req_mask = 0x1f; + chan_req_val = 0x80; + break; + default: + chan_req_mask = 0x0f; + chan_req_val = 0x20; + break; + } + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (PAGING TCH/F)\n", + chan_req_val); + break; + case RR_EST_CAUSE_ANS_PAG_TCH_ANY: + switch (set->ch_cap) { + case GSM_CAP_SDCCH: + chan_req_mask = 0x0f; + chan_req_val = 0x10; + break; + case GSM_CAP_SDCCH_TCHF: + chan_req_mask = 0x1f; + chan_req_val = 0x80; + break; + default: + chan_req_mask = 0x0f; + chan_req_val = 0x30; + break; + } + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (PAGING TCH/H or " + "TCH/F)\n", chan_req_val); + break; + case RR_EST_CAUSE_ORIG_TCHF: + /* ms supports no dual rate */ + chan_req_mask = 0x1f; + chan_req_val = 0xe0; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (Orig TCH/F)\n", + chan_req_val); + break; + case RR_EST_CAUSE_LOC_UPD: + if (s->neci) { + chan_req_mask = 0x0f; + chan_req_val = 0x00; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (Location " + "Update with NECI)\n", chan_req_val); + } else { + chan_req_mask = 0x1f; + chan_req_val = 0x00; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (Location " + "Update no NECI)\n", chan_req_val); + } + break; + case RR_EST_CAUSE_OTHER_SDCCH: + if (s->neci) { + chan_req_mask = 0x0f; + chan_req_val = 0x10; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (OHTER " + "with NECI)\n", chan_req_val); + } else { + chan_req_mask = 0x1f; + chan_req_val = 0xe0; + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: %02x (OTHER " + "no NECI)\n", chan_req_val); + } + break; + default: + if (!rr->rr_est_req) /* no request from MM */ + return -EINVAL; + + LOGP(DRR, LOGL_INFO, "CHANNEL REQUEST: with unknown " + "establishment cause: %d\n", cause); + undefined: + LOGP(DSUM, LOGL_INFO, "Requesting channel failed\n"); + +rel_ind: + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = RR_REL_CAUSE_UNDEFINED; + gsm48_rr_upmsg(ms, nmsg); + new_rr_state(rr, GSM48_RR_ST_IDLE); + return -EINVAL; + } + + /* store value, mask and history */ + rr->chan_req_val = chan_req_val; + rr->chan_req_mask = chan_req_mask; + rr->cr_hist[2].valid = 0; + rr->cr_hist[1].valid = 0; + rr->cr_hist[0].valid = 0; + + /* store establishment cause, so 'choose cell' selects the last cell + * after location updating */ + rr->est_cause = cause; + + /* store paging mobile identity type, if we respond to paging */ + rr->paging_mi_type = paging_mi_type; + + /* if channel is already active somehow */ + if (cs->ccch_state == GSM322_CCCH_ST_DATA) + return gsm48_rr_tx_rand_acc(ms, NULL); + + return 0; +} + +/* send first/next channel request in conn pend state */ +int gsm48_rr_tx_rand_acc(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = &ms->cellsel.sel_si; + struct gsm_settings *set = &ms->settings; + struct msgb *nmsg; + struct abis_rsl_cchan_hdr *ncch; + int slots; + uint8_t chan_req; + uint8_t tx_power; + + /* already assigned */ + if (rr->wait_assign == 2) + return 0; + + /* store frame number */ + if (msg) { + struct abis_rsl_cchan_hdr *ch = msgb_l2(msg); + struct gsm48_req_ref *ref = + (struct gsm48_req_ref *) (ch->data + 1); + + if (msgb_l2len(msg) < sizeof(*ch) + sizeof(*ref)) { + LOGP(DRR, LOGL_ERROR, "CHAN_CNF too slort\n"); + return -EINVAL; + } + + /* shift history and store */ + memcpy(&(rr->cr_hist[2]), &(rr->cr_hist[1]), + sizeof(struct gsm48_cr_hist)); + memcpy(&(rr->cr_hist[1]), &(rr->cr_hist[0]), + sizeof(struct gsm48_cr_hist)); + rr->cr_hist[0].valid = 1; + rr->cr_hist[0].ref.ra = rr->cr_ra; + rr->cr_hist[0].ref.t1 = ref->t1; + rr->cr_hist[0].ref.t2 = ref->t2; + rr->cr_hist[0].ref.t3_low = ref->t3_low; + rr->cr_hist[0].ref.t3_high = ref->t3_high; + } + + if (cs->ccch_state != GSM322_CCCH_ST_DATA) { + LOGP(DRR, LOGL_INFO, "CCCH channel activation failed.\n"); +fail: + if (rr->rr_est_req) { + struct msgb *msg = + gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + struct gsm48_rr_hdr *rrh; + + LOGP(DSUM, LOGL_INFO, "Requesting channel failed\n"); + if (!msg) + return -ENOMEM; + rrh = (struct gsm48_rr_hdr *)msg->data; + rrh->cause = RR_REL_CAUSE_RA_FAILURE; + gsm48_rr_upmsg(ms, msg); + } + + new_rr_state(rr, GSM48_RR_ST_IDLE); + + return 0; + } + + if (!s || !s->si3 || !s->tx_integer) { + LOGP(DRR, LOGL_NOTICE, "Not enough SYSINFO\n"); + goto fail; + } + + if (rr->state == GSM48_RR_ST_IDLE) { + LOGP(DRR, LOGL_INFO, "MM already released RR.\n"); + + return 0; + } + + LOGP(DRR, LOGL_INFO, "RANDOM ACCESS (requests left %d)\n", + rr->n_chan_req); + + if (!rr->n_chan_req) { + LOGP(DRR, LOGL_INFO, "Done with sending RANDOM ACCESS " + "bursts\n"); + if (!osmo_timer_pending(&rr->t3126)) + start_rr_t3126(rr, 5, 0); /* TODO improve! */ + return 0; + } + rr->n_chan_req--; + + if (rr->wait_assign == 0) { + /* first random acces, without delay of slots */ + slots = 0; + rr->wait_assign = 1; + } else { + /* subsequent random acces, with slots from table 3.1 */ + switch(s->tx_integer) { + case 3: case 8: case 14: case 50: + if (s->ccch_conf != 1) /* not combined CCCH */ + slots = 55; + else + slots = 41; + break; + case 4: case 9: case 16: + if (s->ccch_conf != 1) + slots = 76; + else + slots = 52; + break; + case 5: case 10: case 20: + if (s->ccch_conf != 1) + slots = 109; + else + slots = 58; + break; + case 6: case 11: case 25: + if (s->ccch_conf != 1) + slots = 163; + else + slots = 86; + break; + default: + if (s->ccch_conf != 1) + slots = 217; + else + slots = 115; + break; + } + } + + chan_req = random(); + chan_req &= rr->chan_req_mask; + chan_req |= rr->chan_req_val; + + LOGP(DRR, LOGL_INFO, "RANDOM ACCESS (Tx-integer %d combined %s " + "S(lots) %d ra 0x%02x)\n", s->tx_integer, + (s->ccch_conf == 1) ? "yes": "no", slots, chan_req); + + slots = (random() % s->tx_integer) + slots; + + /* (re)send CHANNEL RQD with new randiom */ + nmsg = gsm48_rsl_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + ncch = (struct abis_rsl_cchan_hdr *) msgb_put(nmsg, sizeof(*ncch) + + 4 + 2 + 2); + rsl_init_cchan_hdr(ncch, RSL_MT_CHAN_RQD); + ncch->chan_nr = RSL_CHAN_RACH; + ncch->data[0] = RSL_IE_REQ_REFERENCE; + ncch->data[1] = chan_req; + ncch->data[2] = (slots >> 8) | ((s->ccch_conf == 1) << 7); + ncch->data[3] = slots; + ncch->data[4] = RSL_IE_ACCESS_DELAY; + ncch->data[5] = set->alter_delay; /* (-)=earlier (+)=later */ + ncch->data[6] = RSL_IE_MS_POWER; + if (set->alter_tx_power) { + tx_power = set->alter_tx_power_value; + LOGP(DRR, LOGL_INFO, "Use alternative tx-power %d (%d dBm)\n", + tx_power, + ms_pwr_dbm(gsm_arfcn2band(cs->arfcn), tx_power)); + } else { + tx_power = s->ms_txpwr_max_cch; + /* power offset in case of DCS1800 */ + if (s->po && (cs->arfcn & 1023) >= 512 + && (cs->arfcn & 1023) <= 885) { + LOGP(DRR, LOGL_INFO, "Use MS-TXPWR-MAX-CCH power value " + "%d (%d dBm) with offset %d dBm\n", tx_power, + ms_pwr_dbm(gsm_arfcn2band(cs->arfcn), tx_power), + s->po_value * 2); + /* use reserved bits 7,8 for offset (+ X * 2dB) */ + tx_power |= s->po_value << 6; + } else + LOGP(DRR, LOGL_INFO, "Use MS-TXPWR-MAX-CCH power value " + "%d (%d dBm)\n", tx_power, + ms_pwr_dbm(gsm_arfcn2band(cs->arfcn), + tx_power)); + } + ncch->data[7] = tx_power; + + /* set initial indications */ + rr->cd_now.ind_tx_power = s->ms_txpwr_max_cch; + rr->cd_now.ind_ta = set->alter_delay; + + /* store ra until confirmed, then copy it with time into cr_hist */ + rr->cr_ra = chan_req; + + return lapdm_rslms_recvmsg(nmsg, &ms->lapdm_channel); +} + +/* + * system information + */ + +/* send sysinfo event to other layers */ +static int gsm48_new_sysinfo(struct osmocom_ms *ms, uint8_t type) +{ + struct gsm48_sysinfo *s = ms->cellsel.si; + struct gsm322_cellsel *cs = &ms->cellsel; + struct msgb *nmsg; + struct gsm322_msg *em; + + /* update list of measurements, if BA(SACCH) is complete and new */ + if (s + && (type == GSM48_MT_RR_SYSINFO_5 + || type == GSM48_MT_RR_SYSINFO_5bis + || type == GSM48_MT_RR_SYSINFO_5ter) + && s->si5 + && (!s->nb_ext_ind_si5 || s->si5bis)) { + struct gsm48_rr_meas *rrmeas = &ms->rrlayer.meas; + int n = 0, i, refer_pcs; + + LOGP(DRR, LOGL_NOTICE, "Complete set of SI5* for BA(%d)\n", + s->nb_ba_ind_si5); + rrmeas->nc_num = 0; + refer_pcs = gsm_refer_pcs(cs->arfcn, s); + + /* collect channels from freq list (1..1023,0) */ + for (i = 1; i <= 1024; i++) { + if ((s->freq[i & 1023].mask & FREQ_TYPE_REP)) { + if (n == 32) { + LOGP(DRR, LOGL_NOTICE, "SI5* report " + "exceeds 32 BCCHs\n"); + break; + } + if (refer_pcs && i >= 512 && i <= 810) + rrmeas->nc_arfcn[n] = i | ARFCN_PCS; + else + rrmeas->nc_arfcn[n] = i & 1023; + rrmeas->nc_rxlev[n] = -128; + LOGP(DRR, LOGL_NOTICE, "SI5* report arfcn %s\n", + gsm_print_arfcn(rrmeas->nc_arfcn[n])); + n++; + } + } + rrmeas->nc_num = n; + } + + /* send sysinfo event to other layers */ + nmsg = gsm322_msgb_alloc(GSM322_EVENT_SYSINFO); + if (!nmsg) + return -ENOMEM; + em = (struct gsm322_msg *) nmsg->data; + em->sysinfo = type; + gsm322_cs_sendmsg(ms, nmsg); + + /* if not camping, we don't care about SI */ + if (ms->cellsel.neighbour + || (ms->cellsel.state != GSM322_C3_CAMPED_NORMALLY + && ms->cellsel.state != GSM322_C7_CAMPED_ANY_CELL)) + return 0; + + /* send timer info to location update process */ + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_SYSINFO); + if (!nmsg) + return -ENOMEM; + gsm48_mmevent_msg(ms, nmsg); + + return 0; +} + +/* receive "SYSTEM INFORMATION 1" message (9.1.31) */ +static int gsm48_rr_rx_sysinfo1(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_system_information_type_1 *si = msgb_l3(msg); + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si); + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 1 " + "ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 1 " + "message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si1_msg, MIN(msgb_l3len(msg), sizeof(s->si1_msg)))) + return 0; + + gsm48_decode_sysinfo1(s, si, msgb_l3len(msg)); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 1\n"); + + return gsm48_new_sysinfo(ms, si->header.system_information); +} + +/* receive "SYSTEM INFORMATION 2" message (9.1.32) */ +static int gsm48_rr_rx_sysinfo2(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_system_information_type_2 *si = msgb_l3(msg); + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si); + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 2 " + "ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 2 " + "message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si2_msg, MIN(msgb_l3len(msg), sizeof(s->si2_msg)))) + return 0; + + gsm48_decode_sysinfo2(s, si, msgb_l3len(msg)); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 2\n"); + + return gsm48_new_sysinfo(ms, si->header.system_information); +} + +/* receive "SYSTEM INFORMATION 2bis" message (9.1.33) */ +static int gsm48_rr_rx_sysinfo2bis(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_system_information_type_2bis *si = msgb_l3(msg); + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si); + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 2bis" + " ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 2bis " + "message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si2b_msg, MIN(msgb_l3len(msg), sizeof(s->si2b_msg)))) + return 0; + + gsm48_decode_sysinfo2bis(s, si, msgb_l3len(msg)); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 2bis\n"); + + return gsm48_new_sysinfo(ms, si->header.system_information); +} + +/* receive "SYSTEM INFORMATION 2ter" message (9.1.34) */ +static int gsm48_rr_rx_sysinfo2ter(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_system_information_type_2ter *si = msgb_l3(msg); + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si); + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 2ter" + " ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 2ter " + "message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si2t_msg, MIN(msgb_l3len(msg), sizeof(s->si2t_msg)))) + return 0; + + gsm48_decode_sysinfo2ter(s, si, msgb_l3len(msg)); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 2ter\n"); + + return gsm48_new_sysinfo(ms, si->header.system_information); +} + +/* receive "SYSTEM INFORMATION 3" message (9.1.35) */ +static int gsm48_rr_rx_sysinfo3(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_system_information_type_3 *si = msgb_l3(msg); + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = cs->si; + int payload_len = msgb_l3len(msg) - sizeof(*si); + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 3 " + "ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 3 " + "message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si3_msg, MIN(msgb_l3len(msg), sizeof(s->si3_msg)))) + return 0; + + gsm48_decode_sysinfo3(s, si, msgb_l3len(msg)); + + if (cs->ccch_mode == CCCH_MODE_NONE) { + cs->ccch_mode = (s->ccch_conf == 1) ? CCCH_MODE_COMBINED : + CCCH_MODE_NON_COMBINED; + LOGP(DRR, LOGL_NOTICE, "Changing CCCH_MODE to %d\n", + cs->ccch_mode); + l1ctl_tx_ccch_mode_req(ms, cs->ccch_mode); + } + + return gsm48_new_sysinfo(ms, si->header.system_information); +} + +/* receive "SYSTEM INFORMATION 4" message (9.1.36) */ +static int gsm48_rr_rx_sysinfo4(struct osmocom_ms *ms, struct msgb *msg) +{ + /* NOTE: pseudo length is not in this structure, so we skip */ + struct gsm48_system_information_type_4 *si = msgb_l3(msg); + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si); + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 4 " + "ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 4 " + "message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si4_msg, MIN(msgb_l3len(msg), sizeof(s->si4_msg)))) + return 0; + + gsm48_decode_sysinfo4(s, si, msgb_l3len(msg)); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 4 (mcc %s mnc %s " + "lac 0x%04x)\n", gsm_print_mcc(s->mcc), + gsm_print_mnc(s->mnc), s->lac); + + return gsm48_new_sysinfo(ms, si->header.system_information); +} + +/* receive "SYSTEM INFORMATION 5" message (9.1.37) */ +static int gsm48_rr_rx_sysinfo5(struct osmocom_ms *ms, struct msgb *msg) +{ + /* NOTE: pseudo length is not in this structure, so we skip */ + struct gsm48_system_information_type_5 *si = msgb_l3(msg) + 1; + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si) - 1; + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 5 " + "ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 5 " + "message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si5_msg, MIN(msgb_l3len(msg), sizeof(s->si5_msg)))) + return 0; + + gsm48_decode_sysinfo5(s, si, msgb_l3len(msg)); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 5\n"); + + return gsm48_new_sysinfo(ms, si->system_information); +} + +/* receive "SYSTEM INFORMATION 5bis" message (9.1.38) */ +static int gsm48_rr_rx_sysinfo5bis(struct osmocom_ms *ms, struct msgb *msg) +{ + /* NOTE: pseudo length is not in this structure, so we skip */ + struct gsm48_system_information_type_5bis *si = msgb_l3(msg) + 1; + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si) - 1; + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 5bis" + " ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 5bis " + "message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si5b_msg, MIN(msgb_l3len(msg), + sizeof(s->si5b_msg)))) + return 0; + + gsm48_decode_sysinfo5bis(s, si, msgb_l3len(msg)); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 5bis\n"); + + return gsm48_new_sysinfo(ms, si->system_information); +} + +/* receive "SYSTEM INFORMATION 5ter" message (9.1.39) */ +static int gsm48_rr_rx_sysinfo5ter(struct osmocom_ms *ms, struct msgb *msg) +{ + /* NOTE: pseudo length is not in this structure, so we skip */ + struct gsm48_system_information_type_5ter *si = msgb_l3(msg) + 1; + struct gsm48_sysinfo *s = ms->cellsel.si; + int payload_len = msgb_l3len(msg) - sizeof(*si) - 1; + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 5ter" + " ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 5ter " + "message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si5t_msg, MIN(msgb_l3len(msg), + sizeof(s->si5t_msg)))) + return 0; + + gsm48_decode_sysinfo5ter(s, si, msgb_l3len(msg)); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 5ter\n"); + + return gsm48_new_sysinfo(ms, si->system_information); +} + +/* receive "SYSTEM INFORMATION 6" message (9.1.39) */ +static int gsm48_rr_rx_sysinfo6(struct osmocom_ms *ms, struct msgb *msg) +{ + /* NOTE: pseudo length is not in this structure, so we skip */ + struct gsm48_system_information_type_6 *si = msgb_l3(msg) + 1; + struct gsm48_sysinfo *s = ms->cellsel.si; + struct rx_meas_stat *meas = &ms->meas; + int payload_len = msgb_l3len(msg) - sizeof(*si) - 1; + + if (!s) { + LOGP(DRR, LOGL_INFO, "No cell selected, SYSTEM INFORMATION 6 " + "ignored\n"); + return -EINVAL; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of SYSTEM INFORMATION 6 " + "message.\n"); + return -EINVAL; + } + + if (!memcmp(si, s->si6_msg, MIN(msgb_l3len(msg), sizeof(s->si6_msg)))) + return 0; + + gsm48_decode_sysinfo6(s, si, msgb_l3len(msg)); + + LOGP(DRR, LOGL_INFO, "New SYSTEM INFORMATION 6 (mcc %s mnc %s " + "lac 0x%04x SACCH-timeout %d)\n", gsm_print_mcc(s->mcc), + gsm_print_mnc(s->mnc), s->lac, s->sacch_radio_link_timeout); + + meas->rl_fail = meas->s = s->sacch_radio_link_timeout; + LOGP(DRR, LOGL_INFO, "using (new) SACCH timeout %d\n", meas->rl_fail); + + return gsm48_new_sysinfo(ms, si->system_information); +} + +/* + * paging + */ + +/* paging channel request */ +static int gsm48_rr_chan2cause[4] = { + RR_EST_CAUSE_ANS_PAG_ANY, + RR_EST_CAUSE_ANS_PAG_SDCCH, + RR_EST_CAUSE_ANS_PAG_TCH_F, + RR_EST_CAUSE_ANS_PAG_TCH_ANY +}; + +/* given LV of mobile identity is checked agains ms */ +static uint8_t gsm_match_mi(struct osmocom_ms *ms, uint8_t *mi) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + char imsi[16]; + uint32_t tmsi; + uint8_t mi_type; + + if (mi[0] < 1) + return 0; + mi_type = mi[1] & GSM_MI_TYPE_MASK; + switch (mi_type) { + case GSM_MI_TYPE_TMSI: + if (mi[0] < 5) + return 0; + memcpy(&tmsi, mi+2, 4); + if (ms->subscr.tmsi == ntohl(tmsi) + && ms->subscr.mcc == cs->sel_mcc + && ms->subscr.mnc == cs->sel_mnc + && ms->subscr.lac == cs->sel_lac) { + LOGP(DPAG, LOGL_INFO, " TMSI %08x matches\n", + ntohl(tmsi)); + + return mi_type; + } else + LOGP(DPAG, LOGL_INFO, " TMSI %08x (not for us)\n", + ntohl(tmsi)); + break; + case GSM_MI_TYPE_IMSI: + gsm48_mi_to_string(imsi, sizeof(imsi), mi + 1, mi[0]); + if (!strcmp(imsi, ms->subscr.imsi)) { + LOGP(DPAG, LOGL_INFO, " IMSI %s matches\n", imsi); + + return mi_type; + } else + LOGP(DPAG, LOGL_INFO, " IMSI %s (not for us)\n", imsi); + break; + default: + LOGP(DPAG, LOGL_NOTICE, "Paging with unsupported MI type %d.\n", + mi_type); + } + + return 0; +} + +/* 9.1.22 PAGING REQUEST 1 message received */ +static int gsm48_rr_rx_pag_req_1(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_paging1 *pa = msgb_l3(msg); + int payload_len = msgb_l3len(msg) - sizeof(*pa); + int chan_1, chan_2; + uint8_t *mi, mi_type; + + /* empty paging request */ + if (payload_len >= 2 && (pa->data[1] & GSM_MI_TYPE_MASK) == 0) + return 0; + + /* 3.3.1.1.2: ignore paging while not camping on a cell */ + if (rr->state != GSM48_RR_ST_IDLE || !cs->selected + || (cs->state != GSM322_C3_CAMPED_NORMALLY + && cs->state != GSM322_C7_CAMPED_ANY_CELL) + || cs->neighbour) { + LOGP(DRR, LOGL_INFO, "PAGING ignored, we are not camping.\n"); + + return 0; + } + LOGP(DPAG, LOGL_INFO, "PAGING REQUEST 1\n"); + + if (payload_len < 2) { + short_read: + LOGP(DRR, LOGL_NOTICE, "Short read of PAGING REQUEST 1 " + "message.\n"); + + return -EINVAL; + } + + /* channel needed */ + chan_1 = pa->cneed1; + chan_2 = pa->cneed2; + /* first MI */ + mi = pa->data; + if (payload_len < mi[0] + 1) + goto short_read; + if ((mi_type = gsm_match_mi(ms, mi)) > 0) + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_1], 1, + mi_type); + /* second MI */ + payload_len -= mi[0] + 1; + mi = pa->data + mi[0] + 1; + if (payload_len < 2) + return 0; + if (mi[0] != GSM48_IE_MOBILE_ID) + return 0; + if (payload_len < mi[1] + 2) + goto short_read; + if ((mi_type = gsm_match_mi(ms, mi + 1)) > 0) + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_2], 1, + mi_type); + + return 0; +} + +/* 9.1.23 PAGING REQUEST 2 message received */ +static int gsm48_rr_rx_pag_req_2(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_paging2 *pa = msgb_l3(msg); + int payload_len = msgb_l3len(msg) - sizeof(*pa); + uint8_t *mi, mi_type; + int chan_1, chan_2, chan_3; + + /* 3.3.1.1.2: ignore paging while not camping on a cell */ + if (rr->state != GSM48_RR_ST_IDLE || !cs->selected + || (cs->state != GSM322_C3_CAMPED_NORMALLY + && cs->state != GSM322_C7_CAMPED_ANY_CELL) + || cs->neighbour) { + LOGP(DRR, LOGL_INFO, "PAGING ignored, we are not camping.\n"); + + return 0; + } + LOGP(DPAG, LOGL_INFO, "PAGING REQUEST 2\n"); + + if (payload_len < 0) { + short_read: + LOGP(DRR, LOGL_NOTICE, "Short read of PAGING REQUEST 2 " + "message .\n"); + + return -EINVAL; + } + + /* channel needed */ + chan_1 = pa->cneed1; + chan_2 = pa->cneed2; + /* first MI */ + if (ms->subscr.tmsi == ntohl(pa->tmsi1) + && ms->subscr.mcc == cs->sel_mcc + && ms->subscr.mnc == cs->sel_mnc + && ms->subscr.lac == cs->sel_lac) { + LOGP(DPAG, LOGL_INFO, " TMSI %08x matches\n", ntohl(pa->tmsi1)); + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_1], 1, + GSM_MI_TYPE_TMSI); + } else + LOGP(DPAG, LOGL_INFO, " TMSI %08x (not for us)\n", + ntohl(pa->tmsi1)); + /* second MI */ + if (ms->subscr.tmsi == ntohl(pa->tmsi2) + && ms->subscr.mcc == cs->sel_mcc + && ms->subscr.mnc == cs->sel_mnc + && ms->subscr.lac == cs->sel_lac) { + LOGP(DPAG, LOGL_INFO, " TMSI %08x matches\n", ntohl(pa->tmsi2)); + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_2], 1, + GSM_MI_TYPE_TMSI); + } else + LOGP(DPAG, LOGL_INFO, " TMSI %08x (not for us)\n", + ntohl(pa->tmsi2)); + /* third MI */ + mi = pa->data; + if (payload_len < 2) + return 0; + if (mi[0] != GSM48_IE_MOBILE_ID) + return 0; + if (payload_len < mi[1] + 2 + 1) /* must include "channel needed" */ + goto short_read; + chan_3 = mi[mi[1] + 2] & 0x03; /* channel needed */ + if ((mi_type = gsm_match_mi(ms, mi + 1)) > 0) + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_3], 1, + mi_type); + + return 0; +} + +/* 9.1.24 PAGING REQUEST 3 message received */ +static int gsm48_rr_rx_pag_req_3(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_paging3 *pa = msgb_l3(msg); + int payload_len = msgb_l3len(msg) - sizeof(*pa); + int chan_1, chan_2, chan_3, chan_4; + + /* 3.3.1.1.2: ignore paging while not camping on a cell */ + if (rr->state != GSM48_RR_ST_IDLE || !cs->selected + || (cs->state != GSM322_C3_CAMPED_NORMALLY + && cs->state != GSM322_C7_CAMPED_ANY_CELL) + || cs->neighbour) { + LOGP(DRR, LOGL_INFO, "PAGING ignored, we are not camping.\n"); + + return 0; + } + LOGP(DPAG, LOGL_INFO, "PAGING REQUEST 3\n"); + + if (payload_len < 0) { /* must include "channel needed", part of *pa */ + LOGP(DRR, LOGL_NOTICE, "Short read of PAGING REQUEST 3 " + "message .\n"); + + return -EINVAL; + } + + /* channel needed */ + chan_1 = pa->cneed1; + chan_2 = pa->cneed2; + chan_3 = pa->cneed3; + chan_4 = pa->cneed4; + /* first MI */ + if (ms->subscr.tmsi == ntohl(pa->tmsi1) + && ms->subscr.mcc == cs->sel_mcc + && ms->subscr.mnc == cs->sel_mnc + && ms->subscr.lac == cs->sel_lac) { + LOGP(DPAG, LOGL_INFO, " TMSI %08x matches\n", ntohl(pa->tmsi1)); + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_1], 1, + GSM_MI_TYPE_TMSI); + } else + LOGP(DPAG, LOGL_INFO, " TMSI %08x (not for us)\n", + ntohl(pa->tmsi1)); + /* second MI */ + if (ms->subscr.tmsi == ntohl(pa->tmsi2) + && ms->subscr.mcc == cs->sel_mcc + && ms->subscr.mnc == cs->sel_mnc + && ms->subscr.lac == cs->sel_lac) { + LOGP(DPAG, LOGL_INFO, " TMSI %08x matches\n", ntohl(pa->tmsi2)); + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_2], 1, + GSM_MI_TYPE_TMSI); + } else + LOGP(DPAG, LOGL_INFO, " TMSI %08x (not for us)\n", + ntohl(pa->tmsi2)); + /* thrid MI */ + if (ms->subscr.tmsi == ntohl(pa->tmsi3) + && ms->subscr.mcc == cs->sel_mcc + && ms->subscr.mnc == cs->sel_mnc + && ms->subscr.lac == cs->sel_lac) { + LOGP(DPAG, LOGL_INFO, " TMSI %08x matches\n", ntohl(pa->tmsi3)); + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_3], 1, + GSM_MI_TYPE_TMSI); + } else + LOGP(DPAG, LOGL_INFO, " TMSI %08x (not for us)\n", + ntohl(pa->tmsi3)); + /* fourth MI */ + if (ms->subscr.tmsi == ntohl(pa->tmsi4) + && ms->subscr.mcc == cs->sel_mcc + && ms->subscr.mnc == cs->sel_mnc + && ms->subscr.lac == cs->sel_lac) { + LOGP(DPAG, LOGL_INFO, " TMSI %08x matches\n", ntohl(pa->tmsi4)); + return gsm48_rr_chan_req(ms, gsm48_rr_chan2cause[chan_4], 1, + GSM_MI_TYPE_TMSI); + } else + LOGP(DPAG, LOGL_INFO, " TMSI %08x (not for us)\n", + ntohl(pa->tmsi4)); + + return 0; +} + +/* + * (immediate) assignment + */ + +/* match request reference agains request history */ +static int gsm48_match_ra(struct osmocom_ms *ms, struct gsm48_req_ref *ref) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + int i; + uint8_t ia_t1, ia_t2, ia_t3; + uint8_t cr_t1, cr_t2, cr_t3; + + for (i = 0; i < 3; i++) { + /* filter confirmed RACH requests only */ + if (rr->cr_hist[i].valid && ref->ra == rr->cr_hist[i].ref.ra) { + ia_t1 = ref->t1; + ia_t2 = ref->t2; + ia_t3 = (ref->t3_high << 3) | ref->t3_low; + ref = &rr->cr_hist[i].ref; + cr_t1 = ref->t1; + cr_t2 = ref->t2; + cr_t3 = (ref->t3_high << 3) | ref->t3_low; + if (ia_t1 == cr_t1 && ia_t2 == cr_t2 + && ia_t3 == cr_t3) { + LOGP(DRR, LOGL_INFO, "request %02x matches " + "(fn=%d,%d,%d)\n", ref->ra, ia_t1, + ia_t2, ia_t3); + return 1; + } else + LOGP(DRR, LOGL_INFO, "request %02x matches " + "but not frame number (IMM.ASS " + "fn=%d,%d,%d != RACH fn=%d,%d,%d)\n", + ref->ra, ia_t1, ia_t2, ia_t3, + cr_t1, cr_t2, cr_t3); + } + } + + return 0; +} + +/* 9.1.18 IMMEDIATE ASSIGNMENT is received */ +static int gsm48_rr_rx_imm_ass(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_imm_ass *ia = msgb_l3(msg); + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = cs->si; + int ma_len = msgb_l3len(msg) - sizeof(*ia); + uint8_t ch_type, ch_subch, ch_ts; + struct gsm48_rr_cd cd; +#ifndef TEST_STARTING_TIMER + uint8_t *st, st_len; +#endif + + /* ignore imm.ass. while not camping on a cell */ + if (!cs->selected || cs->neighbour || !s) { + LOGP(DRR, LOGL_INFO, "IMMEDIATED ASSGINMENT ignored, we are " + "have not proper selected the serving cell.\n"); + + return 0; + } + + memset(&cd, 0, sizeof(cd)); + cd.ind_tx_power = rr->cd_now.ind_tx_power; + + if (ma_len < 0 /* mobile allocation IE must be included */ + || ia->mob_alloc_len > ma_len) { /* short read of IE */ + LOGP(DRR, LOGL_NOTICE, "Short read of IMMEDIATE ASSIGNMENT " + "message.\n"); + return -EINVAL; + } + if (ia->mob_alloc_len > 8) { + LOGP(DRR, LOGL_NOTICE, "Moble allocation in IMMEDIATE " + "ASSIGNMENT too large.\n"); + return -EINVAL; + } + + /* starting time */ +#ifdef TEST_STARTING_TIMER + cd.start = 1; + cd.start_tm.fn = (ms->meas.last_fn + TEST_STARTING_TIMER) % 42432; + LOGP(DRR, LOGL_INFO, " TESTING: starting time ahead\n"); +#else + st_len = ma_len - ia->mob_alloc_len; + st = ia->mob_alloc + ia->mob_alloc_len; + if (st_len >= 3 && st[0] == GSM48_IE_START_TIME) + gsm48_decode_start_time(&cd, (struct gsm48_start_time *)(st+1)); +#endif + + /* decode channel description */ + LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT:\n"); + cd.chan_nr = ia->chan_desc.chan_nr; + rsl_dec_chan_nr(cd.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (ia->chan_desc.h0.h) { + cd.h = 1; + gsm48_decode_chan_h1(&ia->chan_desc, &cd.tsc, &cd.maio, + &cd.hsn); + LOGP(DRR, LOGL_INFO, " (ta %d/%dm ra 0x%02x chan_nr 0x%02x " + "MAIO %u HSN %u TS %u SS %u TSC %u)\n", + ia->timing_advance, + ia->timing_advance * GSM_TA_CM / 100, + ia->req_ref.ra, ia->chan_desc.chan_nr, cd.maio, + cd.hsn, ch_ts, ch_subch, cd.tsc); + } else { + cd.h = 0; + gsm48_decode_chan_h0(&ia->chan_desc, &cd.tsc, &cd.arfcn); + if (gsm_refer_pcs(cs->arfcn, s)) + cd.arfcn |= ARFCN_PCS; + LOGP(DRR, LOGL_INFO, " (ta %d/%dm ra 0x%02x chan_nr 0x%02x " + "ARFCN %s TS %u SS %u TSC %u)\n", + ia->timing_advance, + ia->timing_advance * GSM_TA_CM / 100, + ia->req_ref.ra, ia->chan_desc.chan_nr, + gsm_print_arfcn(cd.arfcn), ch_ts, ch_subch, cd.tsc); + } + + /* 3.3.1.1.2: ignore assignment while idle */ + if (rr->state != GSM48_RR_ST_CONN_PEND || rr->wait_assign == 0) { + LOGP(DRR, LOGL_INFO, "Not for us, no request.\n"); + return 0; + } + + if (rr->wait_assign == 2) { + LOGP(DRR, LOGL_INFO, "Ignoring, channel already assigned.\n"); + return 0; + } + + /* request ref */ + if (gsm48_match_ra(ms, &ia->req_ref)) { + /* channel description */ + memcpy(&rr->cd_now, &cd, sizeof(rr->cd_now)); + /* timing advance */ + rr->cd_now.ind_ta = ia->timing_advance; + /* mobile allocation */ + memcpy(&rr->cd_now.mob_alloc_lv, &ia->mob_alloc_len, + ia->mob_alloc_len + 1); + rr->wait_assign = 2; + /* reset scheduler */ + LOGP(DRR, LOGL_INFO, "resetting scheduler\n"); + l1ctl_tx_reset_req(ms, L1CTL_RES_T_SCHED); + + return gsm48_rr_dl_est(ms); + } + LOGP(DRR, LOGL_INFO, "Request, but not for us.\n"); + + return 0; +} + +/* 9.1.19 IMMEDIATE ASSIGNMENT EXTENDED is received */ +static int gsm48_rr_rx_imm_ass_ext(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 gsm48_imm_ass_ext *ia = msgb_l3(msg); + int ma_len = msgb_l3len(msg) - sizeof(*ia); + uint8_t ch_type, ch_subch, ch_ts; + struct gsm48_rr_cd cd1, cd2; +#ifndef TEST_STARTING_TIMER + uint8_t *st, st_len; +#endif + + /* ignore imm.ass.ext while not camping on a cell */ + if (!cs->selected || cs->neighbour || !s) { + LOGP(DRR, LOGL_INFO, "IMMEDIATED ASSGINMENT ignored, we are " + "have not proper selected the serving cell.\n"); + + return 0; + } + + memset(&cd1, 0, sizeof(cd1)); + cd1.ind_tx_power = rr->cd_now.ind_tx_power; + memset(&cd2, 0, sizeof(cd2)); + cd2.ind_tx_power = rr->cd_now.ind_tx_power; + + if (ma_len < 0 /* mobile allocation IE must be included */ + || ia->mob_alloc_len > ma_len) { /* short read of IE */ + LOGP(DRR, LOGL_NOTICE, "Short read of IMMEDIATE ASSIGNMENT " + "EXTENDED message.\n"); + return -EINVAL; + } + if (ia->mob_alloc_len > 4) { + LOGP(DRR, LOGL_NOTICE, "Moble allocation in IMMEDIATE " + "ASSIGNMENT EXTENDED too large.\n"); + return -EINVAL; + } + +#ifdef TEST_STARTING_TIMER + cd1.start = 1; + cd2.start_tm.fn = (ms->meas.last_fn + TEST_STARTING_TIMER) % 42432; + memcpy(&cd2, &cd1, sizeof(cd2)); + LOGP(DRR, LOGL_INFO, " TESTING: starting time ahead\n"); +#else + /* starting time */ + st_len = ma_len - ia->mob_alloc_len; + st = ia->mob_alloc + ia->mob_alloc_len; + if (st_len >= 3 && st[0] == GSM48_IE_START_TIME) { + gsm48_decode_start_time(&cd1, + (struct gsm48_start_time *)(st+1)); + memcpy(&cd2, &cd1, sizeof(cd2)); + } +#endif + + /* decode channel description */ + LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT EXTENDED:\n"); + cd1.chan_nr = ia->chan_desc1.chan_nr; + rsl_dec_chan_nr(cd1.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (ia->chan_desc1.h0.h) { + cd1.h = 1; + gsm48_decode_chan_h1(&ia->chan_desc1, &cd1.tsc, &cd1.maio, + &cd1.hsn); + LOGP(DRR, LOGL_INFO, " assignment 1 (ta %d/%dm ra 0x%02x " + "chan_nr 0x%02x MAIO %u HSN %u TS %u SS %u TSC %u)\n", + ia->timing_advance1, + ia->timing_advance1 * GSM_TA_CM / 100, + ia->req_ref1.ra, ia->chan_desc1.chan_nr, cd1.maio, + cd1.hsn, ch_ts, ch_subch, cd1.tsc); + } else { + cd1.h = 0; + gsm48_decode_chan_h0(&ia->chan_desc1, &cd1.tsc, &cd1.arfcn); + if (gsm_refer_pcs(cs->arfcn, s)) + cd1.arfcn |= ARFCN_PCS; + LOGP(DRR, LOGL_INFO, " assignment 1 (ta %d/%dm ra 0x%02x " + "chan_nr 0x%02x ARFCN %s TS %u SS %u TSC %u)\n", + ia->timing_advance1, + ia->timing_advance1 * GSM_TA_CM / 100, + ia->req_ref1.ra, ia->chan_desc1.chan_nr, + gsm_print_arfcn(cd1.arfcn), ch_ts, ch_subch, cd1.tsc); + } + cd2.chan_nr = ia->chan_desc2.chan_nr; + rsl_dec_chan_nr(cd2.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (ia->chan_desc2.h0.h) { + cd2.h = 1; + gsm48_decode_chan_h1(&ia->chan_desc2, &cd2.tsc, &cd2.maio, + &cd2.hsn); + LOGP(DRR, LOGL_INFO, " assignment 2 (ta %d/%dm ra 0x%02x " + "chan_nr 0x%02x MAIO %u HSN %u TS %u SS %u TSC %u)\n", + ia->timing_advance2, + ia->timing_advance2 * GSM_TA_CM / 100, + ia->req_ref2.ra, ia->chan_desc2.chan_nr, cd2.maio, + cd2.hsn, ch_ts, ch_subch, cd2.tsc); + } else { + cd2.h = 0; + gsm48_decode_chan_h0(&ia->chan_desc2, &cd2.tsc, &cd2.arfcn); + if (gsm_refer_pcs(cs->arfcn, s)) + cd2.arfcn |= ARFCN_PCS; + LOGP(DRR, LOGL_INFO, " assignment 2 (ta %d/%dm ra 0x%02x " + "chan_nr 0x%02x ARFCN %s TS %u SS %u TSC %u)\n", + ia->timing_advance2, + ia->timing_advance2 * GSM_TA_CM / 100, + ia->req_ref2.ra, ia->chan_desc2.chan_nr, + gsm_print_arfcn(cd2.arfcn), ch_ts, ch_subch, cd2.tsc); + } + + /* 3.3.1.1.2: ignore assignment while idle */ + if (rr->state != GSM48_RR_ST_CONN_PEND || rr->wait_assign == 0) { + LOGP(DRR, LOGL_INFO, "Not for us, no request.\n"); + return 0; + } + + if (rr->wait_assign == 2) { + LOGP(DRR, LOGL_INFO, "Ignoring, channel already assigned.\n"); + return 0; + } + + /* request ref 1 */ + if (gsm48_match_ra(ms, &ia->req_ref1)) { + /* channel description */ + memcpy(&rr->cd_now, &cd1, sizeof(rr->cd_now)); + /* timing advance */ + rr->cd_now.ind_ta = ia->timing_advance1; + /* mobile allocation */ + memcpy(&rr->cd_now.mob_alloc_lv, &ia->mob_alloc_len, + ia->mob_alloc_len + 1); + rr->wait_assign = 2; + /* reset scheduler */ + LOGP(DRR, LOGL_INFO, "resetting scheduler\n"); + l1ctl_tx_reset_req(ms, L1CTL_RES_T_SCHED); + + return gsm48_rr_dl_est(ms); + } + /* request ref 2 */ + if (gsm48_match_ra(ms, &ia->req_ref2)) { + /* channel description */ + memcpy(&rr->cd_now, &cd2, sizeof(rr->cd_now)); + /* timing advance */ + rr->cd_now.ind_ta = ia->timing_advance2; + /* mobile allocation */ + memcpy(&rr->cd_now.mob_alloc_lv, &ia->mob_alloc_len, + ia->mob_alloc_len + 1); + rr->wait_assign = 2; + /* reset scheduler */ + LOGP(DRR, LOGL_INFO, "resetting scheduler\n"); + l1ctl_tx_reset_req(ms, L1CTL_RES_T_SCHED); + + return gsm48_rr_dl_est(ms); + } + LOGP(DRR, LOGL_INFO, "Request, but not for us.\n"); + + return 0; +} + +/* 9.1.20 IMMEDIATE ASSIGNMENT REJECT is received */ +static int gsm48_rr_rx_imm_ass_rej(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_imm_ass_rej *ia = msgb_l3(msg); + int payload_len = msgb_l3len(msg) - sizeof(*ia); + int i; + struct gsm48_req_ref *req_ref; + uint8_t t3122_value; + + /* 3.3.1.1.2: ignore assignment while idle */ + if (rr->state != GSM48_RR_ST_CONN_PEND || rr->wait_assign == 0) + return 0; + + if (rr->wait_assign == 2) { + return 0; + } + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of IMMEDIATE ASSIGNMENT " + "REJECT message.\n"); + return -EINVAL; + } + + for (i = 0; i < 4; i++) { + /* request reference */ + req_ref = (struct gsm48_req_ref *) + (((uint8_t *)&ia->req_ref1) + i * 4); + LOGP(DRR, LOGL_INFO, "IMMEDIATE ASSIGNMENT REJECT " + "(ref 0x%02x)\n", req_ref->ra); + if (gsm48_match_ra(ms, req_ref)) { + /* wait indication */ + t3122_value = *(((uint8_t *)&ia->wait_ind1) + i * 4); + if (t3122_value) + start_rr_t3122(rr, t3122_value, 0); + /* start timer 3126 if not already */ + if (!osmo_timer_pending(&rr->t3126)) + start_rr_t3126(rr, 5, 0); /* TODO improve! */ + /* stop assignmnet requests */ + rr->n_chan_req = 0; + + /* wait until timer 3126 expires, then release + * or wait for channel assignment */ + return 0; + } + } + + return 0; +} + +/* 9.1.1 ADDITIONAL ASSIGMENT is received */ +static int gsm48_rr_rx_add_ass(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_add_ass *aa = (struct gsm48_add_ass *)gh->data; + int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*aa); + struct tlv_parsed tp; + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of ADDITIONAL ASSIGNMENT " + "message.\n"); + return gsm48_rr_tx_rr_status(ms, + GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + tlv_parse(&tp, &gsm48_rr_att_tlvdef, aa->data, payload_len, 0, 0); + + return gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_PROT_ERROR_UNSPC); +} + +/* + * measturement reports + */ + +static int gsm48_rr_tx_meas_rep(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_sysinfo *s = ms->cellsel.si; + struct rx_meas_stat *meas = &rr->ms->meas; + struct gsm48_rr_meas *rrmeas = &rr->meas; + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_meas_res *mr; + uint8_t serv_rxlev_full = 0, serv_rxlev_sub = 0, serv_rxqual_full = 0, + serv_rxqual_sub = 0; + uint8_t ta, tx_power; + uint8_t rep_ba = 0, rep_valid = 0, meas_valid = 0, multi_rep = 0; + uint8_t n = 0, rxlev_nc[6], bsic_nc[6], bcch_f_nc[6]; + + /* just in case! */ + if (!s) + return -EINVAL; + + /* check if SI5* is completely received, check BA-IND */ + if (s->si5 + && (!s->nb_ext_ind_si5 || s->si5bis)) { + rep_ba = s->nb_ba_ind_si5; + if ((s->si5bis && s->nb_ext_ind_si5 + && s->nb_ba_ind_si5bis != rep_ba) + || (s->si5ter && s->nb_ba_ind_si5ter != rep_ba)) { + LOGP(DRR, LOGL_NOTICE, "BA-IND missmatch on SI5*"); + } else + rep_valid = 1; + } + + /* check for valid measurements, any frame must exist */ + if (meas->frames) { + meas_valid = 1; + serv_rxlev_full = serv_rxlev_sub = + (meas->rxlev + (meas->frames / 2)) / meas->frames; + serv_rxqual_full = serv_rxqual_sub = 0; // FIXME + } + + memset(&rxlev_nc, 0, sizeof(rxlev_nc)); + memset(&bsic_nc, 0, sizeof(bsic_nc)); + memset(&bcch_f_nc, 0, sizeof(bcch_f_nc)); + if (rep_valid) { + int8_t strongest, current; + uint8_t ncc; + int i, index; + + /* multiband reporting, if not: 0 = normal reporting */ + if (s->si5ter) + multi_rep = s->nb_multi_rep_si5ter; + + /* get 6 strongest measurements */ + // FIXME: multiband report + strongest = 127; /* infinite */ + for (n = 0; n < 6; n++) { + current = -128; /* -infinite */ + index = 0; + for (i = 0; i < rrmeas->nc_num; i++) { + /* only check if NCC is permitted */ + ncc = rrmeas->nc_bsic[i] >> 3; + if ((s->nb_ncc_permitted_si6 & (1 << ncc)) + && rrmeas->nc_rxlev[i] > current + && rrmeas->nc_rxlev[i] < strongest) { + current = rrmeas->nc_rxlev[i]; + index = i; + } + } + if (current == -128) /* no more found */ + break; + rxlev_nc[n] = rrmeas->nc_rxlev[index] + 110; + bsic_nc[n] = rrmeas->nc_bsic[index]; + bcch_f_nc[n] = index; + } + } + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + + /* use indicated tx-power and TA (not the altered ones) */ + tx_power = rr->cd_now.ind_tx_power; + // FIXME: degrade power to the max supported level + ta = rr->cd_now.ind_ta; + + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + mr = (struct gsm48_meas_res *) msgb_put(nmsg, sizeof(*mr)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_MEAS_REP; + + /* measurement results */ + mr->rxlev_full = serv_rxlev_full; + mr->rxlev_sub = serv_rxlev_sub; + mr->rxqual_full = serv_rxqual_full; + mr->rxqual_sub = serv_rxqual_sub; + mr->dtx_used = 0; // FIXME: no DTX yet + mr->ba_used = rep_ba; + mr->meas_valid = !meas_valid; /* 0 = valid */ + if (rep_valid) { + mr->no_nc_n_hi = n >> 2; + mr->no_nc_n_lo = n & 3; + } else { + /* no results for serving cells */ + mr->no_nc_n_hi = 1; + mr->no_nc_n_lo = 3; + } + mr->rxlev_nc1 = rxlev_nc[0]; + mr->rxlev_nc2_hi = rxlev_nc[1] >> 1; + mr->rxlev_nc2_lo = rxlev_nc[1] & 1; + mr->rxlev_nc3_hi = rxlev_nc[2] >> 2; + mr->rxlev_nc3_lo = rxlev_nc[2] & 3; + mr->rxlev_nc4_hi = rxlev_nc[3] >> 3; + mr->rxlev_nc4_lo = rxlev_nc[3] & 7; + mr->rxlev_nc5_hi = rxlev_nc[4] >> 4; + mr->rxlev_nc5_lo = rxlev_nc[4] & 15; + mr->rxlev_nc6_hi = rxlev_nc[5] >> 5; + mr->rxlev_nc6_lo = rxlev_nc[5] & 31; + mr->bsic_nc1_hi = bsic_nc[0] >> 3; + mr->bsic_nc1_lo = bsic_nc[0] & 7; + mr->bsic_nc2_hi = bsic_nc[1] >> 4; + mr->bsic_nc2_lo = bsic_nc[1] & 15; + mr->bsic_nc3_hi = bsic_nc[2] >> 5; + mr->bsic_nc3_lo = bsic_nc[2] & 31; + mr->bsic_nc4 = bsic_nc[3]; + mr->bsic_nc5 = bsic_nc[4]; + mr->bsic_nc6 = bsic_nc[5]; + mr->bcch_f_nc1 = bcch_f_nc[0]; + mr->bcch_f_nc2 = bcch_f_nc[1]; + mr->bcch_f_nc3 = bcch_f_nc[2]; + mr->bcch_f_nc4 = bcch_f_nc[3]; + mr->bcch_f_nc5_hi = bcch_f_nc[4] >> 1; + mr->bcch_f_nc5_lo = bcch_f_nc[4] & 1; + mr->bcch_f_nc6_hi = bcch_f_nc[5] >> 2; + mr->bcch_f_nc6_lo = bcch_f_nc[5] & 3; + + LOGP(DRR, LOGL_INFO, "MEAS REP: pwr=%d TA=%d meas-invalid=%d " + "rxlev-full=%d rxlev-sub=%d rxqual-full=%d rxqual-sub=%d " + "dtx %d ba %d no-ncell-n %d\n", tx_power, ta, mr->meas_valid, + mr->rxlev_full - 110, mr->rxlev_sub - 110, + mr->rxqual_full, mr->rxqual_sub, mr->dtx_used, mr->ba_used, + (mr->no_nc_n_hi << 2) | mr->no_nc_n_lo); + + msgb_tv16_push(nmsg, RSL_IE_L3_INFO, + nmsg->tail - (uint8_t *)msgb_l3(nmsg)); + msgb_push(nmsg, 2 + 2); + nmsg->data[0] = RSL_IE_TIMING_ADVANCE; + nmsg->data[1] = ta; + nmsg->data[2] = RSL_IE_MS_POWER; + nmsg->data[3] = tx_power; + rsl_rll_push_hdr(nmsg, RSL_MT_UNIT_DATA_REQ, rr->cd_now.chan_nr, + 0x40, 1); + + return lapdm_rslms_recvmsg(nmsg, &ms->lapdm_channel); +} + +/* + * link establishment and release + */ + +/* process "Loss Of Signal" */ +int gsm48_rr_los(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + uint8_t *mode; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + + LOGP(DSUM, LOGL_INFO, "Radio link lost signal\n"); + + /* stop T3211 if running */ + stop_rr_t3110(rr); + + switch(rr->state) { + case GSM48_RR_ST_CONN_PEND: + LOGP(DRR, LOGL_INFO, "LOS during RACH request\n"); + + /* stop pending RACH timer */ + stop_rr_t3126(rr); + break; + case GSM48_RR_ST_DEDICATED: + LOGP(DRR, LOGL_INFO, "LOS during dedicated mode, release " + "locally\n"); + + new_rr_state(rr, GSM48_RR_ST_REL_PEND); + + /* release message */ + rel_local: + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + mode = msgb_put(nmsg, 2); + mode[0] = RSL_IE_RELEASE_MODE; + mode[1] = 1; /* local release */ + /* start release */ + gsm48_send_rsl_nol3(ms, RSL_MT_REL_REQ, nmsg, 0); + /* release SAPI 3 link, if exits */ + gsm48_release_sapi3_link(ms); + return 0; + case GSM48_RR_ST_REL_PEND: + LOGP(DRR, LOGL_INFO, "LOS during RR release procedure, release " + "locally\n"); + + /* stop pending RACH timer */ + stop_rr_t3110(rr); + + /* release locally */ + goto rel_local; + default: + /* this should not happen */ + LOGP(DRR, LOGL_ERROR, "LOS in IDLE state, ignoring\n"); + return -EINVAL; + } + + /* send inication to upper layer */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = RR_REL_CAUSE_LOST_SIGNAL; + gsm48_rr_upmsg(ms, nmsg); + + /* return idle */ + new_rr_state(rr, GSM48_RR_ST_IDLE); + return 0; +} + +/* activation of channel in dedicated mode */ +static int gsm48_rr_activate_channel(struct osmocom_ms *ms, + struct gsm48_rr_cd *cd, uint16_t *ma, uint8_t ma_len) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm_settings *set = &ms->settings; + struct gsm48_sysinfo *s = ms->cellsel.si; + struct rx_meas_stat *meas = &ms->meas; + uint8_t ch_type, ch_subch, ch_ts; + uint8_t timeout = 64; + + /* setting (new) timing advance */ + LOGP(DRR, LOGL_INFO, "setting indicated TA %d (actual TA %d)\n", + cd->ind_ta, cd->ind_ta - set->alter_delay); + l1ctl_tx_param_req(ms, cd->ind_ta - set->alter_delay, + (set->alter_tx_power) ? set->alter_tx_power_value + : cd->ind_tx_power); + + /* reset measurement and link timeout */ + meas->ds_fail = 0; + if (s) { + if (s->sacch_radio_link_timeout) { + timeout = s->sacch_radio_link_timeout; + LOGP(DRR, LOGL_INFO, "using last SACCH timeout %d\n", + timeout); + } else if (s->bcch_radio_link_timeout) { + timeout = s->bcch_radio_link_timeout; + LOGP(DRR, LOGL_INFO, "using last BCCH timeout %d\n", + timeout); + } + } + meas->rl_fail = meas->s = timeout; + + /* setting initial (invalid) measurement report, resetting SI5* */ + if (s) { + memset(s->si5_msg, 0, sizeof(s->si5_msg)); + memset(s->si5b_msg, 0, sizeof(s->si5b_msg)); + memset(s->si5t_msg, 0, sizeof(s->si5t_msg)); + } + meas->frames = meas->snr = meas->berr = meas->rxlev = 0; + rr->meas.nc_num = 0; + stop_rr_t_meas(rr); + start_rr_t_meas(rr, 1, 0); + gsm48_rr_tx_meas_rep(ms); + + /* establish */ + LOGP(DRR, LOGL_INFO, "establishing channel in dedicated mode\n"); + rsl_dec_chan_nr(cd->chan_nr, &ch_type, &ch_subch, &ch_ts); + LOGP(DRR, LOGL_INFO, " Channel type %d, subch %d, ts %d, mode %d, " + "audio-mode %d, cipher %d\n", ch_type, ch_subch, ch_ts, + cd->mode, rr->audio_mode, rr->cipher_type + 1); + if (cd->h) + l1ctl_tx_dm_est_req_h1(ms, cd->maio, cd->hsn, + ma, ma_len, cd->chan_nr, cd->tsc, cd->mode, + rr->audio_mode); + else + l1ctl_tx_dm_est_req_h0(ms, cd->arfcn, cd->chan_nr, cd->tsc, + cd->mode, rr->audio_mode); + rr->dm_est = 1; + + /* old SI 5/6 are not valid on a new dedicated channel */ + s->si5 = s->si5bis = s->si5ter = s->si6 = 0; + + if (rr->cipher_on) + l1ctl_tx_crypto_req(ms, rr->cipher_type + 1, subscr->key, 8); + + return 0; +} + +/* frequency change of channel "after time" */ +static int gsm48_rr_channel_after_time(struct osmocom_ms *ms, + struct gsm48_rr_cd *cd, uint16_t *ma, uint8_t ma_len, uint16_t fn) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_rrlayer *rr = &ms->rrlayer; + + if (cd->h) + l1ctl_tx_dm_freq_req_h1(ms, cd->maio, cd->hsn, + ma, ma_len, cd->tsc, fn); + else + l1ctl_tx_dm_freq_req_h0(ms, cd->arfcn, cd->tsc, fn); + + if (rr->cipher_on) + l1ctl_tx_crypto_req(ms, rr->cipher_type + 1, subscr->key, 8); + + gsm48_rr_set_mode(ms, cd->chan_nr, cd->mode); + + return 0; +} + +/* render list of hopping channels from channel description elements */ +static int gsm48_rr_render_ma(struct osmocom_ms *ms, struct gsm48_rr_cd *cd, + uint16_t *ma, uint8_t *ma_len) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = cs->si; + struct gsm_settings *set = &ms->settings; + int i, pcs, index; + uint16_t arfcn; + + pcs = gsm_refer_pcs(cs->arfcn, s) ? ARFCN_PCS : 0; + + /* no hopping (no MA), channel description is valid */ + if (!cd->h) { + *ma_len = 0; + return 0; + } + + /* decode mobile allocation */ + if (cd->mob_alloc_lv[0]) { + struct gsm_sysinfo_freq *freq = s->freq; + + LOGP(DRR, LOGL_INFO, "decoding mobile allocation\n"); + + if (cd->cell_desc_lv[0]) { + LOGP(DRR, LOGL_INFO, "using cell channel descr.\n"); + if (cd->cell_desc_lv[0] != 16) { + LOGP(DRR, LOGL_ERROR, "cell channel descr. " + "has invalid lenght\n"); + return GSM48_RR_CAUSE_ABNORMAL_UNSPEC; + } + gsm48_decode_freq_list(freq, cd->cell_desc_lv + 1, 16, + 0xce, FREQ_TYPE_SERV); + } + + gsm48_decode_mobile_alloc(freq, cd->mob_alloc_lv + 1, + cd->mob_alloc_lv[0], ma, ma_len, 0); + if (*ma_len < 1) { + LOGP(DRR, LOGL_NOTICE, "mobile allocation with no " + "frequency available\n"); + return GSM48_RR_CAUSE_NO_CELL_ALLOC_A; + + } + } else + /* decode frequency list */ + if (cd->freq_list_lv[0]) { + struct gsm_sysinfo_freq f[1024]; + int j = 0; + + LOGP(DRR, LOGL_INFO, "decoding frequency list\n"); + + /* get bitmap */ + if (gsm48_decode_freq_list(f, cd->freq_list_lv + 1, + cd->freq_list_lv[0], 0xce, FREQ_TYPE_SERV)) { + LOGP(DRR, LOGL_NOTICE, "frequency list invalid\n"); + return GSM48_RR_CAUSE_ABNORMAL_UNSPEC; + } + + /* collect channels from bitmap (1..1023,0) */ + for (i = 1; i <= 1024; i++) { + if ((f[i & 1023].mask & FREQ_TYPE_SERV)) { + LOGP(DRR, LOGL_INFO, "Listed ARFCN #%d: %s\n", + j, gsm_print_arfcn((i & 1023) | pcs)); + if (j == 64) { + LOGP(DRR, LOGL_NOTICE, "frequency list " + "exceeds 64 entries!\n"); + return GSM48_RR_CAUSE_ABNORMAL_UNSPEC; + } + ma[j++] = i & 1023; + } + } + *ma_len = j; + } else + /* decode frequency channel sequence */ + if (cd->freq_seq_lv[0]) { + int j = 0, inc; + + LOGP(DRR, LOGL_INFO, "decoding frequency channel sequence\n"); + + if (cd->freq_seq_lv[0] != 9) { + LOGP(DRR, LOGL_NOTICE, "invalid frequency channel " + "sequence\n"); + return GSM48_RR_CAUSE_ABNORMAL_UNSPEC; + } + arfcn = cd->freq_seq_lv[1] & 0x7f; + LOGP(DRR, LOGL_INFO, "Listed Sequence ARFCN #%d: %s\n", j, + gsm_print_arfcn(arfcn | pcs)); + ma[j++] = arfcn; + for (i = 0; i < 16; i++) { + if ((i & 1)) + inc = cd->freq_seq_lv[2 + (i >> 1)] & 0x0f; + else + inc = cd->freq_seq_lv[2 + (i >> 1)] >> 4; + if (inc) { + arfcn += inc; + LOGP(DRR, LOGL_INFO, "Listed Sequence ARFCN " + "#%d: %s\n", j, + gsm_print_arfcn(i | pcs)); + ma[j++] = arfcn; + } else + arfcn += 15; + } + *ma_len = j; + } else { + LOGP(DRR, LOGL_NOTICE, "hopping, but nothing that tells us " + "a sequence\n"); + return GSM48_RR_CAUSE_ABNORMAL_UNSPEC; + } + + /* convert to band_arfcn and check for unsported frequency */ + for (i = 0; i < *ma_len; i++) { + arfcn = ma[i] | pcs; + ma[i] = arfcn; + index = arfcn2index(arfcn); + if (!(set->freq_map[index >> 3] & (1 << (index & 7)))) { + LOGP(DRR, LOGL_NOTICE, "Hopping ARFCN %s not " + "supported\n", gsm_print_arfcn(arfcn)); + return GSM48_RR_CAUSE_FREQ_NOT_IMPL; + } + } + + return 0; +} + +/* activate link and send establish request */ +static int gsm48_rr_dl_est(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm_subscriber *subscr = &ms->subscr; + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_pag_rsp *pr; + uint8_t mi[11]; + uint16_t ma[64]; + uint8_t ma_len; + + /* 3.3.1.1.3.1 */ + stop_rr_t3126(rr); + + /* check if we have to change channel at starting time (we delay) */ + if (rr->cd_now.start) { + int32_t now, start, diff; + uint32_t start_mili = 0; + + /* how much time do we have left? */ + now = ms->meas.last_fn % 42432; + start = rr->cd_now.start_tm.fn % 42432; + diff = start - now; + if (diff < 0) + diff += 42432; + LOGP(DRR, LOGL_INFO, " (Tnow %d Tstart %d diff %d)\n", + now, start, diff); + start_mili = (uint32_t)diff * 19580 / 42432 * 10; + if (diff >= 32024 || !start_mili) { + LOGP(DRR, LOGL_INFO, " -> Start time already " + "elapsed\n"); + rr->cd_now.start = 0; + } else { + LOGP(DRR, LOGL_INFO, " -> Start time is %d ms in the " + "future\n", start_mili); + } + +#ifndef TEST_FREQUENCY_MOD + /* schedule start of IMM.ASS */ + rr->modify_state = GSM48_RR_MOD_IMM_ASS; + start_rr_t_starting(rr, start_mili / 1000, + (start_mili % 1000) * 1000); + /* when timer fires, start time is already elapsed */ + rr->cd_now.start = 0; + + return 0; +#endif + } + + /* get hopping sequence, if required */ + if (gsm48_rr_render_ma(ms, &rr->cd_now, ma, &ma_len)) + return -EINVAL; + + /* clear all sequence numbers for all possible PDs */ + rr->v_sd = 0; + + /* send DL_EST_REQ */ + if (rr->rr_est_msg) { + LOGP(DRR, LOGL_INFO, "sending establish message\n"); + + /* use queued message */ + nmsg = rr->rr_est_msg; + rr->rr_est_msg = 0; + + /* set sequence number and increment */ + gsm48_apply_v_sd(rr, nmsg); + } else { + /* create paging response */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_PAG_RESP; + pr = (struct gsm48_pag_rsp *) msgb_put(nmsg, sizeof(*pr)); + /* key sequence */ + pr->key_seq = gsm_subscr_get_key_seq(ms, subscr); + /* classmark 2 */ + pr->cm2_len = sizeof(pr->cm2); + gsm48_rr_enc_cm2(ms, &pr->cm2, rr->cd_now.arfcn); + /* mobile identity */ + if (ms->subscr.tmsi != 0xffffffff + && ms->subscr.mcc == cs->sel_mcc + && ms->subscr.mnc == cs->sel_mnc + && ms->subscr.lac == cs->sel_lac + && rr->paging_mi_type == GSM_MI_TYPE_TMSI) { + gsm48_generate_mid_from_tmsi(mi, subscr->tmsi); + LOGP(DRR, LOGL_INFO, "sending paging response with " + "TMSI\n"); + } else if (subscr->imsi[0]) { + gsm48_generate_mid_from_imsi(mi, subscr->imsi); + LOGP(DRR, LOGL_INFO, "sending paging response with " + "IMSI\n"); + } else { + mi[1] = 1; + mi[2] = 0xf0 | GSM_MI_TYPE_NONE; + LOGP(DRR, LOGL_INFO, "sending paging response without " + "TMSI/IMSI\n"); + } + msgb_put(nmsg, 1 + mi[1]); + memcpy(pr->data, mi + 1, 1 + mi[1]); + } + +#ifdef TEST_FREQUENCY_MOD + LOGP(DRR, LOGL_INFO, " TESTING: frequency modify IMM.ASS\n"); + memcpy(&rr->cd_before, &rr->cd_now, sizeof(rr->cd_before)); + rr->cd_before.h = 0; + rr->cd_before.arfcn = 0; + /* activate channel */ + gsm48_rr_activate_channel(ms, &rr->cd_before, ma, ma_len); + /* render channel "after time" */ + gsm48_rr_render_ma(ms, &rr->cd_now, ma, &ma_len); + /* schedule change of channel */ + gsm48_rr_channel_after_time(ms, &rr->cd_now, ma, ma_len, + rr->cd_now.start_tm.fn); +#else + /* activate channel */ + gsm48_rr_activate_channel(ms, &rr->cd_now, ma, ma_len); +#endif + + /* set T200 of SAPI 0 */ + ms->lapdm_channel.lapdm_dcch.datalink[DL_SAPI0].dl.t200_sec = + T200_DCCH; + ms->lapdm_channel.lapdm_dcch.datalink[DL_SAPI0].dl.t200_usec = 0; + + /* start establishmnet */ + return gsm48_send_rsl(ms, RSL_MT_EST_REQ, nmsg, 0); +} + +/* the link is established */ +static int gsm48_rr_estab_cnf(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + uint8_t *mode; + struct msgb *nmsg; + + /* if MM has releases before confirm, we start release */ + if (rr->state == GSM48_RR_ST_REL_PEND) { + LOGP(DRR, LOGL_INFO, "MM already released RR.\n"); + /* release message */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + mode = msgb_put(nmsg, 2); + mode[0] = RSL_IE_RELEASE_MODE; + mode[1] = 0; /* normal release */ + /* start release */ + return gsm48_send_rsl_nol3(ms, RSL_MT_REL_REQ, nmsg, 0); + } + + /* 3.3.1.1.4 */ + new_rr_state(rr, GSM48_RR_ST_DEDICATED); + + /* send confirm to upper layer */ + nmsg = gsm48_rr_msgb_alloc( + (rr->rr_est_req) ? GSM48_RR_EST_CNF : GSM48_RR_EST_IND); + if (!nmsg) + return -ENOMEM; + return gsm48_rr_upmsg(ms, nmsg); +} + +/* the link is released in pending state (by l2) */ +static int gsm48_rr_rel_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + + /* switch back to old channel, if modify/ho failed */ + switch (rr->modify_state) { + case GSM48_RR_MOD_ASSIGN: + case GSM48_RR_MOD_HANDO: + /* channel is deactivate there */ + return gsm48_rr_rel_cnf(ms, msg); + case GSM48_RR_MOD_ASSIGN_RESUME: + case GSM48_RR_MOD_HANDO_RESUME: + rr->modify_state = GSM48_RR_MOD_NONE; + break; + } + + LOGP(DSUM, LOGL_INFO, "Radio link is released\n"); + + /* send inication to upper layer */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = RR_REL_CAUSE_NORMAL; + gsm48_rr_upmsg(ms, nmsg); + + /* start release timer, so UA will be transmitted */ + start_rr_t_rel_wait(rr, 1, 500000); + + /* pending release */ + new_rr_state(rr, GSM48_RR_ST_REL_PEND); + + /* also release SAPI 3 link, if exists */ + gsm48_release_sapi3_link(ms); + + return 0; +} + +/* 9.1.7 CHANNEL RELEASE is received */ +static int gsm48_rr_rx_chan_rel(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_chan_rel *cr = (struct gsm48_chan_rel *)gh->data; + int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*cr); + struct tlv_parsed tp; + struct msgb *nmsg; + uint8_t *mode; + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of CHANNEL RELEASE " + "message.\n"); + return gsm48_rr_tx_rr_status(ms, + GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + tlv_parse(&tp, &gsm48_rr_att_tlvdef, cr->data, payload_len, 0, 0); + + LOGP(DRR, LOGL_INFO, "channel release request with cause 0x%02x\n", + cr->rr_cause); + + /* BA range */ + if (TLVP_PRESENT(&tp, GSM48_IE_BA_RANGE)) { + gsm48_decode_ba_range(TLVP_VAL(&tp, GSM48_IE_BA_RANGE), + *(TLVP_VAL(&tp, GSM48_IE_BA_RANGE) - 1), rr->ba_range, + &rr->ba_ranges, + sizeof(rr->ba_range) / sizeof(rr->ba_range[0])); + /* NOTE: the ranges are kept until IDLE state is returned + * (see new_rr_state) + */ + } + + new_rr_state(rr, GSM48_RR_ST_REL_PEND); + + /* start T3110, so that two DISCs can be sent due to T200 timeout */ + start_rr_t3110(rr, 1, 500000); + + /* disconnect the main signalling link */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + mode = msgb_put(nmsg, 2); + mode[0] = RSL_IE_RELEASE_MODE; + mode[1] = 0; /* normal release */ + gsm48_send_rsl_nol3(ms, RSL_MT_REL_REQ, nmsg, 0); + + /* release SAPI 3 link, if exits */ + gsm48_release_sapi3_link(ms); + return 0; +} + +/* + * frequency redefition, chanel mode modify, assignment, and handover + */ + +/* set channel mode in case of TCH */ +static int gsm48_rr_set_mode(struct osmocom_ms *ms, uint8_t chan_nr, + uint8_t mode) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + uint8_t ch_type, ch_subch, ch_ts; + + /* only apply mode to TCH/F or TCH/H */ + rsl_dec_chan_nr(chan_nr, &ch_type, &ch_subch, &ch_ts); + if (ch_type != RSL_CHAN_Bm_ACCHs + && ch_type != RSL_CHAN_Lm_ACCHs) + return -ENOTSUP; + + /* setting (new) timing advance */ + LOGP(DRR, LOGL_INFO, "setting TCH mode to %d, audio mode to %d\n", + mode, rr->audio_mode); + l1ctl_tx_tch_mode_req(ms, mode, rr->audio_mode); + + return 0; +} + +/* 9.1.13 FREQUENCY REDEFINITION is received */ +static int gsm48_rr_rx_frq_redef(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 gsm48_frq_redef *fr = msgb_l3(msg); + int mob_al_len = msgb_l3len(msg) - sizeof(*fr); + uint8_t ch_type, ch_subch, ch_ts; + struct gsm48_rr_cd cd; + uint8_t cause; + uint8_t *st; + uint16_t ma[64]; + uint8_t ma_len; + + memcpy(&cd, &rr->cd_now, sizeof(cd)); + + if (mob_al_len < 0 /* mobile allocation IE must be included */ + || fr->mob_alloc_len + 2 > mob_al_len) { /* short read of IE */ + LOGP(DRR, LOGL_NOTICE, "Short read of FREQUENCY REDEFINITION " + "message.\n"); + return -EINVAL; + } + if (fr->mob_alloc_len > 8) { + LOGP(DRR, LOGL_NOTICE, "Moble allocation in FREQUENCY " + "REDEFINITION too large.\n"); + return -EINVAL; + } + + /* decode channel description */ + LOGP(DRR, LOGL_INFO, "FREQUENCY REDEFINITION:\n"); + cd.chan_nr = fr->chan_desc.chan_nr; + rsl_dec_chan_nr(cd.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (fr->chan_desc.h0.h) { + cd.h = 1; + gsm48_decode_chan_h1(&fr->chan_desc, &cd.tsc, &cd.maio, + &cd.hsn); + LOGP(DRR, LOGL_INFO, " (MAIO %u HSN %u TS %u SS %u TSC %u)\n", + cd.maio, cd.hsn, ch_ts, ch_subch, cd.tsc); + } else { + cd.h = 0; + gsm48_decode_chan_h0(&fr->chan_desc, &cd.tsc, &cd.arfcn); + if (gsm_refer_pcs(cs->arfcn, s)) + cd.arfcn |= ARFCN_PCS; + LOGP(DRR, LOGL_INFO, " (ARFCN %s TS %u SS %u TSC %u)\n", + gsm_print_arfcn(cd.arfcn), ch_ts, ch_subch, cd.tsc); + } + + /* mobile allocation */ + memcpy(rr->cd_now.mob_alloc_lv, &fr->mob_alloc_len, + fr->mob_alloc_len + 1); + + /* starting time */ + st = fr->mob_alloc + fr->mob_alloc_len; + gsm48_decode_start_time(&cd, (struct gsm48_start_time *)(st+1)); + + /* cell channel description */ + if (mob_al_len >= fr->mob_alloc_len + 2 + 17 + && fr->mob_alloc[fr->mob_alloc_len + 2] == GSM48_IE_CELL_CH_DESC) { + const uint8_t *v = fr->mob_alloc + fr->mob_alloc_len + 2 + 1; + + LOGP(DRR, LOGL_INFO, " using cell channel description)\n"); + cd.cell_desc_lv[0] = 16; + memcpy(cd.cell_desc_lv + 1, v, 17); + } + + /* render channel "after time" */ + cause = gsm48_rr_render_ma(ms, &rr->cd_now, ma, &ma_len); + if (cause) + return gsm48_rr_tx_rr_status(ms, cause); + + /* update to new channel data */ + memcpy(&rr->cd_now, &cd, sizeof(rr->cd_now)); + + /* schedule change of channel */ + gsm48_rr_channel_after_time(ms, &rr->cd_now, ma, ma_len, + rr->cd_now.start_tm.fn); + + rr->cd_now.start = 0; + + return 0; +} + +/* 9.1.6 sending CHANNEL MODE MODIFY ACKNOWLEDGE */ +static int gsm48_rr_tx_chan_modify_ack(struct osmocom_ms *ms, + struct gsm48_chan_desc *cd, uint8_t mode) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_chan_mode_modify *cm; + + LOGP(DRR, LOGL_INFO, "CHAN.MODE.MOD ACKNOWLEDGE\n"); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + cm = (struct gsm48_chan_mode_modify *) msgb_put(nmsg, sizeof(*cm)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_CHAN_MODE_MODIF_ACK; + + /* CD */ + memcpy(&cm->chan_desc, cd, sizeof(struct gsm48_chan_desc)); + /* mode */ + cm->mode = mode; + + return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, nmsg, 0); +} + +/* 9.1.5 CHANNEL MODE MODIFY is received */ +static int gsm48_rr_rx_chan_modify(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 gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_chan_mode_modify *cm = + (struct gsm48_chan_mode_modify *)gh->data; + int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*cm); + struct gsm48_rr_cd *cd = &rr->cd_now; + uint8_t ch_type, ch_subch, ch_ts; + uint8_t cause; + + LOGP(DRR, LOGL_INFO, "CHANNEL MODE MODIFY\n"); + + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of CHANNEL MODE MODIFY " + "message.\n"); + return gsm48_rr_tx_rr_status(ms, + GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + + /* decode channel description */ + cd->chan_nr = cm->chan_desc.chan_nr; + rsl_dec_chan_nr(cd->chan_nr, &ch_type, &ch_subch, &ch_ts); + if (cm->chan_desc.h0.h) { + cd->h = 1; + gsm48_decode_chan_h1(&cm->chan_desc, &cd->tsc, &cd->maio, + &cd->hsn); + LOGP(DRR, LOGL_INFO, " (chan_nr 0x%02x MAIO %u HSN %u TS %u " + "SS %u TSC %u mode %u)\n", cm->chan_desc.chan_nr, + cd->maio, cd->hsn, ch_ts, ch_subch, cd->tsc, cm->mode); + } else { + cd->h = 0; + gsm48_decode_chan_h0(&cm->chan_desc, &cd->tsc, &cd->arfcn); + if (gsm_refer_pcs(cs->arfcn, s)) + cd->arfcn |= ARFCN_PCS; + LOGP(DRR, LOGL_INFO, " (chan_nr 0x%02x ARFCN %s TS %u SS %u " + "TSC %u mode %u)\n", cm->chan_desc.chan_nr, + gsm_print_arfcn(cd->arfcn), ch_ts, ch_subch, cd->tsc, + cm->mode); + } + /* mode */ + cause = gsm48_rr_check_mode(ms, cd->chan_nr, cm->mode); + if (cause) + return gsm48_rr_tx_rr_status(ms, cause); + cd->mode = cm->mode; + gsm48_rr_set_mode(ms, cd->chan_nr, cd->mode); + + return gsm48_rr_tx_chan_modify_ack(ms, &cm->chan_desc, cm->mode); +} + +/* 9.1.3 sending ASSIGNMENT COMPLETE */ +static int gsm48_rr_tx_ass_cpl(struct osmocom_ms *ms, uint8_t cause) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_ass_cpl *ac; + + LOGP(DRR, LOGL_INFO, "ASSIGNMENT COMPLETE (cause #%d)\n", cause); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + ac = (struct gsm48_ass_cpl *) msgb_put(nmsg, sizeof(*ac)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_ASS_COMPL; + + /* RR_CAUSE */ + ac->rr_cause = cause; + + /* set T200 of SAPI 0 */ + ms->lapdm_channel.lapdm_dcch.datalink[DL_SAPI0].dl.t200_sec = + T200_DCCH; + ms->lapdm_channel.lapdm_dcch.datalink[DL_SAPI0].dl.t200_usec = 0; + + return gsm48_send_rsl(ms, RSL_MT_RES_REQ, nmsg, 0); +} + +/* 9.1.4 sending ASSIGNMENT FAILURE */ +static int gsm48_rr_tx_ass_fail(struct osmocom_ms *ms, uint8_t cause, + uint8_t rsl_prim) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_ass_fail *af; + + LOGP(DRR, LOGL_INFO, "ASSIGNMENT FAILURE (cause #%d)\n", cause); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + af = (struct gsm48_ass_fail *) msgb_put(nmsg, sizeof(*af)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_ASS_COMPL; + + /* RR_CAUSE */ + af->rr_cause = cause; + + return gsm48_send_rsl(ms, rsl_prim, nmsg, 0); +} + +/* 9.1.2 ASSIGNMENT COMMAND is received */ +static int gsm48_rr_rx_ass_cmd(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 gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_ass_cmd *ac = (struct gsm48_ass_cmd *)gh->data; + int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*ac); + struct tlv_parsed tp; + struct gsm48_rr_cd *cda = &rr->cd_after; + struct gsm48_rr_cd *cdb = &rr->cd_before; + uint8_t ch_type, ch_subch, ch_ts; + uint8_t before_time = 0; + uint16_t ma[64]; + uint8_t ma_len; + uint32_t start_mili = 0; + uint8_t cause; + struct msgb *nmsg; + + + LOGP(DRR, LOGL_INFO, "ASSIGNMENT COMMAND\n"); + + memset(cda, 0, sizeof(*cda)); + cda->ind_tx_power = rr->cd_now.ind_tx_power; + memset(cdb, 0, sizeof(*cdb)); + cdb->ind_tx_power = rr->cd_now.ind_tx_power; + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of ASSIGNMENT COMMAND " + "message.\n"); + return gsm48_rr_tx_rr_status(ms, + GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + tlv_parse(&tp, &gsm48_rr_att_tlvdef, ac->data, payload_len, 0, 0); + + /* decode channel description (before time) */ + if (TLVP_PRESENT(&tp, GSM48_IE_CH_DESC_1_BEFORE)) { + struct gsm48_chan_desc *ccd = (struct gsm48_chan_desc *) + TLVP_VAL(&tp, GSM48_IE_CH_DESC_1_BEFORE); + cdb->chan_nr = ccd->chan_nr; + rsl_dec_chan_nr(cdb->chan_nr, &ch_type, &ch_subch, &ch_ts); + if (ccd->h0.h) { + cdb->h = 1; + gsm48_decode_chan_h1(ccd, &cdb->tsc, &cdb->maio, + &cdb->hsn); + LOGP(DRR, LOGL_INFO, " before: (chan_nr 0x%02x MAIO %u " + "HSN %u TS %u SS %u TSC %u)\n", ccd->chan_nr, + cdb->maio, cdb->hsn, ch_ts, ch_subch, cdb->tsc); + } else { + cdb->h = 0; + gsm48_decode_chan_h0(ccd, &cdb->tsc, &cdb->arfcn); + if (gsm_refer_pcs(cs->arfcn, s)) + cdb->arfcn |= ARFCN_PCS; + LOGP(DRR, LOGL_INFO, " before: (chan_nr 0x%02x " + "ARFCN %s TS %u SS %u TSC %u)\n", ccd->chan_nr, + gsm_print_arfcn(cdb->arfcn), + ch_ts, ch_subch, cdb->tsc); + } + before_time = 1; + } + + /* decode channel description (after time) */ + cda->chan_nr = ac->chan_desc.chan_nr; + rsl_dec_chan_nr(cda->chan_nr, &ch_type, &ch_subch, &ch_ts); + if (ac->chan_desc.h0.h) { + cda->h = 1; + gsm48_decode_chan_h1(&ac->chan_desc, &cda->tsc, &cda->maio, + &cda->hsn); + LOGP(DRR, LOGL_INFO, " after: (chan_nr 0x%02x MAIO %u HSN %u " + "TS %u SS %u TSC %u)\n", ac->chan_desc.chan_nr, + cda->maio, cda->hsn, ch_ts, ch_subch, cda->tsc); + } else { + cda->h = 0; + gsm48_decode_chan_h0(&ac->chan_desc, &cda->tsc, &cda->arfcn); + if (gsm_refer_pcs(cs->arfcn, s)) + cda->arfcn |= ARFCN_PCS; + LOGP(DRR, LOGL_INFO, " after: (chan_nr 0x%02x ARFCN %s TS %u " + "SS %u TSC %u)\n", ac->chan_desc.chan_nr, + gsm_print_arfcn(cda->arfcn), ch_ts, ch_subch, cda->tsc); + } + + /* starting time */ +#ifdef TEST_STARTING_TIMER + cda->start = 1; + cda->start_tm.fn = (ms->meas.last_fn + TEST_STARTING_TIMER) % 42432; + LOGP(DRR, LOGL_INFO, " TESTING: starting time ahead\n"); +#else + if (TLVP_PRESENT(&tp, GSM48_IE_START_TIME)) { + gsm48_decode_start_time(cda, (struct gsm48_start_time *) + TLVP_VAL(&tp, GSM48_IE_START_TIME)); + /* 9.1.2.5 "... before time IE is not present..." */ + if (!before_time) { + LOGP(DRR, LOGL_INFO, " -> channel description after " + "time only, but starting time\n"); + } else + LOGP(DRR, LOGL_INFO, " -> channel description before " + "and after time\n"); + } else { + /* 9.1.2.5 "... IEs unnecessary in this message." */ + if (before_time) { + before_time = 0; + LOGP(DRR, LOGL_INFO, " -> channel description before " + "time, but no starting time, ignoring!\n"); + } + } +#endif + + /* mobile allocation / frequency list after time */ + if (cda->h) { + if (TLVP_PRESENT(&tp, GSM48_IE_MA_AFTER)) { + const uint8_t *lv = + TLVP_VAL(&tp, GSM48_IE_MA_AFTER) - 1; + + LOGP(DRR, LOGL_INFO, " after: hopping required and " + "mobile allocation available\n"); + if (*lv + 1 > sizeof(cda->mob_alloc_lv)) { + LOGP(DRR, LOGL_ERROR, "Error: no LV space!\n"); + return -ENOMEM; + } + memcpy(cda->mob_alloc_lv, lv, *lv + 1); + } else + if (TLVP_PRESENT(&tp, GSM48_IE_FREQ_L_AFTER)) { + const uint8_t *lv = + TLVP_VAL(&tp, GSM48_IE_FREQ_L_AFTER) - 1; + + LOGP(DRR, LOGL_INFO, " after: hopping required and " + "frequency list available\n"); + if (*lv + 1 > sizeof(cda->freq_list_lv)) { + LOGP(DRR, LOGL_ERROR, "Error: no LV space!\n"); + return -ENOMEM; + } + memcpy(cda->freq_list_lv, lv, *lv + 1); + } else { + LOGP(DRR, LOGL_NOTICE, " after: hopping required, but " + "no mobile allocation / frequency list\n"); + } + } + + /* mobile allocation / frequency list before time */ + if (cdb->h) { + if (TLVP_PRESENT(&tp, GSM48_IE_MA_BEFORE)) { + const uint8_t *lv = + TLVP_VAL(&tp, GSM48_IE_MA_BEFORE) - 1; + + LOGP(DRR, LOGL_INFO, " before: hopping required and " + "mobile allocation available\n"); + if (*lv + 1 > sizeof(cdb->mob_alloc_lv)) { + LOGP(DRR, LOGL_ERROR, "Error: no LV space!\n"); + return -ENOMEM; + } + memcpy(cdb->mob_alloc_lv, lv, *lv + 1); + } else + if (TLVP_PRESENT(&tp, GSM48_IE_FREQ_L_BEFORE)) { + const uint8_t *lv = + TLVP_VAL(&tp, GSM48_IE_FREQ_L_BEFORE) - 1; + + LOGP(DRR, LOGL_INFO, " before: hopping required and " + "frequency list available\n"); + if (*lv + 1 > sizeof(cdb->freq_list_lv)) { + LOGP(DRR, LOGL_ERROR, "Error: no LV space!\n"); + return -ENOMEM; + } + memcpy(cdb->freq_list_lv, lv, *lv + 1); + } else + if (TLVP_PRESENT(&tp, GSM48_IE_F_CH_SEQ_BEFORE)) { + const uint8_t *v = + TLVP_VAL(&tp, GSM48_IE_F_CH_SEQ_BEFORE); + uint8_t len = TLVP_LEN(&tp, GSM48_IE_F_CH_SEQ_BEFORE); + + LOGP(DRR, LOGL_INFO, " before: hopping required and " + "frequency channel sequence available\n"); + if (len + 1 > sizeof(cdb->freq_seq_lv)) { + LOGP(DRR, LOGL_ERROR, "Error: no LV space!\n"); + return -ENOMEM; + } + cdb->freq_seq_lv[0] = len; + memcpy(cdb->freq_seq_lv + 1, v, len); + } else + if (cda->mob_alloc_lv[0]) { + LOGP(DRR, LOGL_INFO, " before: hopping required and " + "mobile allocation not available, using " + "mobile allocation after time\n"); + memcpy(cdb->mob_alloc_lv, cda->mob_alloc_lv, + sizeof(cdb->mob_alloc_lv)); + } else + if (cda->freq_list_lv[0]) { + LOGP(DRR, LOGL_INFO, " before: hopping required and " + "frequency list not available, using " + "frequency list after time\n"); + memcpy(cdb->freq_list_lv, cda->freq_list_lv, + sizeof(cdb->freq_list_lv)); + } else { + LOGP(DRR, LOGL_NOTICE, " before: hopping required, but " + "no mobile allocation / frequency list\n"); + } + } + + /* cell channel description */ + if (TLVP_PRESENT(&tp, GSM48_IE_CELL_CH_DESC)) { + const uint8_t *v = TLVP_VAL(&tp, GSM48_IE_CELL_CH_DESC); + uint8_t len = TLVP_LEN(&tp, GSM48_IE_CELL_CH_DESC); + + LOGP(DRR, LOGL_INFO, " both: using cell channel description " + "in case of mobile allocation\n"); + if (len + 1 > sizeof(cdb->cell_desc_lv)) { + LOGP(DRR, LOGL_ERROR, "Error: no LV space!\n"); + return -ENOMEM; + } + cdb->cell_desc_lv[0] = len; + memcpy(cdb->cell_desc_lv + 1, v, len); + cda->cell_desc_lv[0] = len; + memcpy(cda->cell_desc_lv + 1, v, len); + } else { + /* keep old */ + memcpy(cdb->cell_desc_lv, rr->cd_now.cell_desc_lv, + sizeof(cdb->cell_desc_lv)); + memcpy(cda->cell_desc_lv, rr->cd_now.cell_desc_lv, + sizeof(cda->cell_desc_lv)); + } + + /* channel mode */ + if (TLVP_PRESENT(&tp, GSM48_IE_CHANMODE_1)) { + cda->mode = cdb->mode = *TLVP_VAL(&tp, GSM48_IE_CHANMODE_1); + LOGP(DRR, LOGL_INFO, " both: changing channel mode 0x%02x\n", + cda->mode); + } else + cda->mode = cdb->mode = rr->cd_now.mode; + + /* cipher mode setting */ + if (TLVP_PRESENT(&tp, GSM48_IE_CIP_MODE_SET)) { + cda->cipher = cdb->cipher = + *TLVP_VAL(&tp, GSM48_IE_CIP_MODE_SET); + LOGP(DRR, LOGL_INFO, " both: changing cipher mode 0x%02x\n", + cda->cipher); + } else + cda->cipher = cdb->cipher = rr->cd_now.cipher; + + /* power command and TA (before and after time) */ + gsm48_decode_power_cmd_acc( + (struct gsm48_power_cmd *) &ac->power_command, + &cda->ind_tx_power, NULL); + cdb->ind_tx_power = cda->ind_tx_power; + cda->ind_ta = cdb->ind_ta = rr->cd_now.ind_ta; /* same cell */ + LOGP(DRR, LOGL_INFO, " both: (tx_power %d TA %d)\n", cda->ind_tx_power, + cda->ind_ta); + + /* check if we have to change channel at starting time */ + if (cda->start) { + int32_t now, start, diff; + + /* how much time do we have left? */ + now = ms->meas.last_fn % 42432; + start = cda->start_tm.fn % 42432; + diff = start - now; + if (diff < 0) + diff += 42432; + LOGP(DRR, LOGL_INFO, " after: (Tnow %d Tstart %d diff %d)\n", + now, start, diff); + start_mili = (uint32_t)diff * 19580 / 42432 * 10; + if (diff >= 32024 || !start_mili) { + LOGP(DRR, LOGL_INFO, " -> Start time already " + "elapsed\n"); + before_time = 0; + cda->start = 0; + } else { + LOGP(DRR, LOGL_INFO, " -> Start time is %d ms in the " + "future\n", start_mili); + } + } + + /* check if channels are valid */ + cause = gsm48_rr_check_mode(ms, cda->chan_nr, cda->mode); + if (cause) + return gsm48_rr_tx_ass_fail(ms, cause, RSL_MT_DATA_REQ); + if (before_time) { + cause = gsm48_rr_render_ma(ms, cdb, ma, &ma_len); + if (cause) + return gsm48_rr_tx_ass_fail(ms, cause, RSL_MT_DATA_REQ); + } + cause = gsm48_rr_render_ma(ms, cda, ma, &ma_len); + if (cause) + return gsm48_rr_tx_ass_fail(ms, cause, RSL_MT_DATA_REQ); + +#ifdef TEST_FREQUENCY_MOD + LOGP(DRR, LOGL_INFO, " TESTING: frequency modify ASS.CMD\n"); + before_time = 1; + memcpy(cdb, cda, sizeof(*cdb)); + cdb->h = 0; + cdb->arfcn = 0; +#endif + + /* schedule start of assignment */ + rr->modify_state = GSM48_RR_MOD_ASSIGN; + if (!before_time && cda->start) { + start_rr_t_starting(rr, start_mili / 1000, start_mili % 1000); + /* when timer fires, start time is already elapsed */ + cda->start = 0; + + return 0; + } + + /* if no starting time, start suspension of current link directly */ + LOGP(DRR, LOGL_INFO, "request suspension of data link\n"); + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gsm48_send_rsl(ms, RSL_MT_SUSP_REQ, nmsg, 0); + + /* release SAPI 3 link, if exits + * FIXME: suspend and resume afterward */ + gsm48_release_sapi3_link(ms); + + return 0; +} + +/* 9.1.16 sending HANDOVER COMPLETE */ +static int gsm48_rr_tx_hando_cpl(struct osmocom_ms *ms, uint8_t cause) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_ho_cpl *hc; + + LOGP(DRR, LOGL_INFO, "HANDOVER COMPLETE (cause #%d)\n", cause); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + hc = (struct gsm48_ho_cpl *) msgb_put(nmsg, sizeof(*hc)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_HANDO_COMPL; + + /* RR_CAUSE */ + hc->rr_cause = cause; + + // FIXME: mobile observed time + + /* set T200 of SAPI 0 */ + ms->lapdm_channel.lapdm_dcch.datalink[DL_SAPI0].dl.t200_sec = + T200_DCCH; + ms->lapdm_channel.lapdm_dcch.datalink[DL_SAPI0].dl.t200_usec = 0; + + return gsm48_send_rsl(ms, RSL_MT_RES_REQ, nmsg, 0); +} + +/* 9.1.4 sending HANDOVER FAILURE */ +static int gsm48_rr_tx_hando_fail(struct osmocom_ms *ms, uint8_t cause, + uint8_t rsl_prim) +{ + struct msgb *nmsg; + struct gsm48_hdr *gh; + struct gsm48_ho_fail *hf; + + LOGP(DRR, LOGL_INFO, "HANDOVER FAILURE (cause #%d)\n", cause); + + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gh = (struct gsm48_hdr *) msgb_put(nmsg, sizeof(*gh)); + hf = (struct gsm48_ho_fail *) msgb_put(nmsg, sizeof(*hf)); + + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_ASS_COMPL; + + /* RR_CAUSE */ + hf->rr_cause = cause; + + return gsm48_send_rsl(ms, rsl_prim, nmsg, 0); +} + +/* receiving HANDOVER COMMAND message (9.1.15) */ +static int gsm48_rr_rx_hando_cmd(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 gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_ho_cmd *ho = (struct gsm48_ho_cmd *)gh->data; + int payload_len = msgb_l3len(msg) - sizeof(*gh) - sizeof(*ho); + struct tlv_parsed tp; + struct gsm48_rr_cd *cda = &rr->cd_after; + struct gsm48_rr_cd *cdb = &rr->cd_before; + uint16_t arfcn; + uint8_t bcc, ncc; + uint8_t ch_type, ch_subch, ch_ts; + uint8_t before_time = 0; + uint16_t ma[64]; + uint8_t ma_len; + uint32_t start_mili = 0; + uint8_t cause; + struct msgb *nmsg; + + LOGP(DRR, LOGL_INFO, "HANDOVER COMMAND\n"); + + memset(cda, 0, sizeof(*cda)); + cda->ind_tx_power = rr->cd_now.ind_tx_power; + memset(cdb, 0, sizeof(*cdb)); + cdb->ind_tx_power = rr->cd_now.ind_tx_power; + + if (payload_len < 0) { + LOGP(DRR, LOGL_NOTICE, "Short read of HANDOVER COMMAND " + "message.\n"); + return gsm48_rr_tx_rr_status(ms, + GSM48_RR_CAUSE_PROT_ERROR_UNSPC); + } + + /* cell description */ + gsm48_decode_cell_desc(&ho->cell_desc, &arfcn, &ncc, &bcc); + + /* handover reference */ + rr->chan_req_val = ho->ho_ref; + rr->chan_req_mask = 0x00; + + tlv_parse(&tp, &gsm48_rr_att_tlvdef, ho->data, payload_len, 0, 0); + + /* sync ind */ + if (TLVP_PRESENT(&tp, GSM48_IE_SYNC_IND)) { + gsm48_decode_sync_ind(rr, (struct gsm48_sync_ind *) + TLVP_VAL(&tp, GSM48_IE_SYNC_IND)); + LOGP(DRR, LOGL_INFO, " (sync_ind=%d rot=%d nci=%d)\n", + rr->hando_sync_ind, rr->hando_rot, rr->hando_nci); + } + + /* decode channel description (before time) */ + if (TLVP_PRESENT(&tp, GSM48_IE_CH_DESC_1_BEFORE)) { + struct gsm48_chan_desc *ccd = (struct gsm48_chan_desc *) + TLVP_VAL(&tp, GSM48_IE_CH_DESC_1_BEFORE); + cdb->chan_nr = ccd->chan_nr; + rsl_dec_chan_nr(cdb->chan_nr, &ch_type, &ch_subch, &ch_ts); + if (ccd->h0.h) { + cdb->h = 1; + gsm48_decode_chan_h1(ccd, &cdb->tsc, &cdb->maio, + &cdb->hsn); + LOGP(DRR, LOGL_INFO, " before: (chan_nr 0x%02x MAIO %u " + "HSN %u TS %u SS %u TSC %u)\n", ccd->chan_nr, + cdb->maio, cdb->hsn, ch_ts, ch_subch, cdb->tsc); + } else { + cdb->h = 0; + gsm48_decode_chan_h0(ccd, &cdb->tsc, &cdb->arfcn); + if (gsm_refer_pcs(cs->arfcn, s)) + cdb->arfcn |= ARFCN_PCS; + LOGP(DRR, LOGL_INFO, " before: (chan_nr 0x%02x " + "ARFCN %s TS %u SS %u TSC %u)\n", ccd->chan_nr, + gsm_print_arfcn(cdb->arfcn), + ch_ts, ch_subch, cdb->tsc); + } + before_time = 1; + } + + /* decode channel description (after time) */ + cda->chan_nr = ho->chan_desc.chan_nr; + rsl_dec_chan_nr(cda->chan_nr, &ch_type, &ch_subch, &ch_ts); + if (ho->chan_desc.h0.h) { + cda->h = 1; + gsm48_decode_chan_h1(&ho->chan_desc, &cda->tsc, &cda->maio, + &cda->hsn); + LOGP(DRR, LOGL_INFO, " after: (chan_nr 0x%02x MAIO %u HSN %u " + "TS %u SS %u TSC %u)\n", ho->chan_desc.chan_nr, + cda->maio, cda->hsn, ch_ts, ch_subch, cda->tsc); + } else { + cda->h = 0; + gsm48_decode_chan_h0(&ho->chan_desc, &cda->tsc, &cda->arfcn); + if (gsm_refer_pcs(cs->arfcn, s)) + cda->arfcn |= ARFCN_PCS; + LOGP(DRR, LOGL_INFO, " after: (chan_nr 0x%02x ARFCN %s TS %u " + "SS %u TSC %u)\n", ho->chan_desc.chan_nr, + gsm_print_arfcn(cda->arfcn), ch_ts, ch_subch, cda->tsc); + } + + /* starting time */ +#ifdef TEST_STARTING_TIMER + cda->start = 1; + cda->start_tm.fn = (ms->meas.last_fn + TEST_STARTING_TIMER) % 42432; + LOGP(DRR, LOGL_INFO, " TESTING: starting time ahead\n"); +#else + if (TLVP_PRESENT(&tp, GSM48_IE_START_TIME)) { + gsm48_decode_start_time(cda, (struct gsm48_start_time *) + TLVP_VAL(&tp, GSM48_IE_START_TIME)); + /* 9.1.2.5 "... before time IE is not present..." */ + if (!before_time) { + LOGP(DRR, LOGL_INFO, " -> channel description after " + "time only, but starting time\n"); + } else + LOGP(DRR, LOGL_INFO, " -> channel description before " + "and after time\n"); + } else { + /* 9.1.2.5 "... IEs unnecessary in this message." */ + if (before_time) { + before_time = 0; + LOGP(DRR, LOGL_INFO, " -> channel description before " + "time, but no starting time, ignoring!\n"); + } + } +#endif + + /* mobile allocation / frequency list after time */ + if (cda->h) { + if (TLVP_PRESENT(&tp, GSM48_IE_MA_AFTER)) { + const uint8_t *lv = + TLVP_VAL(&tp, GSM48_IE_MA_AFTER) - 1; + + LOGP(DRR, LOGL_INFO, " after: hopping required and " + "mobile allocation available\n"); + if (*lv + 1 > sizeof(cda->mob_alloc_lv)) { + LOGP(DRR, LOGL_ERROR, "Error: no LV space!\n"); + return -ENOMEM; + } + memcpy(cda->mob_alloc_lv, lv, *lv + 1); + } else + if (TLVP_PRESENT(&tp, GSM48_IE_FREQ_L_AFTER)) { + const uint8_t *lv = + TLVP_VAL(&tp, GSM48_IE_FREQ_L_AFTER) - 1; + + LOGP(DRR, LOGL_INFO, " after: hopping required and " + "frequency list available\n"); + if (*lv + 1 > sizeof(cda->freq_list_lv)) { + LOGP(DRR, LOGL_ERROR, "Error: no LV space!\n"); + return -ENOMEM; + } + memcpy(cda->freq_list_lv, lv, *lv + 1); + } else { + LOGP(DRR, LOGL_NOTICE, " after: hopping required, but " + "no mobile allocation / frequency list\n"); + } + } + + /* mobile allocation / frequency list before time */ + if (cdb->h) { + if (TLVP_PRESENT(&tp, GSM48_IE_MA_BEFORE)) { + const uint8_t *lv = + TLVP_VAL(&tp, GSM48_IE_MA_BEFORE) - 1; + + LOGP(DRR, LOGL_INFO, " before: hopping required and " + "mobile allocation available\n"); + if (*lv + 1 > sizeof(cdb->mob_alloc_lv)) { + LOGP(DRR, LOGL_ERROR, "Error: no LV space!\n"); + return -ENOMEM; + } + memcpy(cdb->mob_alloc_lv, lv, *lv + 1); + } else + if (TLVP_PRESENT(&tp, GSM48_IE_FREQ_L_BEFORE)) { + const uint8_t *lv = + TLVP_VAL(&tp, GSM48_IE_FREQ_L_BEFORE) - 1; + + LOGP(DRR, LOGL_INFO, " before: hopping required and " + "frequency list available\n"); + if (*lv + 1 > sizeof(cdb->freq_list_lv)) { + LOGP(DRR, LOGL_ERROR, "Error: no LV space!\n"); + return -ENOMEM; + } + memcpy(cdb->freq_list_lv, lv, *lv + 1); + } else + if (TLVP_PRESENT(&tp, GSM48_IE_F_CH_SEQ_BEFORE)) { + const uint8_t *v = + TLVP_VAL(&tp, GSM48_IE_F_CH_SEQ_BEFORE); + uint8_t len = TLVP_LEN(&tp, GSM48_IE_F_CH_SEQ_BEFORE); + + LOGP(DRR, LOGL_INFO, " before: hopping required and " + "frequency channel sequence available\n"); + if (len + 1 > sizeof(cdb->freq_seq_lv)) { + LOGP(DRR, LOGL_ERROR, "Error: no LV space!\n"); + return -ENOMEM; + } + cdb->freq_seq_lv[0] = len; + memcpy(cdb->freq_seq_lv, v + 1, *v); + } else + if (cda->mob_alloc_lv[0]) { + LOGP(DRR, LOGL_INFO, " before: hopping required and " + "mobile allocation not available, using " + "mobile allocation after time\n"); + memcpy(cdb->mob_alloc_lv, cda->mob_alloc_lv, + sizeof(cdb->mob_alloc_lv)); + } else + if (cda->freq_list_lv[0]) { + LOGP(DRR, LOGL_INFO, " before: hopping required and " + "frequency list not available, using " + "frequency list after time\n"); + memcpy(cdb->freq_list_lv, cda->freq_list_lv, + sizeof(cdb->freq_list_lv)); + } else { + LOGP(DRR, LOGL_NOTICE, " before: hopping required, but " + "no mobile allocation / frequency list\n"); + } + } + + /* cell channel description */ + if (TLVP_PRESENT(&tp, GSM48_IE_CELL_CH_DESC)) { + const uint8_t *v = TLVP_VAL(&tp, GSM48_IE_CELL_CH_DESC); + uint8_t len = TLVP_LEN(&tp, GSM48_IE_CELL_CH_DESC); + + LOGP(DRR, LOGL_INFO, " both: using cell channel description " + "in case of mobile allocation\n"); + if (len + 1 > sizeof(cdb->cell_desc_lv)) { + LOGP(DRR, LOGL_ERROR, "Error: no LV space!\n"); + return -ENOMEM; + } + cdb->cell_desc_lv[0] = len; + memcpy(cdb->cell_desc_lv + 1, v, len); + cda->cell_desc_lv[0] = len; + memcpy(cda->cell_desc_lv + 1, v, len); + } else { + /* keep old */ + memcpy(cdb->cell_desc_lv, rr->cd_now.cell_desc_lv, + sizeof(cdb->cell_desc_lv)); + memcpy(cda->cell_desc_lv, rr->cd_now.cell_desc_lv, + sizeof(cda->cell_desc_lv)); + } + + /* channel mode */ + if (TLVP_PRESENT(&tp, GSM48_IE_CHANMODE_1)) { + cda->mode = cdb->mode = *TLVP_VAL(&tp, GSM48_IE_CHANMODE_1); + LOGP(DRR, LOGL_INFO, " both: changing channel mode 0x%02x\n", + cda->mode); + } else + cda->mode = cdb->mode = rr->cd_now.mode; + + /* cipher mode setting */ + if (TLVP_PRESENT(&tp, GSM48_IE_CIP_MODE_SET)) { + cda->cipher = cdb->cipher = + *TLVP_VAL(&tp, GSM48_IE_CIP_MODE_SET); + LOGP(DRR, LOGL_INFO, " both: changing cipher mode 0x%02x\n", + cda->cipher); + } else + cda->cipher = cdb->cipher = rr->cd_now.cipher; + + /* power command and TA (before and after time) */ + gsm48_decode_power_cmd_acc( + (struct gsm48_power_cmd *) &ho->power_command, + &cda->ind_tx_power, &rr->hando_act); + cdb->ind_tx_power = cda->ind_tx_power; + cda->ind_ta = cdb->ind_ta = rr->cd_now.ind_ta; /* same cell */ + LOGP(DRR, LOGL_INFO, " both: (tx_power %d TA %d access=%s)\n", + cda->ind_tx_power, cda->ind_ta, + (rr->hando_act) ? "optional" : "mandatory"); + + /* check if we have to change channel at starting time */ + if (cda->start) { + int32_t now, start, diff; + + /* how much time do we have left? */ + now = ms->meas.last_fn % 42432; + start = cda->start_tm.fn % 42432; + diff = start - now; + if (diff < 0) + diff += 42432; + LOGP(DRR, LOGL_INFO, " after: (Tnow %d Tstart %d diff %d)\n", + now, start, diff); + start_mili = (uint32_t)diff * 19580 / 42432 * 10; + if (diff >= 32024 || !start_mili) { + LOGP(DRR, LOGL_INFO, " -> Start time already " + "elapsed\n"); + before_time = 0; + cda->start = 0; + } else { + LOGP(DRR, LOGL_INFO, " -> Start time is %d ms in the " + "future\n", start_mili); + } + } + + /* check if channels are valid */ + if (before_time) { + cause = gsm48_rr_render_ma(ms, cdb, ma, &ma_len); + if (cause) + return gsm48_rr_tx_hando_fail(ms, cause, RSL_MT_DATA_REQ); + } + cause = gsm48_rr_render_ma(ms, cda, ma, &ma_len); + if (cause) + return gsm48_rr_tx_hando_fail(ms, cause, RSL_MT_DATA_REQ); + + +#if 0 + if (not supported) { + LOGP(DRR, LOGL_NOTICE, "New channel is not supported.\n"); + return GSM48_RR_CAUSE_CHAN_MODE_UNACCEPT; + } +#endif + +#ifdef TEST_FREQUENCY_MOD + LOGP(DRR, LOGL_INFO, " TESTING: frequency modify HANDO.CMD\n"); + before_time = 1; + memcpy(cdb, cda, sizeof(*cdb)); + cdb->h = 0; + cdb->arfcn = 0; +#endif + + /* schedule start of handover */ + rr->modify_state = GSM48_RR_MOD_HANDO; + if (!before_time && cda->start) { + start_rr_t_starting(rr, start_mili / 1000, start_mili % 1000); + /* when timer fires, start time is already elapsed */ + cda->start = 0; + + return 0; + } + + /* if no starting time, start suspension of current link directly */ + LOGP(DRR, LOGL_INFO, "request suspension of data link\n"); + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + gsm48_send_rsl(ms, RSL_MT_SUSP_REQ, nmsg, 0); + + /* release SAPI 3 link, if exits + * FIXME: suspend and resume afterward */ + gsm48_release_sapi3_link(ms); + + return 0; +} + +/* send all queued messages down to layer 2 */ +static int gsm48_rr_dequeue_down(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *msg; + + while((msg = msgb_dequeue(&rr->downqueue))) { + struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *) msg->data; + uint8_t sapi = rrh->sapi; + + LOGP(DRR, LOGL_INFO, "Sending queued message.\n"); + if (sapi && rr->sapi3_state != GSM48_RR_SAPI3ST_ESTAB) { + LOGP(DRR, LOGL_INFO, "Dropping SAPI 3 msg, no link!\n"); + msgb_free(msg); + return 0; + } + gsm48_send_rsl(ms, RSL_MT_DATA_REQ, msg, 0); + } + + return 0; +} + +/* channel is resumed in dedicated mode */ +static int gsm48_rr_estab_cnf_dedicated(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + LOGP(DRR, LOGL_INFO, "data link is resumed\n"); + + /* transmit queued frames during ho / ass transition */ + gsm48_rr_dequeue_down(ms); + + rr->modify_state = GSM48_RR_MOD_NONE; + + return 0; +} + +/* suspend confirm in dedicated mode */ +static int gsm48_rr_susp_cnf_dedicated(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + if (rr->modify_state) { + uint16_t ma[64]; + uint8_t ma_len; + + /* deactivating dedicated mode */ + LOGP(DRR, LOGL_INFO, "suspension coplete, leaving dedicated " + "mode\n"); + l1ctl_tx_dm_rel_req(ms); + ms->meas.rl_fail = 0; + rr->dm_est = 0; + l1ctl_tx_reset_req(ms, L1CTL_RES_T_SCHED); + + /* store current channel descriptions */ + memcpy(&rr->cd_last, &rr->cd_now, sizeof(rr->cd_last)); + + /* copy channel description "after time" */ + memcpy(&rr->cd_now, &rr->cd_after, sizeof(rr->cd_now)); + + if (rr->cd_after.start) { + /* render channel "before time" */ + gsm48_rr_render_ma(ms, &rr->cd_before, ma, &ma_len); + + /* activate channel */ + gsm48_rr_activate_channel(ms, &rr->cd_before, ma, + ma_len); + + /* render channel "after time" */ + gsm48_rr_render_ma(ms, &rr->cd_now, ma, &ma_len); + + /* schedule change of channel */ + gsm48_rr_channel_after_time(ms, &rr->cd_now, ma, ma_len, + rr->cd_now.start_tm.fn); + } else { + /* render channel "after time" */ + gsm48_rr_render_ma(ms, &rr->cd_now, ma, &ma_len); + + /* activate channel */ + gsm48_rr_activate_channel(ms, &rr->cd_now, ma, ma_len); + } + + /* send DL-RESUME REQUEST */ + LOGP(DRR, LOGL_INFO, "request resume of data link\n"); + switch (rr->modify_state) { + case GSM48_RR_MOD_ASSIGN: + gsm48_rr_tx_ass_cpl(ms, GSM48_RR_CAUSE_NORMAL); + break; + case GSM48_RR_MOD_HANDO: + gsm48_rr_tx_hando_cpl(ms, GSM48_RR_CAUSE_NORMAL); + break; + } + +#ifdef TODO + /* trigger RACH */ + if (rr->modify_state == GSM48_RR_MOD_HANDO) { + gsm48_rr_tx_hando_access(ms); + rr->hando_acc_left = 3; + } +#endif + } + return 0; +} + +/* + * radio ressource requests + */ + +/* establish request for dedicated mode */ +static int gsm48_rr_est_req(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm48_sysinfo *s = &cs->sel_si; + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *) msg->data; + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t cause; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + uint16_t acc_class; + + /* 3.3.1.1.3.2 */ + if (osmo_timer_pending(&rr->t3122)) { + if (rrh->cause != RR_EST_CAUSE_EMERGENCY) { + LOGP(DRR, LOGL_INFO, "T3122 running, rejecting!\n"); + cause = RR_REL_CAUSE_T3122; + reject: + LOGP(DSUM, LOGL_INFO, "Establishing radio link not " + "possible\n"); + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = cause; + return gsm48_rr_upmsg(ms, nmsg); + } + LOGP(DRR, LOGL_INFO, "T3122 running, but emergency call\n"); + stop_rr_t3122(rr); + } + + /* if state is not idle */ + if (rr->state != GSM48_RR_ST_IDLE) { + LOGP(DRR, LOGL_INFO, "We are not IDLE yet, rejecting!\n"); + cause = RR_REL_CAUSE_TRY_LATER; + goto reject; + } + + /* cell selected */ + if (!cs->selected) { + LOGP(DRR, LOGL_INFO, "No cell selected, rejecting!\n"); + cause = RR_REL_CAUSE_TRY_LATER; + goto reject; + } + + /* check if camping */ + if (cs->state != GSM322_C3_CAMPED_NORMALLY + && rrh->cause != RR_EST_CAUSE_EMERGENCY) { + LOGP(DRR, LOGL_INFO, "Not camping normally, rejecting! " + "(cs->state = %d)\n", cs->state); + cause = RR_REL_CAUSE_EMERGENCY_ONLY; + goto reject; + } + if (cs->state != GSM322_C3_CAMPED_NORMALLY + && cs->state != GSM322_C7_CAMPED_ANY_CELL) { + LOGP(DRR, LOGL_INFO, "Not camping, rejecting! " + "(cs->state = %d)\n", cs->state); + cause = RR_REL_CAUSE_TRY_LATER; + goto reject; + } + + /* check for relevant informations */ + if (!s->si3) { + LOGP(DRR, LOGL_INFO, "Not enough SI, rejecting!\n"); + cause = RR_REL_CAUSE_TRY_LATER; + goto reject; + } + + /* 3.3.1.1.1 */ + if (!subscr->acc_barr && s->cell_barr) { + LOGP(DRR, LOGL_INFO, "Cell barred, rejecting!\n"); + cause = RR_REL_CAUSE_NOT_AUTHORIZED; + goto reject; + } + if (rrh->cause == RR_EST_CAUSE_EMERGENCY) + acc_class = subscr->acc_class | 0x0400; + else + acc_class = subscr->acc_class & 0xfbff; + if (!subscr->acc_barr && !(acc_class & (s->class_barr ^ 0xffff))) { + LOGP(DRR, LOGL_INFO, "Cell barred for our access class (access " + "%04x barred %04x)!\n", acc_class, s->class_barr); + cause = RR_REL_CAUSE_NOT_AUTHORIZED; + goto reject; + } + + /* requested by RR */ + rr->rr_est_req = 1; + + /* clone and store REQUEST message */ + if (!gh) { + LOGP(DRR, LOGL_ERROR, "Error, missing l3 message\n"); + return -EINVAL; + } + rr->rr_est_msg = gsm48_l3_msgb_alloc(); + if (!rr->rr_est_msg) + return -ENOMEM; + memcpy(msgb_put(rr->rr_est_msg, msgb_l3len(msg)), + msgb_l3(msg), msgb_l3len(msg)); + + /* request channel */ + return gsm48_rr_chan_req(ms, rrh->cause, 0, 0); +} + +/* 3.4.2 transfer data in dedicated mode */ +static int gsm48_rr_data_req(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *) msg->data; + uint8_t sapi = rrh->sapi; + + if (rr->state != GSM48_RR_ST_DEDICATED) { + msgb_free(msg); + return -EINVAL; + } + + /* pull RR header */ + msgb_pull(msg, sizeof(struct gsm48_rr_hdr)); + + /* set sequence number and increment */ + gsm48_apply_v_sd(rr, msg); + + /* queue message, during handover or assignment procedure */ + if (rr->modify_state == GSM48_RR_MOD_ASSIGN + || rr->modify_state == GSM48_RR_MOD_HANDO) { + LOGP(DRR, LOGL_INFO, "Queueing message during suspend.\n"); + msgb_enqueue(&rr->downqueue, msg); + return 0; + } + + if (sapi && rr->sapi3_state != GSM48_RR_SAPI3ST_ESTAB) { + LOGP(DRR, LOGL_INFO, "Dropping SAPI 3 msg, no link!\n"); + msgb_free(msg); + return 0; + } + + /* forward message */ + return gsm48_send_rsl(ms, RSL_MT_DATA_REQ, msg, + sapi ? rr->sapi3_link_id : 0); +} + +/* + * data indications from data link + */ + +/* 3.4.2 data from layer 2 to RR and upper layer*/ +static int gsm48_rr_data_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_rr_hdr *rrh; + uint8_t pdisc = gh->proto_discr & 0x0f; + + if (pdisc == GSM48_PDISC_RR) { + int rc = -EINVAL; + uint8_t skip_ind = (gh->proto_discr & 0xf0) >> 4; + + /* ignore if skip indicator is not B'0000' */ + if (skip_ind) + return 0; + + switch(gh->msg_type) { + case GSM48_MT_RR_ADD_ASS: + rc = gsm48_rr_rx_add_ass(ms, msg); + break; + case GSM48_MT_RR_ASS_CMD: + rc = gsm48_rr_rx_ass_cmd(ms, msg); + break; + case GSM48_MT_RR_CIPH_M_CMD: + rc = gsm48_rr_rx_cip_mode_cmd(ms, msg); + break; + case GSM48_MT_RR_CLSM_ENQ: + rc = gsm48_rr_rx_cm_enq(ms, msg); + break; + case GSM48_MT_RR_CHAN_MODE_MODIF: + rc = gsm48_rr_rx_chan_modify(ms, msg); + break; + case GSM48_MT_RR_HANDO_CMD: + rc = gsm48_rr_rx_hando_cmd(ms, msg); + break; + case GSM48_MT_RR_FREQ_REDEF: + rc = gsm48_rr_rx_frq_redef(ms, msg); + break; + case GSM48_MT_RR_CHAN_REL: + rc = gsm48_rr_rx_chan_rel(ms, msg); + break; + case GSM48_MT_RR_APP_INFO: + LOGP(DRR, LOGL_NOTICE, "APP INFO not supported!\n"); + break; + default: + LOGP(DRR, LOGL_NOTICE, "Message type 0x%02x unknown.\n", + gh->msg_type); + + /* status message */ + gsm48_rr_tx_rr_status(ms, GSM48_RR_CAUSE_MSG_TYPE_N); + } + + msgb_free(msg); + return rc; + } + + /* pull off RSL header up to L3 message */ + msgb_pull(msg, (long)msgb_l3(msg) - (long)msg->data); + + /* push RR header */ + msgb_push(msg, sizeof(struct gsm48_rr_hdr)); + rrh = (struct gsm48_rr_hdr *)msg->data; + rrh->msg_type = GSM48_RR_DATA_IND; + + return gsm48_rr_upmsg(ms, msg); +} + +/* receive BCCH at RR layer */ +static int gsm48_rr_rx_bcch(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_system_information_type_header *sih = msgb_l3(msg); + + switch (sih->system_information) { + case GSM48_MT_RR_SYSINFO_1: + return gsm48_rr_rx_sysinfo1(ms, msg); + case GSM48_MT_RR_SYSINFO_2: + return gsm48_rr_rx_sysinfo2(ms, msg); + case GSM48_MT_RR_SYSINFO_2bis: + return gsm48_rr_rx_sysinfo2bis(ms, msg); + case GSM48_MT_RR_SYSINFO_2ter: + return gsm48_rr_rx_sysinfo2ter(ms, msg); + case GSM48_MT_RR_SYSINFO_3: + return gsm48_rr_rx_sysinfo3(ms, msg); + case GSM48_MT_RR_SYSINFO_4: + return gsm48_rr_rx_sysinfo4(ms, msg); + default: +#if 0 + LOGP(DRR, LOGL_NOTICE, "BCCH message type 0x%02x not sup.\n", + sih->system_information); +#endif + return -EINVAL; + } +} + +/* receive CCCH at RR layer */ +static int gsm48_rr_rx_pch_agch(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_system_information_type_header *sih = msgb_l3(msg); + + switch (sih->system_information) { + case GSM48_MT_RR_PAG_REQ_1: + return gsm48_rr_rx_pag_req_1(ms, msg); + case GSM48_MT_RR_PAG_REQ_2: + return gsm48_rr_rx_pag_req_2(ms, msg); + case GSM48_MT_RR_PAG_REQ_3: + return gsm48_rr_rx_pag_req_3(ms, msg); + + case GSM48_MT_RR_IMM_ASS: + return gsm48_rr_rx_imm_ass(ms, msg); + case GSM48_MT_RR_IMM_ASS_EXT: + return gsm48_rr_rx_imm_ass_ext(ms, msg); + case GSM48_MT_RR_IMM_ASS_REJ: + return gsm48_rr_rx_imm_ass_rej(ms, msg); + default: +#if 0 + LOGP(DRR, LOGL_NOTICE, "CCCH message type 0x%02x unknown.\n", + sih->system_information); +#endif + return -EINVAL; + } +} + +/* receive ACCH at RR layer */ +static int gsm48_rr_rx_acch(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm_settings *set = &ms->settings; + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + struct gsm48_system_information_type_header *sih = msgb_l3(msg); + uint8_t ind_ta, ind_tx_power; + + if (msgb_l2len(msg) < sizeof(*rllh) + 2 + 2) { + LOGP(DRR, LOGL_ERROR, "Missing TA and TX_POWER IEs\n"); + return -EINVAL; + } + + ind_ta = rllh->data[1]; + ind_tx_power = rllh->data[3]; + LOGP(DRR, LOGL_INFO, "Indicated ta %d (actual ta %d)\n", + ind_ta, ind_ta - set->alter_delay); + LOGP(DRR, LOGL_INFO, "Indicated tx_power %d\n", + ind_tx_power); + if (ind_ta != rr->cd_now.ind_ta + || ind_tx_power != rr->cd_now.ind_tx_power) { + LOGP(DRR, LOGL_INFO, "setting new ta and tx_power\n"); + l1ctl_tx_param_req(ms, ind_ta - set->alter_delay, + (set->alter_tx_power) ? set->alter_tx_power_value + : ind_tx_power); + rr->cd_now.ind_ta = ind_ta; + rr->cd_now.ind_tx_power = ind_tx_power; + } + + switch (sih->system_information) { + case GSM48_MT_RR_SYSINFO_5: + return gsm48_rr_rx_sysinfo5(ms, msg); + case GSM48_MT_RR_SYSINFO_5bis: + return gsm48_rr_rx_sysinfo5bis(ms, msg); + case GSM48_MT_RR_SYSINFO_5ter: + return gsm48_rr_rx_sysinfo5ter(ms, msg); + case GSM48_MT_RR_SYSINFO_6: + return gsm48_rr_rx_sysinfo6(ms, msg); + default: + LOGP(DRR, LOGL_NOTICE, "ACCH message type 0x%02x unknown.\n", + sih->system_information); + return -EINVAL; + } +} + +/* unit data from layer 2 to RR layer */ +static int gsm48_rr_unit_data_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + struct tlv_parsed tv; + uint8_t ch_type, ch_subch, ch_ts; + + DEBUGP(DRSL, "RSLms UNIT DATA IND chan_nr=0x%02x link_id=0x%02x\n", + rllh->chan_nr, rllh->link_id); + + rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg)-sizeof(*rllh)); + if (!TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) { + DEBUGP(DRSL, "UNIT_DATA_IND without L3 INFO ?!?\n"); + return -EIO; + } + msg->l3h = (uint8_t *) TLVP_VAL(&tv, RSL_IE_L3_INFO); + + if (cs->ccch_state != GSM322_CCCH_ST_SYNC + && cs->ccch_state != GSM322_CCCH_ST_DATA) + return -EINVAL; + + /* temporary moved here until confirm is fixed */ + if (cs->ccch_state != GSM322_CCCH_ST_DATA) { + LOGP(DCS, LOGL_INFO, "Channel provides data.\n"); + cs->ccch_state = GSM322_CCCH_ST_DATA; + + /* in dedicated mode */ + if (ms->rrlayer.state == GSM48_RR_ST_CONN_PEND) + return gsm48_rr_tx_rand_acc(ms, NULL); + + /* 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_PLMN_SEARCH + || cs->state == GSM322_HPLMN_SEARCH) + start_cs_timer(cs, ms->support.scan_to, 0); + // TODO: timer depends on BCCH config + } + + rsl_dec_chan_nr(rllh->chan_nr, &ch_type, &ch_subch, &ch_ts); + switch (ch_type) { + case RSL_CHAN_PCH_AGCH: + return gsm48_rr_rx_pch_agch(ms, msg); + case RSL_CHAN_BCCH: + return gsm48_rr_rx_bcch(ms, msg); + case RSL_CHAN_Bm_ACCHs: + case RSL_CHAN_Lm_ACCHs: + case RSL_CHAN_SDCCH4_ACCH: + case RSL_CHAN_SDCCH8_ACCH: + return gsm48_rr_rx_acch(ms, msg); + default: + LOGP(DRSL, LOGL_NOTICE, "RSL with chan_nr 0x%02x unknown.\n", + rllh->chan_nr); + return -EINVAL; + } +} + +/* 3.4.13.3 RR abort in dedicated mode (also in conn. pending mode) */ +static int gsm48_rr_abort_req(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + uint8_t *mode; + + /* stop pending RACH timer */ + stop_rr_t3126(rr); + + /* release "normally" if we are in dedicated mode */ + if (rr->state == GSM48_RR_ST_DEDICATED) { + struct msgb *nmsg; + + LOGP(DRR, LOGL_INFO, "Abort in dedicated state, send release " + "to layer 2.\n"); + + new_rr_state(rr, GSM48_RR_ST_REL_PEND); + + /* release message */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + mode = msgb_put(nmsg, 2); + mode[0] = RSL_IE_RELEASE_MODE; + mode[1] = 0; /* normal release */ + gsm48_send_rsl_nol3(ms, RSL_MT_REL_REQ, nmsg, 0); + + /* release SAPI 3 link, if exits */ + gsm48_release_sapi3_link(ms); + return 0; + } + + LOGP(DRR, LOGL_INFO, "Abort in connection pending state, return to " + "idle state.\n"); + /* return idle */ + new_rr_state(rr, GSM48_RR_ST_IDLE); + + return 0; +} + +/* release confirm */ +static int gsm48_rr_rel_cnf(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + uint8_t cause = RR_REL_CAUSE_NORMAL; + uint16_t ma[64]; + uint8_t ma_len; + + /* switch back to old channel, if modify/ho failed */ + switch (rr->modify_state) { + case GSM48_RR_MOD_ASSIGN: + case GSM48_RR_MOD_HANDO: + /* deactivate channel */ + l1ctl_tx_dm_rel_req(ms); + ms->meas.rl_fail = 0; + rr->dm_est = 0; + l1ctl_tx_reset_req(ms, L1CTL_RES_T_SCHED); + + /* get old channel description */ + memcpy(&rr->cd_now, &rr->cd_last, sizeof(rr->cd_now)); + + /* render and change radio to old channel */ + gsm48_rr_render_ma(ms, &rr->cd_now, ma, &ma_len); + gsm48_rr_activate_channel(ms, &rr->cd_now, ma, ma_len); + + /* re-establish old link */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + if (rr->modify_state == GSM48_RR_MOD_ASSIGN) { + rr->modify_state = GSM48_RR_MOD_ASSIGN_RESUME; + return gsm48_rr_tx_ass_fail(ms, + GSM48_RR_CAUSE_ABNORMAL_UNSPEC, + RSL_MT_RECON_REQ); + } else { + rr->modify_state = GSM48_RR_MOD_HANDO_RESUME; + return gsm48_rr_tx_hando_fail(ms, + GSM48_RR_CAUSE_ABNORMAL_UNSPEC, + RSL_MT_RECON_REQ); + } + /* returns above */ + case GSM48_RR_MOD_ASSIGN_RESUME: + case GSM48_RR_MOD_HANDO_RESUME: + rr->modify_state = GSM48_RR_MOD_NONE; + cause = RR_REL_CAUSE_LINK_FAILURE; + break; + } + + LOGP(DSUM, LOGL_INFO, "Requested channel aborted\n"); + + /* stop T3211 if running */ + stop_rr_t3110(rr); + + /* send release indication */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = cause; + gsm48_rr_upmsg(ms, nmsg); + + /* return idle */ + new_rr_state(rr, GSM48_RR_ST_IDLE); + return 0; +} + +/* MDL-ERROR */ +static int gsm48_rr_mdl_error_ind(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + uint8_t *mode; + uint8_t cause = rllh->data[2]; + uint8_t link_id = rllh->link_id; + + switch (cause) { + case RLL_CAUSE_SEQ_ERR: + case RLL_CAUSE_UNSOL_DM_RESP_MF: + break; + default: + LOGP(DRR, LOGL_NOTICE, "MDL-Error (cause %d) ignoring\n", + cause); + } + + LOGP(DRR, LOGL_NOTICE, "MDL-Error (cause %d) aborting\n", cause); + + /* disconnect the (main) signalling link */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + mode = msgb_put(nmsg, 2); + mode[0] = RSL_IE_RELEASE_MODE; + mode[1] = 1; /* local release */ + gsm48_send_rsl_nol3(ms, RSL_MT_REL_REQ, nmsg, link_id); + + /* in case of modify/hando: wait for confirm */ + if (rr->modify_state) + return 0; + + /* send abort ind to upper layer */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_ABORT_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = RR_REL_CAUSE_LINK_FAILURE; + nrrh->sapi = link_id & 7; + gsm48_rr_upmsg(ms, nmsg); + + /* only for main signalling link */ + if ((link_id & 7) == 0) { + /* return idle */ + new_rr_state(rr, GSM48_RR_ST_IDLE); + /* release SAPI 3 link, if exits */ + gsm48_release_sapi3_link(ms); + } else { + new_sapi3_state(rr, GSM48_RR_SAPI3ST_IDLE); + LOGP(DSUM, LOGL_INFO, "Radio link SAPI3 failed\n"); + } + return 0; +} + +static int gsm48_rr_estab_ind_sapi3(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + uint8_t link_id = rllh->link_id; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + + if (rr->state != GSM48_RR_ST_DEDICATED) { + /* disconnect sapi 3 link */ + gsm48_release_sapi3_link(ms); + return -EINVAL; + } + + new_sapi3_state(rr, GSM48_RR_SAPI3ST_ESTAB); + rr->sapi3_link_id = link_id; /* set link ID */ + + LOGP(DSUM, LOGL_INFO, "Radio link SAPI3 is established\n"); + + if ((link_id & 0xf8) == 0x00) { + /* raise T200 of SAPI 0 */ + ms->lapdm_channel.lapdm_dcch.datalink[DL_SAPI0].dl.t200_sec = + T200_DCCH_SHARED; + ms->lapdm_channel.lapdm_dcch.datalink[DL_SAPI0].dl.t200_usec= 0; + } + + /* send inication to upper layer */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_EST_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->sapi = link_id & 7; + + return gsm48_rr_upmsg(ms, nmsg); +} + +static int gsm48_rr_estab_cnf_sapi3(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + uint8_t link_id = rllh->link_id; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + + if (rr->state != GSM48_RR_ST_DEDICATED) { + gsm48_release_sapi3_link(ms); + return -EINVAL; + } + + new_sapi3_state(rr, GSM48_RR_SAPI3ST_ESTAB); + rr->sapi3_link_id = link_id; /* set link ID, just to be sure */ + + LOGP(DSUM, LOGL_INFO, "Radio link SAPI3 is established\n"); + + /* send inication to upper layer */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_EST_CNF); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->sapi = link_id & 7; + + return gsm48_rr_upmsg(ms, nmsg); +} + +/* 3.4.2 data from layer 2 to RR and upper layer (sapi 3)*/ +static int gsm48_rr_data_ind_sapi3(struct osmocom_ms *ms, struct msgb *msg) +{ + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + uint8_t sapi = rllh->link_id & 7; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_rr_hdr *rrh; + uint8_t pdisc = gh->proto_discr & 0x0f; + + if (pdisc == GSM48_PDISC_RR) { + msgb_free(msg); + return -EINVAL; + } + + /* pull off RSL header up to L3 message */ + msgb_pull(msg, (long)msgb_l3(msg) - (long)msg->data); + + /* push RR header */ + msgb_push(msg, sizeof(struct gsm48_rr_hdr)); + rrh = (struct gsm48_rr_hdr *)msg->data; + rrh->msg_type = GSM48_RR_DATA_IND; + rrh->sapi = sapi; + + return gsm48_rr_upmsg(ms, msg); +} + +static int gsm48_rr_rel_ind_sapi3(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + uint8_t link_id = rllh->link_id; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + + new_sapi3_state(rr, GSM48_RR_SAPI3ST_IDLE); + + LOGP(DSUM, LOGL_INFO, "Radio link SAPI3 is released\n"); + + /* lower T200 of SAPI 0 */ + ms->lapdm_channel.lapdm_dcch.datalink[DL_SAPI0].dl.t200_sec = + T200_DCCH; + ms->lapdm_channel.lapdm_dcch.datalink[DL_SAPI0].dl.t200_usec = 0; + + /* send inication to upper layer */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = RR_REL_CAUSE_NORMAL; + nrrh->sapi = link_id & 7; + + return gsm48_rr_upmsg(ms, nmsg); +} + +/* request SAPI 3 establishment */ +static int gsm48_rr_est_req_sapi3(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + uint8_t ch_type, ch_subch, ch_ts; + struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *) msg->data; + uint8_t sapi = rrh->sapi; + struct msgb *nmsg; + + if (rr->state != GSM48_RR_ST_DEDICATED) { + struct gsm48_rr_hdr *nrrh; + + /* send inication to upper layer */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_REL_IND); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->cause = RR_REL_CAUSE_NORMAL; + nrrh->sapi = sapi; + return gsm48_rr_upmsg(ms, nmsg); + } + + rsl_dec_chan_nr(rr->cd_now.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (ch_type != RSL_CHAN_Bm_ACCHs + && ch_type != RSL_CHAN_Lm_ACCHs) { + LOGP(DRR, LOGL_INFO, "Requesting DCCH link, because no TCH " + "(sapi %d)\n", sapi); + rr->sapi3_link_id = 0x00 | sapi; /* SAPI 3, DCCH */ + /* raise T200 of SAPI 0 */ + ms->lapdm_channel.lapdm_dcch.datalink[DL_SAPI0].dl.t200_sec = + T200_DCCH_SHARED; + ms->lapdm_channel.lapdm_dcch.datalink[DL_SAPI0].dl.t200_usec= 0; + } else { + LOGP(DRR, LOGL_INFO, "Requesting ACCH link, because TCH " + "(sapi %d)\n", sapi); + rr->sapi3_link_id = 0x40 | sapi; /* SAPI 3, ACCH */ + } + + /* already established */ + new_sapi3_state(rr, GSM48_RR_SAPI3ST_WAIT_EST); + + /* send message */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + return gsm48_send_rsl_nol3(ms, RSL_MT_EST_REQ, nmsg, rr->sapi3_link_id); +} + +static int gsm48_rr_est_req_estab_sapi3(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *) msg->data; + uint8_t sapi = rrh->sapi; + struct msgb *nmsg; + struct gsm48_rr_hdr *nrrh; + + LOGP(DRR, LOGL_INFO, "Radio link SAPI3 already established\n"); + + /* send inication to upper layer */ + nmsg = gsm48_rr_msgb_alloc(GSM48_RR_EST_CNF); + if (!nmsg) + return -ENOMEM; + nrrh = (struct gsm48_rr_hdr *)nmsg->data; + nrrh->sapi = sapi; + + return gsm48_rr_upmsg(ms, nmsg); +} + +/* + * state machines + */ + +/* state trasitions for link layer messages (lower layer) */ +static struct dldatastate { + uint32_t states; + int type; + int (*rout) (struct osmocom_ms *ms, struct msgb *msg); +} dldatastatelist[] = { + /* SAPI 0 on DCCH */ + + /* data transfer */ + {SBIT(GSM48_RR_ST_IDLE) | + SBIT(GSM48_RR_ST_CONN_PEND) | + SBIT(GSM48_RR_ST_DEDICATED) | + SBIT(GSM48_RR_ST_REL_PEND), + RSL_MT_UNIT_DATA_IND, gsm48_rr_unit_data_ind}, + + {SBIT(GSM48_RR_ST_DEDICATED), /* 3.4.2 */ + RSL_MT_DATA_IND, gsm48_rr_data_ind}, + + /* esablish */ + {SBIT(GSM48_RR_ST_IDLE) | + SBIT(GSM48_RR_ST_CONN_PEND) | + SBIT(GSM48_RR_ST_REL_PEND), + RSL_MT_EST_CONF, gsm48_rr_estab_cnf}, + + /* resume */ + {SBIT(GSM48_RR_ST_DEDICATED), + RSL_MT_EST_CONF, gsm48_rr_estab_cnf_dedicated}, + + /* release */ + {SBIT(GSM48_RR_ST_CONN_PEND) | + SBIT(GSM48_RR_ST_DEDICATED), + RSL_MT_REL_IND, gsm48_rr_rel_ind}, + + {SBIT(GSM48_RR_ST_REL_PEND), + RSL_MT_REL_CONF, gsm48_rr_rel_cnf}, + + /* reconnect */ + {SBIT(GSM48_RR_ST_CONN_PEND) | + SBIT(GSM48_RR_ST_DEDICATED), + RSL_MT_REL_CONF, gsm48_rr_rel_cnf}, + + /* suspenion */ + {SBIT(GSM48_RR_ST_DEDICATED), + RSL_MT_SUSP_CONF, gsm48_rr_susp_cnf_dedicated}, + +#if 0 + {SBIT(GSM48_RR_ST_DEDICATED), + RSL_MT_CHAN_CNF, gsm48_rr_rand_acc_cnf_dedicated}, +#endif + + {SBIT(GSM48_RR_ST_DEDICATED), + RSL_MT_ERROR_IND, gsm48_rr_mdl_error_ind}, +}; + +#define DLDATASLLEN \ + (sizeof(dldatastatelist) / sizeof(struct dldatastate)) + +static struct dldatastate dldatastatelists3[] = { + /* SAPI 3 on DCCH */ + + /* establish */ + {SBIT(GSM48_RR_SAPI3ST_IDLE), + RSL_MT_EST_IND, gsm48_rr_estab_ind_sapi3}, + + /* establish */ + {SBIT(GSM48_RR_SAPI3ST_IDLE) | SBIT(GSM48_RR_SAPI3ST_WAIT_EST), + RSL_MT_EST_CONF, gsm48_rr_estab_cnf_sapi3}, + + /* data transfer */ + {SBIT(GSM48_RR_SAPI3ST_ESTAB), + RSL_MT_DATA_IND, gsm48_rr_data_ind_sapi3}, + + /* release */ + {SBIT(GSM48_RR_SAPI3ST_WAIT_EST) | SBIT(GSM48_RR_SAPI3ST_ESTAB), + RSL_MT_REL_IND, gsm48_rr_rel_ind_sapi3}, + + {ALL_STATES, + RSL_MT_ERROR_IND, gsm48_rr_mdl_error_ind}, +}; + +#define DLDATASLLENS3 \ + (sizeof(dldatastatelists3) / sizeof(struct dldatastate)) + +static int gsm48_rcv_rll(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + int msg_type = rllh->c.msg_type; + int link_id = rllh->link_id; + int i; + int rc; + + LOGP(DRSL, LOGL_INFO, "(ms %s) Received '%s' from L2 in state " + "%s (link_id 0x%x)\n", ms->name, rsl_msg_name(msg_type), + gsm48_rr_state_names[rr->state], link_id); + + /* find function for current state and message */ + if (!(link_id & 7)) { + /* SAPI 0 */ + for (i = 0; i < DLDATASLLEN; i++) + if ((msg_type == dldatastatelist[i].type) + && ((1 << rr->state) & dldatastatelist[i].states)) + break; + if (i == DLDATASLLEN) { + LOGP(DRSL, LOGL_NOTICE, "RSLms message '%s' " + "unhandled\n", rsl_msg_name(msg_type)); + msgb_free(msg); + return 0; + } + + rc = dldatastatelist[i].rout(ms, msg); + } else { + /* SAPI 3 */ + for (i = 0; i < DLDATASLLENS3; i++) + if ((msg_type == dldatastatelists3[i].type) + && ((1 << rr->sapi3_state) & + dldatastatelists3[i].states)) + break; + if (i == DLDATASLLENS3) { + LOGP(DRSL, LOGL_NOTICE, "RSLms message '%s' " + "unhandled\n", rsl_msg_name(msg_type)); + msgb_free(msg); + return 0; + } + + rc = dldatastatelists3[i].rout(ms, msg); + } + + /* free msgb unless it is forwarded */ + if (msg_type != RSL_MT_DATA_IND) + msgb_free(msg); + + return rc; +} + +static int gsm48_rcv_cch(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct abis_rsl_cchan_hdr *ch = msgb_l2(msg); + int msg_type = ch->c.msg_type; + int rc; + + LOGP(DRSL, LOGL_INFO, "(ms %s) Received '%s' from L2 in state " + "%s\n", ms->name, rsl_msg_name(msg_type), + gsm48_rr_state_names[rr->state]); + + if (rr->state == GSM48_RR_ST_CONN_PEND + && msg_type == RSL_MT_CHAN_CONF) { + rc = gsm48_rr_tx_rand_acc(ms, msg); + msgb_free(msg); + return rc; + } + + LOGP(DRSL, LOGL_NOTICE, "RSLms message unhandled\n"); + msgb_free(msg); + return 0; +} + + +/* input function for L2 messags up to L3 */ +static int gsm48_rcv_rsl(struct osmocom_ms *ms, struct msgb *msg) +{ + struct abis_rsl_common_hdr *rslh = msgb_l2(msg); + int rc = 0; + + switch (rslh->msg_discr & 0xfe) { + case ABIS_RSL_MDISC_RLL: + rc = gsm48_rcv_rll(ms, msg); + break; + case ABIS_RSL_MDISC_COM_CHAN: + rc = gsm48_rcv_cch(ms, msg); + break; + default: + /* FIXME: implement this */ + LOGP(DRSL, LOGL_NOTICE, "unknown RSLms msg_discr 0x%02x\n", + rslh->msg_discr); + msgb_free(msg); + rc = -EINVAL; + break; + } + + return rc; +} + +/* state trasitions for RR-SAP messages from up (main link) */ +static struct rrdownstate { + uint32_t states; + int type; + int (*rout) (struct osmocom_ms *ms, struct msgb *msg); +} rrdownstatelist[] = { + /* SAPI 0 */ + + /* NOTE: If not IDLE, it is rejected there. */ + {ALL_STATES, /* 3.3.1.1 */ + GSM48_RR_EST_REQ, gsm48_rr_est_req}, + + {SBIT(GSM48_RR_ST_DEDICATED), /* 3.4.2 */ + GSM48_RR_DATA_REQ, gsm48_rr_data_req}, + + {SBIT(GSM48_RR_ST_CONN_PEND) | + SBIT(GSM48_RR_ST_DEDICATED), /* 3.4.13.3 */ + GSM48_RR_ABORT_REQ, gsm48_rr_abort_req}, +}; + +#define RRDOWNSLLEN \ + (sizeof(rrdownstatelist) / sizeof(struct rrdownstate)) + +/* state trasitions for RR-SAP messages from up with (SAPI 3) */ +static struct rrdownstate rrdownstatelists3[] = { + /* SAPI 3 */ + + {SBIT(GSM48_RR_SAPI3ST_IDLE), + GSM48_RR_EST_REQ, gsm48_rr_est_req_sapi3}, + + {SBIT(GSM48_RR_SAPI3ST_ESTAB), + GSM48_RR_EST_REQ, gsm48_rr_est_req_estab_sapi3}, + + {SBIT(GSM48_RR_SAPI3ST_ESTAB), + GSM48_RR_DATA_REQ, gsm48_rr_data_req}, /* handles SAPI 3 too */ +}; + +#define RRDOWNSLLENS3 \ + (sizeof(rrdownstatelists3) / sizeof(struct rrdownstate)) + +int gsm48_rr_downmsg(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm48_rr_hdr *rrh = (struct gsm48_rr_hdr *) msg->data; + int msg_type = rrh->msg_type; + int sapi = rrh->sapi; + int i; + int rc; + + LOGP(DRR, LOGL_INFO, "(ms %s) Message '%s' received in state %s " + "(sapi %d)\n", ms->name, get_rr_name(msg_type), + gsm48_rr_state_names[rr->state], sapi); + + if (!sapi) { + /* SAPI 0: find function for current state and message */ + for (i = 0; i < RRDOWNSLLEN; i++) + if ((msg_type == rrdownstatelist[i].type) + && ((1 << rr->state) & rrdownstatelist[i].states)) + break; + if (i == RRDOWNSLLEN) { + LOGP(DRR, LOGL_NOTICE, "Message unhandled at this " + "state.\n"); + msgb_free(msg); + return 0; + } + + rc = rrdownstatelist[i].rout(ms, msg); + } else { + /* SAPI 3: find function for current state and message */ + for (i = 0; i < RRDOWNSLLENS3; i++) + if ((msg_type == rrdownstatelists3[i].type) + && ((1 << rr->sapi3_state) + & rrdownstatelists3[i].states)) + break; + if (i == RRDOWNSLLENS3) { + LOGP(DRR, LOGL_NOTICE, "Message unhandled at this " + "state.\n"); + msgb_free(msg); + return 0; + } + + rc = rrdownstatelists3[i].rout(ms, msg); + } + + /* free msgb unless it is forwarded */ + if (msg_type != GSM48_RR_DATA_REQ) + msgb_free(msg); + + return rc; +} + +/* + * init/exit + */ + +int gsm48_rr_init(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + memset(rr, 0, sizeof(*rr)); + rr->ms = ms; + + LOGP(DRR, LOGL_INFO, "init Radio Ressource process\n"); + + INIT_LLIST_HEAD(&rr->rsl_upqueue); + INIT_LLIST_HEAD(&rr->downqueue); + /* downqueue is handled here, so don't add_work */ + + lapdm_channel_set_l3(&ms->lapdm_channel, &rcv_rsl, ms); + + start_rr_t_meas(rr, 1, 0); + + rr->audio_mode = AUDIO_TX_MICROPHONE | AUDIO_RX_SPEAKER; + + return 0; +} + +int gsm48_rr_exit(struct osmocom_ms *ms) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *msg; + + LOGP(DRR, LOGL_INFO, "exit Radio Ressource process\n"); + + /* flush queues */ + while ((msg = msgb_dequeue(&rr->rsl_upqueue))) + msgb_free(msg); + while ((msg = msgb_dequeue(&rr->downqueue))) + msgb_free(msg); + + if (rr->rr_est_msg) { + msgb_free(rr->rr_est_msg); + rr->rr_est_msg = NULL; + } + + stop_rr_t_meas(rr); + stop_rr_t_starting(rr); + stop_rr_t_rel_wait(rr); + stop_rr_t3110(rr); + stop_rr_t3122(rr); + stop_rr_t3124(rr); + stop_rr_t3126(rr); + + return 0; +} + +#if 0 + +todo rr_sync_ind when receiving ciph, re ass, channel mode modify + + +static void timeout_rr_t3124(void *arg) +{ + struct gsm48_rrlayer *rr = arg; + struct msgb *nmsg; + + /* stop sending more access bursts when timer expired */ + hando_acc_left = 0; + + /* get old channel description */ + memcpy(&rr->chan_desc, &rr->chan_last, sizeof(rr->chan_desc)); + + /* change radio to old channel */ + tx_ph_dm_est_req(ms, rr->cd_now.arfcn, rr->cd_now.chan_nr, + rr->cd_now.tsc); + rr->dm_est = 1; + + /* re-establish old link */ + nmsg = gsm48_l3_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + return gsm48_send_rsl(ms, RSL_MT_REEST_REQ, nmsg, 0); + + todo +} + +/* send HANDOVER ACCESS burst (9.1.14) */ +static int gsm48_rr_tx_hando_access(struct osmocom_ms *ms) +{ + nmsg = msgb_alloc_headroom(20, 16, "HAND_ACCESS"); + if (!nmsg) + return -ENOMEM; + *msgb_put(nmsg, 1) = rr->hando_ref; + todo burst + return gsm48_send_rsl(ms, RSL_MT_RAND_ACC_REQ, nmsg, 0); +} + +/* send next channel request in dedicated state */ +static int gsm48_rr_rand_acc_cnf_dedicated(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct msgb *nmsg; + int s; + + if (rr->modify_state != GSM48_RR_MOD_HANDO) { + LOGP(DRR, LOGL_NOTICE, "Random acces confirm, but not in handover state.\n"); + return 0; + } + + /* send up to four handover access bursts */ + if (rr->hando_acc_left) { + rr->hando_acc_left--; + gsm48_rr_tx_hando_access(ms); + return; + } + + /* start timer for sending next HANDOVER ACCESS bursts afterwards */ + if (!osmo_timer_pending(&rr->t3124)) { + if (allocated channel is SDCCH) + start_rr_t3124(rr, GSM_T3124_675); + else + start_rr_t3124(rr, GSM_T3124_320); + } + if (!rr->n_chan_req) { + start_rr_t3126(rr, 5, 0); /* TODO improve! */ + return 0; + } + rr->n_chan_req--; + + /* wait for PHYSICAL INFORMATION message or T3124 timeout */ + return 0; + +} + +#endif + +int gsm48_rr_tx_voice(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + uint8_t ch_type, ch_subch, ch_ts; + + if (!rr->dm_est) { + LOGP(DRR, LOGL_INFO, "Current channel is not active\n"); + msgb_free(msg); + return -ENOTSUP; + } + + rsl_dec_chan_nr(rr->cd_now.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (ch_type != RSL_CHAN_Bm_ACCHs) { + LOGP(DRR, LOGL_INFO, "Current channel is not (yet) TCH/F\n"); + msgb_free(msg); + return -ENOTSUP; + } + + return l1ctl_tx_traffic_req(ms, msg, rr->cd_now.chan_nr, 0); +} + +int gsm48_rr_audio_mode(struct osmocom_ms *ms, uint8_t mode) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + uint8_t ch_type, ch_subch, ch_ts; + + LOGP(DRR, LOGL_INFO, "setting audio mode to %d\n", mode); + + rr->audio_mode = mode; + + if (!rr->dm_est) + return 0; + + rsl_dec_chan_nr(rr->cd_now.chan_nr, &ch_type, &ch_subch, &ch_ts); + if (ch_type != RSL_CHAN_Bm_ACCHs + && ch_type != RSL_CHAN_Lm_ACCHs) + return 0; + + return l1ctl_tx_tch_mode_req(ms, rr->cd_now.mode, mode); +} + diff --git a/src/host/layer23/src/mobile/main.c b/src/host/layer23/src/mobile/main.c new file mode 100644 index 00000000..312bcd66 --- /dev/null +++ b/src/host/layer23/src/mobile/main.c @@ -0,0 +1,271 @@ +/* Main method of the layer2/3 stack */ + +/* (C) 2010 by Holger Hans Peter Freyther + * (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/mobile/app_mobile.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/core/gsmtap.h> +#include <osmocom/core/signal.h> +#include <osmocom/core/application.h> + +#include <arpa/inet.h> + +#define _GNU_SOURCE +#include <getopt.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <time.h> +#include <libgen.h> + +struct log_target *stderr_target; + +void *l23_ctx = NULL; +struct llist_head ms_list; +static char *gsmtap_ip = 0; +struct gsmtap_inst *gsmtap_inst = NULL; +static char *vty_ip = "127.0.0.1"; +unsigned short vty_port = 4247; +int debug_set = 0; +char *config_dir = NULL; +int use_mncc_sock = 0; +int daemonize = 0; + +int mncc_recv_socket(struct osmocom_ms *ms, int msg_type, void *arg); + +int mobile_delete(struct osmocom_ms *ms, int force); +int mobile_signal_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data); +int mobile_work(struct osmocom_ms *ms); +int mobile_exit(struct osmocom_ms *ms, int force); + + +const char *debug_default = + "DCS:DNB:DPLMN:DRR:DMM:DSIM:DCC:DMNCC:DSS:DLSMS:DPAG:DSUM"; + +const char *openbsc_copyright = + "Copyright (C) 2008-2010 ...\n" + "Contributions by ...\n\n" + "License GPLv2+: GNU GPL version 2 or later " + "<http://gnu.org/licenses/gpl.html>\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n"; + +static void print_usage(const char *app) +{ + printf("Usage: %s\n", app); +} + +static void print_help() +{ + printf(" Some help...\n"); + printf(" -h --help this text\n"); + printf(" -i --gsmtap-ip The destination IP used for GSMTAP.\n"); + printf(" -u --vty-ip The VTY IP to telnet to. " + "(default %s)\n", vty_ip); + printf(" -v --vty-port The VTY port number to telnet to. " + "(default %u)\n", vty_port); + printf(" -d --debug Change debug flags. default: %s\n", + debug_default); + printf(" -D --daemonize Run as daemon\n"); + printf(" -m --mncc-sock Disable built-in MNCC handler and " + "offer socket\n"); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"gsmtap-ip", 1, 0, 'i'}, + {"vty-ip", 1, 0, 'u'}, + {"vty-port", 1, 0, 'v'}, + {"debug", 1, 0, 'd'}, + {"daemonize", 0, 0, 'D'}, + {"mncc-sock", 0, 0, 'm'}, + {0, 0, 0, 0}, + }; + + c = getopt_long(argc, argv, "hi:u:v:d:Dm", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage(argv[0]); + print_help(); + exit(0); + break; + case 'i': + gsmtap_ip = optarg; + break; + case 'u': + vty_ip = optarg; + break; + case 'v': + vty_port = atoi(optarg); + break; + case 'd': + log_parse_category_mask(stderr_target, optarg); + debug_set = 1; + break; + case 'D': + daemonize = 1; + break; + case 'm': + use_mncc_sock = 1; + break; + default: + break; + } + } +} + +void sighandler(int sigset) +{ + if (sigset == SIGHUP || sigset == SIGPIPE) + return; + + fprintf(stderr, "Signal %d received.\n", sigset); + + switch (sigset) { + case SIGINT: + /* If another signal is received afterwards, the program + * is terminated without finishing shutdown process. + */ + signal(SIGINT, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + signal(SIGABRT, SIG_DFL); + signal(SIGUSR1, SIG_DFL); + signal(SIGUSR2, SIG_DFL); + + osmo_signal_dispatch(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL); + break; + case SIGABRT: + /* in case of abort, we want to obtain a talloc report + * and then return to the caller, who will abort the process + */ + case SIGUSR1: + case SIGUSR2: + talloc_report_full(l23_ctx, stderr); + break; + } +} + +int main(int argc, char **argv) +{ + int quit = 0; + int rc; + char const * home; + size_t len; + const char osmocomcfg[] = ".osmocom/bb/mobile.cfg"; + char *config_file = NULL; + + printf("%s\n", openbsc_copyright); + + srand(time(NULL)); + + INIT_LLIST_HEAD(&ms_list); + log_init(&log_info, NULL); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + log_set_all_filter(stderr_target, 1); + + l23_ctx = talloc_named_const(NULL, 1, "layer2 context"); + msgb_set_talloc_ctx(l23_ctx); + + handle_options(argc, argv); + + if (!debug_set) + log_parse_category_mask(stderr_target, debug_default); + log_set_log_level(stderr_target, LOGL_DEBUG); + + if (gsmtap_ip) { + gsmtap_inst = gsmtap_source_init(gsmtap_ip, GSMTAP_UDP_PORT, 1); + if (!gsmtap_inst) { + fprintf(stderr, "Failed during gsmtap_init()\n"); + exit(1); + } + gsmtap_source_add_sink(gsmtap_inst); + } + + home = getenv("HOME"); + if (home != NULL) { + len = strlen(home) + 1 + sizeof(osmocomcfg); + config_file = talloc_size(l23_ctx, len); + if (config_file != NULL) + snprintf(config_file, len, "%s/%s", home, osmocomcfg); + } + /* save the config file directory name */ + config_dir = talloc_strdup(l23_ctx, config_file); + config_dir = dirname(config_dir); + + if (use_mncc_sock) + rc = l23_app_init(mncc_recv_socket, config_file, vty_ip, vty_port); + else + rc = l23_app_init(NULL, config_file, vty_ip, vty_port); + if (rc) + exit(rc); + + signal(SIGINT, sighandler); + signal(SIGHUP, sighandler); + signal(SIGTERM, sighandler); + signal(SIGPIPE, sighandler); + signal(SIGABRT, sighandler); + signal(SIGUSR1, sighandler); + signal(SIGUSR2, sighandler); + + if (daemonize) { + printf("Running as daemon\n"); + rc = osmo_daemonize(); + if (rc) + fprintf(stderr, "Failed to run as daemon\n"); + } + + while (1) { + l23_app_work(&quit); + if (quit && llist_empty(&ms_list)) + break; + osmo_select_main(0); + } + + l23_app_exit(); + + talloc_free(config_file); + talloc_free(config_dir); + talloc_report_full(l23_ctx, stderr); + + return 0; +} diff --git a/src/host/layer23/src/mobile/mncc_sock.c b/src/host/layer23/src/mobile/mncc_sock.c new file mode 100644 index 00000000..1e239428 --- /dev/null +++ b/src/host/layer23/src/mobile/mncc_sock.c @@ -0,0 +1,347 @@ +/* mncc_sock.c: Tie the MNCC interface to a unix domain socket */ + +/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2009,2011 by Andreas Eversberg <andreas@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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <stdint.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/mobile/mncc.h> +#include <osmocom/bb/mobile/mncc_sock.h> +#include <osmocom/bb/mobile/gsm48_cc.h> + +/* input from CC code into mncc_sock */ +int mncc_sock_from_cc(struct mncc_sock_state *state, struct msgb *msg) +{ + struct gsm_mncc *mncc_in = (struct gsm_mncc *) msgb_data(msg); + int msg_type = mncc_in->msg_type; + + /* Check if we currently have a MNCC handler connected */ + if (state->conn_bfd.fd < 0) { + LOGP(DMNCC, LOGL_ERROR, "mncc_sock receives %s for external CC app " + "but socket is gone\n", get_mncc_name(msg_type)); + if (msg_type != GSM_TCHF_FRAME + && msg_type != GSM_TCHF_FRAME_EFR + && msg_type != MNCC_REL_IND) { + /* release the request */ + struct gsm_mncc mncc_out; + memset(&mncc_out, 0, sizeof(mncc_out)); + mncc_out.callref = mncc_in->callref; + mncc_set_cause(&mncc_out, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_TEMP_FAILURE); + mncc_tx_to_cc(state->inst, MNCC_REL_REQ, &mncc_out); + } + /* free the original message */ + msgb_free(msg); + return -1; + } + + /* FIXME: check for some maximum queue depth? */ + + /* Actually enqueue the message and mark socket write need */ + msgb_enqueue(&state->upqueue, msg); + state->conn_bfd.when |= BSC_FD_WRITE; + return 0; +} + +void mncc_sock_write_pending(struct mncc_sock_state *state) +{ + state->conn_bfd.when |= BSC_FD_WRITE; +} + +/* FIXME: move this to libosmocore */ +int osmo_unixsock_listen(struct osmo_fd *bfd, int type, const char *path); + +static void mncc_sock_close(struct mncc_sock_state *state) +{ + struct osmo_fd *bfd = &state->conn_bfd; + + LOGP(DMNCC, LOGL_NOTICE, "MNCC Socket has closed connection\n"); + + close(bfd->fd); + bfd->fd = -1; + osmo_fd_unregister(bfd); + + /* re-enable the generation of ACCEPT for new connections */ + state->listen_bfd.when |= BSC_FD_READ; + + /* FIXME: make sure we don't enqueue anymore */ + + /* release all exisitng calls */ + mncc_clear_trans(state->inst, GSM48_PDISC_CC); + + /* flush the queue */ + while (!llist_empty(&state->upqueue)) { + struct msgb *msg = msgb_dequeue(&state->upqueue); + msgb_free(msg); + } +} + +static int mncc_sock_read(struct osmo_fd *bfd) +{ + struct mncc_sock_state *state = (struct mncc_sock_state *)bfd->data; + struct gsm_mncc *mncc_prim; + struct msgb *msg; + int rc; + + msg = msgb_alloc(sizeof(*mncc_prim)+256, "mncc_sock_rx"); + if (!msg) + return -ENOMEM; + + mncc_prim = (struct gsm_mncc *) msg->tail; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + + if (rc < 0) { + if (errno == EAGAIN) + return 0; + goto close; + } + + rc = mncc_tx_to_cc(state->inst, mncc_prim->msg_type, mncc_prim); + + /* as we always synchronously process the message in mncc_send() and + * its callbacks, we can free the message here. */ + msgb_free(msg); + + return rc; + +close: + msgb_free(msg); + mncc_sock_close(state); + return -1; +} + +static int mncc_sock_write(struct osmo_fd *bfd) +{ + struct mncc_sock_state *state = bfd->data; + int rc; + + while (!llist_empty(&state->upqueue)) { + struct msgb *msg, *msg2; + struct gsm_mncc *mncc_prim; + + /* peek at the beginning of the queue */ + msg = llist_entry(state->upqueue.next, struct msgb, list); + mncc_prim = (struct gsm_mncc *)msg->data; + + bfd->when &= ~BSC_FD_WRITE; + + /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ + if (!msgb_length(msg)) { + LOGP(DMNCC, LOGL_ERROR, "message type (%d) with ZERO " + "bytes!\n", mncc_prim->msg_type); + goto dontsend; + } + + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) { + bfd->when |= BSC_FD_WRITE; + break; + } + goto close; + } + +dontsend: + /* _after_ we send it, we can deueue */ + msg2 = msgb_dequeue(&state->upqueue); + assert(msg == msg2); + msgb_free(msg); + } + return 0; + +close: + mncc_sock_close(state); + + return -1; +} + +static int mncc_sock_cb(struct osmo_fd *bfd, unsigned int flags) +{ + int rc = 0; + + if (flags & BSC_FD_READ) + rc = mncc_sock_read(bfd); + if (rc < 0) + return rc; + + if (flags & BSC_FD_WRITE) + rc = mncc_sock_write(bfd); + + return rc; +} + +/* accept a new connection */ +static int mncc_sock_accept(struct osmo_fd *bfd, unsigned int flags) +{ + struct mncc_sock_state *state = (struct mncc_sock_state *)bfd->data; + struct osmo_fd *conn_bfd = &state->conn_bfd; + struct sockaddr_un un_addr; + socklen_t len; + int rc; + + len = sizeof(un_addr); + rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len); + if (rc < 0) { + LOGP(DMNCC, LOGL_ERROR, "Failed to accept a new connection\n"); + return -1; + } + + if (conn_bfd->fd >= 0) { + LOGP(DMNCC, LOGL_NOTICE, "MNCC app connects but we already have " + "another active connection ?!?\n"); + /* We already have one MNCC app connected, this is all we support */ + state->listen_bfd.when &= ~BSC_FD_READ; + close(rc); + return 0; + } + + conn_bfd->fd = rc; + conn_bfd->when = BSC_FD_READ; + conn_bfd->cb = mncc_sock_cb; + conn_bfd->data = state; + + if (osmo_fd_register(conn_bfd) != 0) { + LOGP(DMNCC, LOGL_ERROR, "Failed to register new connection fd\n"); + close(conn_bfd->fd); + conn_bfd->fd = -1; + return -1; + } + + LOGP(DMNCC, LOGL_NOTICE, "MNCC Socket has connection with external " + "call control application\n"); + + return 0; +} + + +struct mncc_sock_state *mncc_sock_init(void *inst, const char *name, void *tall_ctx) +{ + struct mncc_sock_state *state; + struct osmo_fd *bfd; + int rc; + + state = talloc_zero(tall_ctx, struct mncc_sock_state); + if (!state) + return NULL; + + state->inst = inst; + INIT_LLIST_HEAD(&state->upqueue); + state->conn_bfd.fd = -1; + + bfd = &state->listen_bfd; + + rc = osmo_unixsock_listen(bfd, SOCK_SEQPACKET, name); + if (rc < 0) { + LOGP(DMNCC, LOGL_ERROR, "Could not create unix socket: %s\n", + strerror(errno)); + talloc_free(state); + return NULL; + } + + bfd->when = BSC_FD_READ; + bfd->cb = mncc_sock_accept; + bfd->data = state; + + rc = osmo_fd_register(bfd); + if (rc < 0) { + LOGP(DMNCC, LOGL_ERROR, "Could not register listen fd: %d\n", rc); + close(bfd->fd); + talloc_free(state); + return NULL; + } + + return state; +} + +void mncc_sock_exit(struct mncc_sock_state *state) +{ + if (state->conn_bfd.fd > -1) + mncc_sock_close(state); + osmo_fd_unregister(&state->listen_bfd); + close(state->listen_bfd.fd); + talloc_free(state); +} + +/* FIXME: move this to libosmocore */ +int osmo_unixsock_listen(struct osmo_fd *bfd, int type, const char *path) +{ + struct sockaddr_un local; + unsigned int namelen; + int rc; + + bfd->fd = socket(AF_UNIX, type, 0); + + if (bfd->fd < 0) { + fprintf(stderr, "Failed to create Unix Domain Socket.\n"); + return -1; + } + + local.sun_family = AF_UNIX; + strncpy(local.sun_path, path, sizeof(local.sun_path)); + local.sun_path[sizeof(local.sun_path) - 1] = '\0'; + unlink(local.sun_path); + + /* we use the same magic that X11 uses in Xtranssock.c for + * calculating the proper length of the sockaddr */ +#if defined(BSD44SOCKETS) || defined(__UNIXWARE__) + local.sun_len = strlen(local.sun_path); +#endif +#if defined(BSD44SOCKETS) || defined(SUN_LEN) + namelen = SUN_LEN(&local); +#else + namelen = strlen(local.sun_path) + + offsetof(struct sockaddr_un, sun_path); +#endif + + rc = bind(bfd->fd, (struct sockaddr *) &local, namelen); + if (rc != 0) { + fprintf(stderr, "Failed to bind the unix domain socket. '%s'\n", + local.sun_path); + return -1; + } + + if (listen(bfd->fd, 0) != 0) { + fprintf(stderr, "Failed to listen.\n"); + return -1; + } + + return 0; +} diff --git a/src/host/layer23/src/mobile/mnccms.c b/src/host/layer23/src/mobile/mnccms.c new file mode 100644 index 00000000..9fdc45fd --- /dev/null +++ b/src/host/layer23/src/mobile/mnccms.c @@ -0,0 +1,813 @@ +/* + * (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdint.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <osmocom/core/talloc.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/mobile/mncc.h> +#include <osmocom/bb/mobile/vty.h> + +void *l23_ctx; +static uint32_t new_callref = 1; +static LLIST_HEAD(call_list); + +void mncc_set_cause(struct gsm_mncc *data, int loc, int val); +static int dtmf_statemachine(struct gsm_call *call, struct gsm_mncc *mncc); +static void timeout_dtmf(void *arg); +int mncc_answer(struct osmocom_ms *ms); + +/* + * support functions + */ + +/* DTMF timer */ +static void start_dtmf_timer(struct gsm_call *call, uint16_t ms) +{ + LOGP(DCC, LOGL_INFO, "starting DTMF timer %d ms\n", ms); + call->dtmf_timer.cb = timeout_dtmf; + call->dtmf_timer.data = call; + osmo_timer_schedule(&call->dtmf_timer, 0, ms * 1000); +} + +static void stop_dtmf_timer(struct gsm_call *call) +{ + if (osmo_timer_pending(&call->dtmf_timer)) { + LOGP(DCC, LOGL_INFO, "stopping pending DTMF timer\n"); + osmo_timer_del(&call->dtmf_timer); + } +} + +/* free call instance */ +static void free_call(struct gsm_call *call) +{ + stop_dtmf_timer(call); + + llist_del(&call->entry); + DEBUGP(DMNCC, "(call %x) Call removed.\n", call->callref); + talloc_free(call); +} + + +struct gsm_call *get_call_ref(uint32_t callref) +{ + struct gsm_call *callt; + + llist_for_each_entry(callt, &call_list, entry) { + if (callt->callref == callref) + return callt; + } + return NULL; +} + +static int8_t mncc_get_bearer(struct gsm_settings *set, uint8_t speech_ver) +{ + switch (speech_ver) { + case 4: + if (set->full_v3) + LOGP(DMNCC, LOGL_INFO, " net suggests full rate v3\n"); + else { + LOGP(DMNCC, LOGL_INFO, " full rate v3 not supported\n"); + speech_ver = -1; + } + break; + case 2: + if (set->full_v2) + LOGP(DMNCC, LOGL_INFO, " net suggests full rate v2\n"); + else { + LOGP(DMNCC, LOGL_INFO, " full rate v2 not supported\n"); + speech_ver = -1; + } + break; + case 0: /* mandatory */ + if (set->full_v1) + LOGP(DMNCC, LOGL_INFO, " net suggests full rate v1\n"); + else { + LOGP(DMNCC, LOGL_INFO, " full rate v1 not supported\n"); + speech_ver = -1; + } + break; + case 5: + if (set->half_v3) + LOGP(DMNCC, LOGL_INFO, " net suggests half rate v3\n"); + else { + LOGP(DMNCC, LOGL_INFO, " half rate v3 not supported\n"); + speech_ver = -1; + } + break; + case 1: + if (set->half_v1) + LOGP(DMNCC, LOGL_INFO, " net suggests half rate v1\n"); + else { + LOGP(DMNCC, LOGL_INFO, " half rate v1 not supported\n"); + speech_ver = -1; + } + break; + default: + LOGP(DMNCC, LOGL_INFO, " net suggests unknown speech version " + "%d\n", speech_ver); + speech_ver = -1; + } + + return speech_ver; +} + +static void mncc_set_bearer(struct osmocom_ms *ms, int8_t speech_ver, + struct gsm_mncc *mncc) +{ + struct gsm_settings *set = &ms->settings; + int i = 0; + + mncc->fields |= MNCC_F_BEARER_CAP; + mncc->bearer_cap.coding = 0; + if (set->ch_cap == GSM_CAP_SDCCH_TCHF_TCHH + && (set->half_v1 || set->half_v3)) { + mncc->bearer_cap.radio = 3; + LOGP(DMNCC, LOGL_INFO, " support TCH/H also\n"); + } else { + mncc->bearer_cap.radio = 1; + LOGP(DMNCC, LOGL_INFO, " support TCH/F only\n"); + } + mncc->bearer_cap.speech_ctm = 0; + /* if no specific speech_ver is given */ + if (speech_ver < 0) { + /* if half rate is supported and prefered */ + if (set->half_v3 && set->half && set->half_prefer) { + mncc->bearer_cap.speech_ver[i++] = 5; + LOGP(DMNCC, LOGL_INFO, " support half rate v3\n"); + } + if (set->half_v1 && set->half && set->half_prefer) { + mncc->bearer_cap.speech_ver[i++] = 1; + LOGP(DMNCC, LOGL_INFO, " support half rate v1\n"); + } + /* if full rate is supported */ + if (set->full_v3) { + mncc->bearer_cap.speech_ver[i++] = 4; + LOGP(DMNCC, LOGL_INFO, " support full rate v3\n"); + } + if (set->full_v2) { + mncc->bearer_cap.speech_ver[i++] = 2; + LOGP(DMNCC, LOGL_INFO, " support full rate v2\n"); + } + if (set->full_v1) { /* mandatory, so it's always true */ + mncc->bearer_cap.speech_ver[i++] = 0; + LOGP(DMNCC, LOGL_INFO, " support full rate v1\n"); + } + /* if half rate is supported and not prefered */ + if (set->half_v3 && set->half && !set->half_prefer) { + mncc->bearer_cap.speech_ver[i++] = 5; + LOGP(DMNCC, LOGL_INFO, " support half rate v3\n"); + } + if (set->half_v1 && set->half && !set->half_prefer) { + mncc->bearer_cap.speech_ver[i++] = 1; + LOGP(DMNCC, LOGL_INFO, " support half rate v1\n"); + } + /* if specific speech_ver is given (it must be supported) */ + } else + mncc->bearer_cap.speech_ver[i++] = speech_ver; + mncc->bearer_cap.speech_ver[i] = -1; /* end of list */ + mncc->bearer_cap.transfer = 0; + mncc->bearer_cap.mode = 0; +} + +/* + * MNCCms dummy application + */ + +/* this is a minimal implementation as required by GSM 04.08 */ +int mncc_recv_dummy(struct osmocom_ms *ms, int msg_type, void *arg) +{ + struct gsm_mncc *data = arg; + uint32_t callref = data->callref; + struct gsm_mncc rel; + + if (msg_type == MNCC_REL_IND || msg_type == MNCC_REL_CNF) + return 0; + + LOGP(DMNCC, LOGL_INFO, "Rejecting incoming call\n"); + + /* reject, as we don't support Calls */ + memset(&rel, 0, sizeof(struct gsm_mncc)); + rel.callref = callref; + mncc_set_cause(&rel, GSM48_CAUSE_LOC_USER, + GSM48_CC_CAUSE_INCOMPAT_DEST); + + return mncc_tx_to_cc(ms, MNCC_REL_REQ, &rel); +} + +/* + * MNCCms call application via socket + */ +int mncc_recv_socket(struct osmocom_ms *ms, int msg_type, void *arg) +{ + struct mncc_sock_state *state = ms->mncc_entity.sock_state; + struct gsm_mncc *mncc = arg; + struct msgb *msg; + unsigned char *data; + + if (!state) { + if (msg_type != MNCC_REL_IND && msg_type != MNCC_REL_CNF) { + struct gsm_mncc rel; + + /* reject */ + memset(&rel, 0, sizeof(struct gsm_mncc)); + rel.callref = mncc->callref; + mncc_set_cause(&rel, GSM48_CAUSE_LOC_USER, + GSM48_CC_CAUSE_TEMP_FAILURE); + return mncc_tx_to_cc(ms, MNCC_REL_REQ, &rel); + } + return 0; + } + + mncc->msg_type = msg_type; + + msg = msgb_alloc(sizeof(struct gsm_mncc), "MNCC"); + if (!msg) + return -ENOMEM; + + data = msgb_put(msg, sizeof(struct gsm_mncc)); + memcpy(data, mncc, sizeof(struct gsm_mncc)); + + return mncc_sock_from_cc(state, msg); +} + +/* + * MNCCms basic call application + */ + +int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg) +{ + struct gsm_settings *set = &ms->settings; + struct gsm_mncc *data = arg; + struct gsm_call *call = get_call_ref(data->callref); + struct gsm_mncc mncc; + uint8_t cause; + int8_t speech_ver = -1, speech_ver_half = -1, temp; + int first_call = 0; + + /* call does not exist */ + if (!call && msg_type != MNCC_SETUP_IND) { + LOGP(DMNCC, LOGL_INFO, "Rejecting incoming call " + "(callref %x)\n", data->callref); + if (msg_type == MNCC_REL_IND || msg_type == MNCC_REL_CNF) + return 0; + cause = GSM48_CC_CAUSE_INCOMPAT_DEST; + release: + memset(&mncc, 0, sizeof(struct gsm_mncc)); + mncc.callref = data->callref; + mncc_set_cause(&mncc, GSM48_CAUSE_LOC_USER, cause); + return mncc_tx_to_cc(ms, MNCC_REL_REQ, &mncc); + } + + /* setup without call */ + if (!call) { + if (llist_empty(&call_list)) + first_call = 1; + call = talloc_zero(l23_ctx, struct gsm_call); + if (!call) + return -ENOMEM; + call->ms = ms; + call->callref = data->callref; + llist_add_tail(&call->entry, &call_list); + } + + /* not in initiated state anymore */ + call->init = 0; + + switch (msg_type) { + case MNCC_DISC_IND: + vty_notify(ms, NULL); + switch (data->cause.value) { + case GSM48_CC_CAUSE_UNASSIGNED_NR: + vty_notify(ms, "Call: Number not assigned\n"); + break; + case GSM48_CC_CAUSE_NO_ROUTE: + vty_notify(ms, "Call: Destination unreachable\n"); + break; + case GSM48_CC_CAUSE_NORM_CALL_CLEAR: + vty_notify(ms, "Call: Remote hangs up\n"); + break; + case GSM48_CC_CAUSE_USER_BUSY: + vty_notify(ms, "Call: Remote busy\n"); + break; + case GSM48_CC_CAUSE_USER_NOTRESPOND: + vty_notify(ms, "Call: Remote not responding\n"); + break; + case GSM48_CC_CAUSE_USER_ALERTING_NA: + vty_notify(ms, "Call: Remote not answering\n"); + break; + case GSM48_CC_CAUSE_CALL_REJECTED: + vty_notify(ms, "Call has been rejected\n"); + break; + case GSM48_CC_CAUSE_NUMBER_CHANGED: + vty_notify(ms, "Call: Number changed\n"); + break; + case GSM48_CC_CAUSE_PRE_EMPTION: + vty_notify(ms, "Call: Cleared due to pre-emption\n"); + break; + case GSM48_CC_CAUSE_DEST_OOO: + vty_notify(ms, "Call: Remote out of order\n"); + break; + case GSM48_CC_CAUSE_INV_NR_FORMAT: + vty_notify(ms, "Call: Number invalid or imcomplete\n"); + break; + case GSM48_CC_CAUSE_NO_CIRCUIT_CHAN: + vty_notify(ms, "Call: No channel available\n"); + break; + case GSM48_CC_CAUSE_NETWORK_OOO: + vty_notify(ms, "Call: Network out of order\n"); + break; + case GSM48_CC_CAUSE_TEMP_FAILURE: + vty_notify(ms, "Call: Temporary failure\n"); + break; + case GSM48_CC_CAUSE_SWITCH_CONG: + vty_notify(ms, "Congestion\n"); + break; + default: + vty_notify(ms, "Call has been disconnected " + "(clear cause %d)\n", data->cause.value); + } + LOGP(DMNCC, LOGL_INFO, "Call has been disconnected " + "(cause %d)\n", data->cause.value); + if ((data->fields & MNCC_F_PROGRESS) + && data->progress.descr == 8) { + vty_notify(ms, "Please hang up!\n"); + break; + } + free_call(call); + cause = GSM48_CC_CAUSE_NORM_CALL_CLEAR; + goto release; + case MNCC_REL_IND: + case MNCC_REL_CNF: + vty_notify(ms, NULL); + if (data->cause.value == GSM48_CC_CAUSE_CALL_REJECTED) + vty_notify(ms, "Call has been rejected\n"); + else + vty_notify(ms, "Call has been released\n"); + LOGP(DMNCC, LOGL_INFO, "Call has been released (cause %d)\n", + data->cause.value); + free_call(call); + break; + case MNCC_CALL_PROC_IND: + vty_notify(ms, NULL); + vty_notify(ms, "Call is proceeding\n"); + LOGP(DMNCC, LOGL_INFO, "Call is proceeding\n"); + if ((data->fields & MNCC_F_BEARER_CAP) + && data->bearer_cap.speech_ver[0] >= 0) { + mncc_get_bearer(set, data->bearer_cap.speech_ver[0]); + } + break; + case MNCC_ALERT_IND: + vty_notify(ms, NULL); + vty_notify(ms, "Call is alerting\n"); + LOGP(DMNCC, LOGL_INFO, "Call is alerting\n"); + break; + case MNCC_SETUP_CNF: + vty_notify(ms, NULL); + vty_notify(ms, "Call is answered\n"); + LOGP(DMNCC, LOGL_INFO, "Call is answered\n"); + break; + case MNCC_SETUP_IND: + vty_notify(ms, NULL); + if (!first_call && !ms->settings.cw) { + vty_notify(ms, "Incoming call rejected while busy\n"); + LOGP(DMNCC, LOGL_INFO, "Incoming call but busy\n"); + cause = GSM48_CC_CAUSE_USER_BUSY; + goto release; + } + /* select first supported speech_ver */ + if ((data->fields & MNCC_F_BEARER_CAP)) { + int i; + + for (i = 0; data->bearer_cap.speech_ver[i] >= 0; i++) { + + temp = mncc_get_bearer(set, + data->bearer_cap.speech_ver[i]); + if (temp < 0) + continue; + if (temp == 5 || temp == 1) { /* half */ + /* only the first half rate */ + if (speech_ver_half < 0) + speech_ver_half = temp; + } else { + /* only the first full rate */ + if (speech_ver < 0) + speech_ver = temp; + } + } + /* half and full given */ + if (speech_ver_half >= 0 && speech_ver >= 0) { + if (set->half_prefer) { + LOGP(DMNCC, LOGL_INFO, " both supported" + " codec rates are given, using " + "preferred half rate\n"); + speech_ver = speech_ver_half; + } else + LOGP(DMNCC, LOGL_INFO, " both supported" + " codec rates are given, using " + "preferred full rate\n"); + } else if (speech_ver_half < 0 && speech_ver < 0) { + LOGP(DMNCC, LOGL_INFO, " no supported codec " + "rate is given\n"); + /* only half rate is given, use it */ + } else if (speech_ver_half >= 0) { + LOGP(DMNCC, LOGL_INFO, " only supported half " + "rate codec is given, using it\n"); + speech_ver = speech_ver_half; + /* only full rate is given, use it */ + } else { + LOGP(DMNCC, LOGL_INFO, " only supported full " + "rate codec is given, using it\n"); + } + } + /* presentation allowed if present == 0 */ + if (data->calling.present || !data->calling.number[0]) + vty_notify(ms, "Incoming call (anonymous)\n"); + else if (data->calling.type == 1) + vty_notify(ms, "Incoming call (from +%s)\n", + data->calling.number); + else if (data->calling.type == 2) + vty_notify(ms, "Incoming call (from 0-%s)\n", + data->calling.number); + else + vty_notify(ms, "Incoming call (from %s)\n", + data->calling.number); + LOGP(DMNCC, LOGL_INFO, "Incoming call (from %s callref %x)\n", + data->calling.number, call->callref); + memset(&mncc, 0, sizeof(struct gsm_mncc)); + mncc.callref = call->callref; + /* only include bearer cap, if not given in setup + * or if multiple codecs are given + * or if not only full rate + * or if given codec is unimplemented + */ + if (!(data->fields & MNCC_F_BEARER_CAP) || speech_ver < 0) + mncc_set_bearer(ms, -1, &mncc); + else if (data->bearer_cap.speech_ver[1] >= 0 + || speech_ver != 0) + mncc_set_bearer(ms, speech_ver, &mncc); + /* CC capabilities (optional) */ + if (ms->settings.cc_dtmf) { + mncc.fields |= MNCC_F_CCCAP; + mncc.cccap.dtmf = 1; + } + mncc_tx_to_cc(ms, MNCC_CALL_CONF_REQ, &mncc); + if (first_call) + LOGP(DMNCC, LOGL_INFO, "Ring!\n"); + else { + LOGP(DMNCC, LOGL_INFO, "Knock!\n"); + call->hold = 1; + } + call->ring = 1; + memset(&mncc, 0, sizeof(struct gsm_mncc)); + mncc.callref = call->callref; + mncc_tx_to_cc(ms, MNCC_ALERT_REQ, &mncc); + if (ms->settings.auto_answer) { + LOGP(DMNCC, LOGL_INFO, "Auto-answering call\n"); + mncc_answer(ms); + } + break; + case MNCC_SETUP_COMPL_IND: + vty_notify(ms, NULL); + vty_notify(ms, "Call is connected\n"); + LOGP(DMNCC, LOGL_INFO, "Call is connected\n"); + break; + case MNCC_HOLD_CNF: + vty_notify(ms, NULL); + vty_notify(ms, "Call is on hold\n"); + LOGP(DMNCC, LOGL_INFO, "Call is on hold\n"); + call->hold = 1; + break; + case MNCC_HOLD_REJ: + vty_notify(ms, NULL); + vty_notify(ms, "Call hold was rejected\n"); + LOGP(DMNCC, LOGL_INFO, "Call hold was rejected\n"); + break; + case MNCC_RETRIEVE_CNF: + vty_notify(ms, NULL); + vty_notify(ms, "Call is retrieved\n"); + LOGP(DMNCC, LOGL_INFO, "Call is retrieved\n"); + call->hold = 0; + break; + case MNCC_RETRIEVE_REJ: + vty_notify(ms, NULL); + vty_notify(ms, "Call retrieve was rejected\n"); + LOGP(DMNCC, LOGL_INFO, "Call retrieve was rejected\n"); + break; + case MNCC_FACILITY_IND: + LOGP(DMNCC, LOGL_INFO, "Facility info not displayed, " + "unsupported\n"); + break; + case MNCC_START_DTMF_RSP: + case MNCC_START_DTMF_REJ: + case MNCC_STOP_DTMF_RSP: + dtmf_statemachine(call, data); + break; + default: + LOGP(DMNCC, LOGL_INFO, "Message 0x%02x unsupported\n", + msg_type); + return -EINVAL; + } + + return 0; +} + +int mncc_call(struct osmocom_ms *ms, char *number) +{ + struct gsm_call *call; + struct gsm_mncc setup; + + llist_for_each_entry(call, &call_list, entry) { + if (!call->hold) { + vty_notify(ms, NULL); + vty_notify(ms, "Please put active call on hold " + "first!\n"); + LOGP(DMNCC, LOGL_INFO, "Cannot make a call, busy!\n"); + return -EBUSY; + } + } + + call = talloc_zero(l23_ctx, struct gsm_call); + if (!call) + return -ENOMEM; + call->ms = ms; + call->callref = new_callref++; + call->init = 1; + llist_add_tail(&call->entry, &call_list); + + memset(&setup, 0, sizeof(struct gsm_mncc)); + setup.callref = call->callref; + + if (!strncasecmp(number, "emerg", 5)) { + LOGP(DMNCC, LOGL_INFO, "Make emergency call\n"); + /* emergency */ + setup.emergency = 1; + } else { + LOGP(DMNCC, LOGL_INFO, "Make call to %s\n", number); + /* called number */ + setup.fields |= MNCC_F_CALLED; + if (number[0] == '+') { + number++; + setup.called.type = 1; /* international */ + } else + setup.called.type = 0; /* auto/unknown - prefix must be + used */ + setup.called.plan = 1; /* ISDN */ + strncpy(setup.called.number, number, + sizeof(setup.called.number) - 1); + + /* bearer capability (mandatory) */ + mncc_set_bearer(ms, -1, &setup); + if (ms->settings.clir) + setup.clir.sup = 1; + else if (ms->settings.clip) + setup.clir.inv = 1; + + /* CC capabilities (optional) */ + if (ms->settings.cc_dtmf) { + setup.fields |= MNCC_F_CCCAP; + setup.cccap.dtmf = 1; + } + } + + return mncc_tx_to_cc(ms, MNCC_SETUP_REQ, &setup); +} + +int mncc_hangup(struct osmocom_ms *ms) +{ + struct gsm_call *call, *found = NULL; + struct gsm_mncc disc; + + llist_for_each_entry(call, &call_list, entry) { + if (!call->hold) { + found = call; + break; + } + } + if (!found) { + LOGP(DMNCC, LOGL_INFO, "No active call to hangup\n"); + vty_notify(ms, NULL); + vty_notify(ms, "No active call\n"); + return -EINVAL; + } + + memset(&disc, 0, sizeof(struct gsm_mncc)); + disc.callref = found->callref; + mncc_set_cause(&disc, GSM48_CAUSE_LOC_USER, + GSM48_CC_CAUSE_NORM_CALL_CLEAR); + return mncc_tx_to_cc(ms, (call->init) ? MNCC_REL_REQ : MNCC_DISC_REQ, + &disc); +} + +int mncc_answer(struct osmocom_ms *ms) +{ + struct gsm_call *call, *alerting = NULL; + struct gsm_mncc rsp; + int active = 0; + + llist_for_each_entry(call, &call_list, entry) { + if (call->ring) + alerting = call; + else if (!call->hold) + active = 1; + } + if (!alerting) { + LOGP(DMNCC, LOGL_INFO, "No call alerting\n"); + vty_notify(ms, NULL); + vty_notify(ms, "No alerting call\n"); + return -EBUSY; + } + if (active) { + LOGP(DMNCC, LOGL_INFO, "Answer but we have an active call\n"); + vty_notify(ms, NULL); + vty_notify(ms, "Please put active call on hold first!\n"); + return -EBUSY; + } + alerting->ring = 0; + alerting->hold = 0; + + memset(&rsp, 0, sizeof(struct gsm_mncc)); + rsp.callref = alerting->callref; + return mncc_tx_to_cc(ms, MNCC_SETUP_RSP, &rsp); +} + +int mncc_hold(struct osmocom_ms *ms) +{ + struct gsm_call *call, *found = NULL; + struct gsm_mncc hold; + + llist_for_each_entry(call, &call_list, entry) { + if (!call->hold) { + found = call; + break; + } + } + if (!found) { + LOGP(DMNCC, LOGL_INFO, "No active call to hold\n"); + vty_notify(ms, NULL); + vty_notify(ms, "No active call\n"); + return -EINVAL; + } + + memset(&hold, 0, sizeof(struct gsm_mncc)); + hold.callref = found->callref; + return mncc_tx_to_cc(ms, MNCC_HOLD_REQ, &hold); +} + +int mncc_retrieve(struct osmocom_ms *ms, int number) +{ + struct gsm_call *call; + struct gsm_mncc retr; + int holdnum = 0, active = 0, i = 0; + + llist_for_each_entry(call, &call_list, entry) { + if (call->hold) + holdnum++; + if (!call->hold) + active = 1; + } + if (active) { + LOGP(DMNCC, LOGL_INFO, "Cannot retrieve during active call\n"); + vty_notify(ms, NULL); + vty_notify(ms, "Hold active call first!\n"); + return -EINVAL; + } + if (holdnum == 0) { + vty_notify(ms, NULL); + vty_notify(ms, "No call on hold!\n"); + return -EINVAL; + } + if (holdnum > 1 && number <= 0) { + vty_notify(ms, NULL); + vty_notify(ms, "Select call 1..%d\n", holdnum); + return -EINVAL; + } + if (holdnum == 1 && number <= 0) + number = 1; + if (number > holdnum) { + vty_notify(ms, NULL); + vty_notify(ms, "Given number %d out of range!\n", number); + vty_notify(ms, "Select call 1..%d\n", holdnum); + return -EINVAL; + } + + llist_for_each_entry(call, &call_list, entry) { + i++; + if (i == number) + break; + } + + memset(&retr, 0, sizeof(struct gsm_mncc)); + retr.callref = call->callref; + return mncc_tx_to_cc(ms, MNCC_RETRIEVE_REQ, &retr); +} + +/* + * DTMF + */ + +static int dtmf_statemachine(struct gsm_call *call, struct gsm_mncc *mncc) +{ + struct osmocom_ms *ms = call->ms; + struct gsm_mncc dtmf; + + switch (call->dtmf_state) { + case DTMF_ST_SPACE: + case DTMF_ST_IDLE: + /* end of string */ + if (!call->dtmf[call->dtmf_index]) { + LOGP(DMNCC, LOGL_INFO, "done with DTMF\n"); + call->dtmf_state = DTMF_ST_IDLE; + return -EOF; + } + memset(&dtmf, 0, sizeof(struct gsm_mncc)); + dtmf.callref = call->callref; + dtmf.keypad = call->dtmf[call->dtmf_index++]; + call->dtmf_state = DTMF_ST_START; + LOGP(DMNCC, LOGL_INFO, "start DTMF (keypad %c)\n", + dtmf.keypad); + return mncc_tx_to_cc(ms, MNCC_START_DTMF_REQ, &dtmf); + case DTMF_ST_START: + if (mncc->msg_type != MNCC_START_DTMF_RSP) { + LOGP(DMNCC, LOGL_INFO, "DTMF was rejected\n"); + return -ENOTSUP; + } + start_dtmf_timer(call, 70); + call->dtmf_state = DTMF_ST_MARK; + LOGP(DMNCC, LOGL_INFO, "DTMF is on\n"); + break; + case DTMF_ST_MARK: + memset(&dtmf, 0, sizeof(struct gsm_mncc)); + dtmf.callref = call->callref; + call->dtmf_state = DTMF_ST_STOP; + LOGP(DMNCC, LOGL_INFO, "stop DTMF\n"); + return mncc_tx_to_cc(ms, MNCC_STOP_DTMF_REQ, &dtmf); + case DTMF_ST_STOP: + start_dtmf_timer(call, 120); + call->dtmf_state = DTMF_ST_SPACE; + LOGP(DMNCC, LOGL_INFO, "DTMF is off\n"); + break; + } + + return 0; +} + +static void timeout_dtmf(void *arg) +{ + struct gsm_call *call = arg; + + LOGP(DCC, LOGL_INFO, "DTMF timer has fired\n"); + dtmf_statemachine(call, NULL); +} + +int mncc_dtmf(struct osmocom_ms *ms, char *dtmf) +{ + struct gsm_call *call, *found = NULL; + + llist_for_each_entry(call, &call_list, entry) { + if (!call->hold) { + found = call; + break; + } + } + if (!found) { + LOGP(DMNCC, LOGL_INFO, "No active call to send DTMF\n"); + vty_notify(ms, NULL); + vty_notify(ms, "No active call\n"); + return -EINVAL; + } + + if (call->dtmf_state != DTMF_ST_IDLE) { + LOGP(DMNCC, LOGL_INFO, "sending DTMF already\n"); + return -EINVAL; + } + + call->dtmf_index = 0; + strncpy(call->dtmf, dtmf, sizeof(call->dtmf) - 1); + return dtmf_statemachine(call, NULL); +} + diff --git a/src/host/layer23/src/mobile/settings.c b/src/host/layer23/src/mobile/settings.c new file mode 100644 index 00000000..e34db7e1 --- /dev/null +++ b/src/host/layer23/src/mobile/settings.c @@ -0,0 +1,188 @@ +/* + * (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdint.h> +#include <errno.h> +#include <string.h> +#include <osmocom/core/talloc.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/networks.h> + +static char *layer2_socket_path = "/tmp/osmocom_l2"; +static char *sap_socket_path = "/tmp/osmocom_sap"; + +int gsm_settings_init(struct osmocom_ms *ms) +{ + struct gsm_settings *set = &ms->settings; + struct gsm_support *sup = &ms->support; + + strcpy(set->layer2_socket_path, layer2_socket_path); + strcpy(set->sap_socket_path, sap_socket_path); + + /* network search */ + set->plmn_mode = PLMN_MODE_AUTO; + + /* IMEI */ + sprintf(set->imei, "000000000000000"); + sprintf(set->imeisv, "0000000000000000"); + + /* SIM type */ + set->sim_type = GSM_SIM_TYPE_READER; + + /* test SIM */ + strcpy(set->test_imsi, "001010000000000"); + set->test_rplmn_mcc = set->test_rplmn_mnc = 1; + set->test_lac = 0x0000; + set->test_tmsi = 0xffffffff; + + /* set all supported features */ + set->sms_ptp = sup->sms_ptp; + set->a5_1 = sup->a5_1; + set->a5_2 = sup->a5_2; + set->a5_3 = sup->a5_3; + set->a5_4 = sup->a5_4; + set->a5_5 = sup->a5_5; + set->a5_6 = sup->a5_6; + set->a5_7 = sup->a5_7; + set->p_gsm = sup->p_gsm; + set->e_gsm = sup->e_gsm; + set->r_gsm = sup->r_gsm; + set->dcs = sup->dcs; + set->class_900 = sup->class_900; + set->class_dcs = sup->class_dcs; + set->class_850 = sup->class_850; + set->class_pcs = sup->class_pcs; + set->class_400 = sup->class_400; + set->full_v1 = sup->full_v1; + set->full_v2 = sup->full_v2; + set->full_v3 = sup->full_v3; + set->half_v1 = sup->half_v1; + set->half_v3 = sup->half_v3; + set->ch_cap = sup->ch_cap; + set->min_rxlev_db = sup->min_rxlev_db; + set->dsc_max = sup->dsc_max; + + if (sup->half_v1 || sup->half_v3) + set->half = 1; + + + /* software features */ + set->cc_dtmf = 1; + + INIT_LLIST_HEAD(&set->abbrev); + + return 0; +} + +int gsm_settings_arfcn(struct osmocom_ms *ms) +{ + int i; + struct gsm_settings *set = &ms->settings; + + /* set supported frequencies */ + memset(set->freq_map, 0, sizeof(set->freq_map)); + if (set->p_gsm) + for(i = 1; i <= 124; i++) + set->freq_map[i >> 3] |= (1 << (i & 7)); + if (set->gsm_850) + for(i = 128; i <= 251; i++) + set->freq_map[i >> 3] |= (1 << (i & 7)); + if (set->gsm_450) + for(i = 259; i <= 293; i++) + set->freq_map[i >> 3] |= (1 << (i & 7)); + if (set->gsm_480) + for(i = 306; i <= 340; i++) + set->freq_map[i >> 3] |= (1 << (i & 7)); + if (set->dcs) + for(i = 512; i <= 885; i++) + set->freq_map[i >> 3] |= (1 << (i & 7)); + if (set->pcs) + for(i = 1024; i <= 1024-512+810; i++) + set->freq_map[i >> 3] |= (1 << (i & 7)); + if (set->e_gsm) { + for(i = 975; i <= 1023; i++) + set->freq_map[i >> 3] |= (1 << (i & 7)); + set->freq_map[0] |= 1; + } + if (set->r_gsm) + for(i = 955; i <= 974; i++) + set->freq_map[i >> 3] |= (1 << (i & 7)); + + return 0; +} + +int gsm_settings_exit(struct osmocom_ms *ms) +{ + struct gsm_settings *set = &ms->settings; + struct gsm_settings_abbrev *abbrev; + + while (!llist_empty(&set->abbrev)) { + abbrev = llist_entry(set->abbrev.next, + struct gsm_settings_abbrev, list); + llist_del(&abbrev->list); + talloc_free(abbrev); + } + + return 0; +} + +char *gsm_check_imei(const char *imei, const char *sv) +{ + int i; + + if (!imei || strlen(imei) != 15) + return "IMEI must have 15 digits!"; + + for (i = 0; i < strlen(imei); i++) { + if (imei[i] < '0' || imei[i] > '9') + return "IMEI must have digits 0 to 9 only!"; + } + + if (!sv || strlen(sv) != 1) + return "Software version must have 1 digit!"; + + if (sv[0] < '0' || sv[0] > '9') + return "Software version must have digits 0 to 9 only!"; + + return NULL; +} + +int gsm_random_imei(struct gsm_settings *set) +{ + int digits = set->imei_random; + char rand[16]; + + if (digits <= 0) + return 0; + if (digits > 15) + digits = 15; + + sprintf(rand, "%08ld", random() % 100000000); + sprintf(rand + 8, "%07ld", random() % 10000000); + + strcpy(set->imei + 15 - digits, rand + 15 - digits); + strncpy(set->imeisv, set->imei, 15); + + return 0; +} + diff --git a/src/host/layer23/src/mobile/subscriber.c b/src/host/layer23/src/mobile/subscriber.c new file mode 100644 index 00000000..cefc8556 --- /dev/null +++ b/src/host/layer23/src/mobile/subscriber.c @@ -0,0 +1,1255 @@ +/* + * (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdint.h> +#include <errno.h> +#include <string.h> +#include <arpa/inet.h> +#include <osmocom/core/talloc.h> +#include <osmocom/gsm/comp128.h> + +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/networks.h> +#include <osmocom/bb/mobile/vty.h> + +/* enable to get an empty list of forbidden PLMNs, even if stored on SIM. + * if list is changed, the result is not written back to SIM */ +//#define TEST_EMPTY_FPLMN + +void *l23_ctx; + +static void subscr_sim_query_cb(struct osmocom_ms *ms, struct msgb *msg); +static void subscr_sim_update_cb(struct osmocom_ms *ms, struct msgb *msg); +static void subscr_sim_key_cb(struct osmocom_ms *ms, struct msgb *msg); + +/* + * support + */ + +char *gsm_check_imsi(const char *imsi) +{ + int i; + + if (!imsi || strlen(imsi) != 15) + return "IMSI must have 15 digits!"; + + for (i = 0; i < strlen(imsi); i++) { + if (imsi[i] < '0' || imsi[i] > '9') + return "IMSI must have digits 0 to 9 only!"; + } + + return NULL; +} + +static char *sim_decode_bcd(uint8_t *data, uint8_t length) +{ + int i, j = 0; + static char result[32], c; + + for (i = 0; i < (length << 1); i++) { + if ((i & 1)) + c = (data[i >> 1] >> 4); + else + c = (data[i >> 1] & 0xf); + if (c == 0xf) + break; + result[j++] = c + '0'; + if (j == sizeof(result) - 1) + break; + } + result[j] = '\0'; + + return result; +} + +static void xor96(uint8_t *ki, uint8_t *rand, uint8_t *sres, uint8_t *kc) +{ + int i; + + for (i=0; i < 4; i++) + sres[i] = rand[i] ^ ki[i]; + for (i=0; i < 8; i++) + kc[i] = rand[i] ^ ki[i+4]; +} + +/* + * init/exit + */ + +int gsm_subscr_init(struct osmocom_ms *ms) +{ + struct gsm_subscriber *subscr = &ms->subscr; + + memset(subscr, 0, sizeof(*subscr)); + subscr->ms = ms; + + /* set TMSI / LAC invalid */ + subscr->tmsi = 0xffffffff; + subscr->lac = 0x0000; + + /* set key invalid */ + subscr->key_seq = 7; + + /* any cell selection timer timeout */ + subscr->any_timeout = 30; + + /* init lists */ + INIT_LLIST_HEAD(&subscr->plmn_list); + INIT_LLIST_HEAD(&subscr->plmn_na); + + /* open SIM */ + subscr->sim_handle_query = sim_open(ms, subscr_sim_query_cb); + subscr->sim_handle_update = sim_open(ms, subscr_sim_update_cb); + subscr->sim_handle_key = sim_open(ms, subscr_sim_key_cb); + + return 0; +} + +int gsm_subscr_exit(struct osmocom_ms *ms) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct llist_head *lh, *lh2; + + if (subscr->sim_handle_query) { + sim_close(ms, subscr->sim_handle_query); + subscr->sim_handle_query = 0; + } + if (subscr->sim_handle_update) { + sim_close(ms, subscr->sim_handle_update); + subscr->sim_handle_update = 0; + } + if (subscr->sim_handle_key) { + sim_close(ms, subscr->sim_handle_key); + subscr->sim_handle_key = 0; + } + + /* flush lists */ + llist_for_each_safe(lh, lh2, &subscr->plmn_list) { + llist_del(lh); + talloc_free(lh); + } + llist_for_each_safe(lh, lh2, &subscr->plmn_na) { + llist_del(lh); + talloc_free(lh); + } + + return 0; +} + +/* + * test card + */ + +/* Attach test card, no SIM must be currently attached */ +int gsm_subscr_testcard(struct osmocom_ms *ms, uint16_t mcc, uint16_t mnc, + uint16_t lac, uint32_t tmsi) +{ + struct gsm_settings *set = &ms->settings; + struct gsm_subscriber *subscr = &ms->subscr; + struct msgb *nmsg; + char *error; + + if (subscr->sim_valid) { + LOGP(DMM, LOGL_ERROR, "Cannot insert card, until current card " + "is detached.\n"); + return -EBUSY; + } + + error = gsm_check_imsi(set->test_imsi); + if (error) { + LOGP(DMM, LOGL_ERROR, "%s\n", error); + return -EINVAL; + } + + /* reset subscriber */ + gsm_subscr_exit(ms); + gsm_subscr_init(ms); + + subscr->sim_type = GSM_SIM_TYPE_TEST; + sprintf(subscr->sim_name, "test"); + subscr->sim_valid = 1; + subscr->ustate = GSM_SIM_U2_NOT_UPDATED; + subscr->acc_barr = set->test_barr; /* we may access barred cell */ + subscr->acc_class = 0xffff; /* we have any access class */ + subscr->plmn_valid = set->test_rplmn_valid; + subscr->plmn_mcc = mcc; + subscr->plmn_mnc = mnc; + subscr->mcc = mcc; + subscr->mnc = mnc; + subscr->lac = lac; + subscr->tmsi = tmsi; + subscr->always_search_hplmn = set->test_always; + subscr->t6m_hplmn = 1; /* try to find home network every 6 min */ + strcpy(subscr->imsi, set->test_imsi); + + LOGP(DMM, LOGL_INFO, "(ms %s) Inserting test card (IMSI=%s %s, %s)\n", + ms->name, subscr->imsi, gsm_imsi_mcc(subscr->imsi), + gsm_imsi_mnc(subscr->imsi)); + + if (subscr->plmn_valid) + LOGP(DMM, LOGL_INFO, "-> Test card registered to %s %s 0x%04x" + "(%s, %s)\n", gsm_print_mcc(mcc), + gsm_print_mnc(mnc), lac, gsm_get_mcc(mcc), + gsm_get_mnc(mcc, mnc)); + else + LOGP(DMM, LOGL_INFO, "-> Test card not registered\n"); + + /* insert card */ + nmsg = gsm48_mmr_msgb_alloc(GSM48_MMR_REG_REQ); + if (!nmsg) + return -ENOMEM; + gsm48_mmr_downmsg(ms, nmsg); + + return 0; +} + +/* + * sim card + */ + +static int subscr_sim_iccid(struct osmocom_ms *ms, uint8_t *data, + uint8_t length) +{ + struct gsm_subscriber *subscr = &ms->subscr; + + strcpy(subscr->iccid, sim_decode_bcd(data, length)); + sprintf(subscr->sim_name, "sim-%s", subscr->iccid); + LOGP(DMM, LOGL_INFO, "received ICCID %s from SIM\n", subscr->iccid); + + return 0; +} + +static int subscr_sim_imsi(struct osmocom_ms *ms, uint8_t *data, + uint8_t length) +{ + struct gsm_subscriber *subscr = &ms->subscr; + char *imsi; + + /* get actual length */ + if (length < 1) + return -EINVAL; + if (data[0] + 1 < length) { + LOGP(DMM, LOGL_NOTICE, "invalid length = %d\n", length); + return -EINVAL; + } + length = data[0]; + + /* decode IMSI, skip first digit (parity) */ + imsi = sim_decode_bcd(data + 1, length); + if (strlen(imsi) - 1 > GSM_IMSI_LENGTH - 1 || strlen(imsi) - 1 < 6) { + LOGP(DMM, LOGL_NOTICE, "IMSI invalid length = %d\n", + strlen(imsi) - 1); + return -EINVAL; + } + + strncpy(subscr->imsi, imsi + 1, sizeof(subscr->imsi) - 1); + + LOGP(DMM, LOGL_INFO, "received IMSI %s from SIM\n", subscr->imsi); + + return 0; +} + +static int subscr_sim_loci(struct osmocom_ms *ms, uint8_t *data, + uint8_t length) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm1111_ef_loci *loci; + + if (length < 11) + return -EINVAL; + loci = (struct gsm1111_ef_loci *) data; + + /* TMSI */ + subscr->tmsi = ntohl(loci->tmsi); + + /* LAI */ + gsm48_decode_lai_hex(&loci->lai, &subscr->mcc, &subscr->mnc, + &subscr->lac); + + /* location update status */ + switch (loci->lupd_status & 0x07) { + case 0x00: + subscr->ustate = GSM_SIM_U1_UPDATED; + break; + case 0x02: + case 0x03: + subscr->ustate = GSM_SIM_U3_ROAMING_NA; + break; + default: + subscr->ustate = GSM_SIM_U2_NOT_UPDATED; + } + + LOGP(DMM, LOGL_INFO, "received LOCI from SIM (mcc=%s mnc=%s lac=0x%04x " + "U%d)\n", gsm_print_mcc(subscr->mcc), + gsm_print_mnc(subscr->mnc), subscr->lac, subscr->ustate); + + return 0; +} + +static int subscr_sim_msisdn(struct osmocom_ms *ms, uint8_t *data, + uint8_t length) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm1111_ef_adn *adn; + + if (length < sizeof(*adn)) + return -EINVAL; + adn = (struct gsm1111_ef_adn *) (data + length - sizeof(*adn)); + + /* empty */ + subscr->msisdn[0] = '\0'; + if (adn->len_bcd <= 1) + return 0; + + /* number */ + if (((adn->ton_npi & 0x70) >> 4) == 1) + strcpy(subscr->msisdn, "+"); + if (((adn->ton_npi & 0x70) >> 4) == 2) + strcpy(subscr->msisdn, "0"); + strncat(subscr->msisdn, sim_decode_bcd(adn->number, adn->len_bcd - 1), + sizeof(subscr->msisdn) - 2); + + LOGP(DMM, LOGL_INFO, "received MSISDN %s from SIM\n", subscr->msisdn); + + return 0; +} + +static int subscr_sim_smsp(struct osmocom_ms *ms, uint8_t *data, + uint8_t length) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm1111_ef_smsp *smsp; + + if (length < sizeof(*smsp)) + return -EINVAL; + smsp = (struct gsm1111_ef_smsp *) (data + length - sizeof(*smsp)); + + /* empty */ + subscr->sms_sca[0] = '\0'; + + /* TS-Service Centre Address */ + if (!(smsp->par_ind & 0x02) && smsp->ts_sca[0] <= 11) { + if (((smsp->ts_sca[1] & 0x70) >> 4) == 1) + strcpy(subscr->sms_sca, "+"); + if (((smsp->ts_sca[1] & 0x70) >> 4) == 2) + strcpy(subscr->sms_sca, "0"); + gsm48_decode_bcd_number(subscr->sms_sca + + strlen(subscr->sms_sca), sizeof(subscr->sms_sca) + - strlen(subscr->sms_sca), smsp->ts_sca, 1); + } + + LOGP(DMM, LOGL_INFO, "received SMSP from SIM (sca=%s)\n", + subscr->sms_sca); + + return 0; +} + +static int subscr_sim_kc(struct osmocom_ms *ms, uint8_t *data, + uint8_t length) +{ + struct gsm_subscriber *subscr = &ms->subscr; + + if (length < 9) + return -EINVAL; + + /* key */ + memcpy(subscr->key, data, 8); + + /* key sequence */ + subscr->key_seq = data[8] & 0x07; + + LOGP(DMM, LOGL_INFO, "received KEY from SIM\n"); + + return 0; +} + +static int subscr_sim_plmnsel(struct osmocom_ms *ms, uint8_t *data, + uint8_t length) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm_sub_plmn_list *plmn; + struct llist_head *lh, *lh2; + uint8_t lai[5]; + uint16_t dummy_lac; + + /* flush list */ + llist_for_each_safe(lh, lh2, &subscr->plmn_list) { + llist_del(lh); + talloc_free(lh); + } + + while(length >= 3) { + /* end of list inside mandatory fields */ + if (data[0] == 0xff && data[1] == 0xff && data[2] == 0x0ff) + break; + + /* add to list */ + plmn = talloc_zero(l23_ctx, struct gsm_sub_plmn_list); + if (!plmn) + return -ENOMEM; + lai[0] = data[0]; + lai[1] = data[1]; + lai[2] = data[2]; + gsm48_decode_lai_hex((struct gsm48_loc_area_id *)lai, + &plmn->mcc, &plmn->mnc, &dummy_lac); + llist_add_tail(&plmn->entry, &subscr->plmn_list); + + LOGP(DMM, LOGL_INFO, "received PLMN selector (mcc=%s mnc=%s) " + "from SIM\n", + gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc)); + + data += 3; + length -= 3; + } + + return 0; +} + +static int subscr_sim_hpplmn(struct osmocom_ms *ms, uint8_t *data, + uint8_t length) +{ + struct gsm_subscriber *subscr = &ms->subscr; + + if (length < 1) + return -EINVAL; + + /* HPLMN search interval */ + subscr->t6m_hplmn = *data; /* multiple of 6 minutes */ + + LOGP(DMM, LOGL_INFO, "received HPPLMN %d (%d mins) from SIM\n", + subscr->t6m_hplmn, subscr->t6m_hplmn * 6); + + return 0; +} + +static int subscr_sim_spn(struct osmocom_ms *ms, uint8_t *data, + uint8_t length) +{ + struct gsm_subscriber *subscr = &ms->subscr; + int i; + + /* UCS2 code not supported */ + if (length < 17 || data[1] >= 0x80) + return -ENOTSUP; + + data++; + for (i = 0; i < 16; i++) { + if (*data == 0xff) + break; + subscr->sim_spn[i] = *data++; + } + subscr->sim_spn[i] = '\0'; + + LOGP(DMM, LOGL_INFO, "received SPN %s from SIM\n", subscr->sim_spn); + + return 0; +} + +static int subscr_sim_acc(struct osmocom_ms *ms, uint8_t *data, + uint8_t length) +{ + struct gsm_subscriber *subscr = &ms->subscr; + uint16_t ac; + + if (length < 2) + return -EINVAL; + + /* cell access */ + memcpy(&ac, data, sizeof(ac)); + subscr->acc_class = ntohs(ac); + + LOGP(DMM, LOGL_INFO, "received ACC %04x from SIM\n", subscr->acc_class); + + return 0; +} + +static int subscr_sim_fplmn(struct osmocom_ms *ms, uint8_t *data, + uint8_t length) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm_sub_plmn_na *na; + struct llist_head *lh, *lh2; + uint8_t lai[5]; + uint16_t dummy_lac; + +#ifdef TEST_EMPTY_FPLMN + return 0; +#endif + + /* flush list */ + llist_for_each_safe(lh, lh2, &subscr->plmn_na) { + llist_del(lh); + talloc_free(lh); + } + + while (length >= 3) { + /* end of list inside mandatory fields */ + if (data[0] == 0xff && data[1] == 0xff && data[2] == 0x0ff) + break; + + /* add to list */ + na = talloc_zero(l23_ctx, struct gsm_sub_plmn_na); + if (!na) + return -ENOMEM; + lai[0] = data[0]; + lai[1] = data[1]; + lai[2] = data[2]; + gsm48_decode_lai_hex((struct gsm48_loc_area_id *)lai, &na->mcc, + &na->mnc, &dummy_lac); + LOGP(DMM, LOGL_INFO, "received Forbidden PLMN %s %s from SIM\n", + gsm_print_mcc(na->mcc), gsm_print_mnc(na->mnc)); + na->cause = -1; /* must have a value, but SIM stores no cause */ + llist_add_tail(&na->entry, &subscr->plmn_na); + + data += 3; + length -= 3; + } + return 0; +} + +static struct subscr_sim_file { + uint8_t mandatory; + uint16_t path[MAX_SIM_PATH_LENGTH]; + uint16_t file; + uint8_t sim_job; + int (*func)(struct osmocom_ms *ms, uint8_t *data, + uint8_t length); +} subscr_sim_files[] = { + { 1, { 0 }, 0x2fe2, SIM_JOB_READ_BINARY, subscr_sim_iccid }, + { 1, { 0x7f20, 0 }, 0x6f07, SIM_JOB_READ_BINARY, subscr_sim_imsi }, + { 1, { 0x7f20, 0 }, 0x6f7e, SIM_JOB_READ_BINARY, subscr_sim_loci }, + { 0, { 0x7f20, 0 }, 0x6f20, SIM_JOB_READ_BINARY, subscr_sim_kc }, + { 0, { 0x7f20, 0 }, 0x6f30, SIM_JOB_READ_BINARY, subscr_sim_plmnsel }, + { 0, { 0x7f20, 0 }, 0x6f31, SIM_JOB_READ_BINARY, subscr_sim_hpplmn }, + { 0, { 0x7f20, 0 }, 0x6f46, SIM_JOB_READ_BINARY, subscr_sim_spn }, + { 0, { 0x7f20, 0 }, 0x6f78, SIM_JOB_READ_BINARY, subscr_sim_acc }, + { 0, { 0x7f20, 0 }, 0x6f7b, SIM_JOB_READ_BINARY, subscr_sim_fplmn }, + { 0, { 0x7f10, 0 }, 0x6f40, SIM_JOB_READ_RECORD, subscr_sim_msisdn }, + { 0, { 0x7f10, 0 }, 0x6f42, SIM_JOB_READ_RECORD, subscr_sim_smsp }, + { 0, { 0 }, 0, 0, NULL } +}; + +/* request file from SIM */ +static int subscr_sim_request(struct osmocom_ms *ms) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct subscr_sim_file *sf = &subscr_sim_files[subscr->sim_file_index]; + struct msgb *nmsg; + struct sim_hdr *nsh; + int i; + + /* we are done, fire up PLMN and cell selection process */ + if (!sf->func) { + LOGP(DMM, LOGL_INFO, "(ms %s) Done reading SIM card " + "(IMSI=%s %s, %s)\n", ms->name, subscr->imsi, + gsm_imsi_mcc(subscr->imsi), gsm_imsi_mnc(subscr->imsi)); + + /* if LAI is valid, set RPLMN */ + if (subscr->lac > 0x0000 && subscr->lac < 0xfffe) { + subscr->plmn_valid = 1; + subscr->plmn_mcc = subscr->mcc; + subscr->plmn_mnc = subscr->mnc; + LOGP(DMM, LOGL_INFO, "-> SIM card registered to %s %s " + "(%s, %s)\n", gsm_print_mcc(subscr->plmn_mcc), + gsm_print_mnc(subscr->plmn_mnc), + gsm_get_mcc(subscr->plmn_mcc), + gsm_get_mnc(subscr->plmn_mcc, + subscr->plmn_mnc)); + } else + LOGP(DMM, LOGL_INFO, "-> SIM card not registered\n"); + + /* insert card */ + nmsg = gsm48_mmr_msgb_alloc(GSM48_MMR_REG_REQ); + if (!nmsg) + return -ENOMEM; + gsm48_mmr_downmsg(ms, nmsg); + + return 0; + } + + /* trigger SIM reading */ + nmsg = gsm_sim_msgb_alloc(subscr->sim_handle_query, + sf->sim_job); + if (!nmsg) + return -ENOMEM; + nsh = (struct sim_hdr *) nmsg->data; + i = 0; + while (sf->path[i]) { + nsh->path[i] = sf->path[i]; + i++; + } + nsh->path[i] = 0; /* end of path */ + nsh->file = sf->file; + nsh->rec_no = 1; + nsh->rec_mode = 0x04; + LOGP(DMM, LOGL_INFO, "Requesting SIM file 0x%04x\n", nsh->file); + sim_job(ms, nmsg); + + return 0; +} + +static void subscr_sim_query_cb(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct sim_hdr *sh = (struct sim_hdr *) msg->data; + uint8_t *payload = msg->data + sizeof(*sh); + uint16_t payload_len = msg->len - sizeof(*sh); + int rc; + struct subscr_sim_file *sf = &subscr_sim_files[subscr->sim_file_index]; + struct msgb *nmsg; + + /* error handling */ + if (sh->job_type == SIM_JOB_ERROR) { + uint8_t cause = payload[0]; + + switch (cause) { + /* unlocking required */ + case SIM_CAUSE_PIN1_REQUIRED: + LOGP(DMM, LOGL_INFO, "PIN is required, %d tries left\n", + payload[1]); + + vty_notify(ms, NULL); + vty_notify(ms, "Please give PIN for ICCID %s (you have " + "%d tries left)\n", subscr->iccid, payload[1]); + subscr->sim_pin_required = 1; + break; + case SIM_CAUSE_PIN1_BLOCKED: + LOGP(DMM, LOGL_NOTICE, "PIN is blocked\n"); + + vty_notify(ms, NULL); + vty_notify(ms, "PIN is blocked\n"); + if (payload[1]) { + vty_notify(ms, "Please give PUC for ICCID %s " + "(you have %d tries left)\n", + subscr->iccid, payload[1]); + } + subscr->sim_pin_required = 1; + break; + case SIM_CAUSE_PUC_BLOCKED: + LOGP(DMM, LOGL_NOTICE, "PUC is blocked\n"); + + vty_notify(ms, NULL); + vty_notify(ms, "PUC is blocked\n"); + subscr->sim_pin_required = 1; + break; + default: + if (sf->func && !sf->mandatory) { + LOGP(DMM, LOGL_NOTICE, "SIM reading failed, " + "ignoring!\n"); + goto ignore; + } + LOGP(DMM, LOGL_NOTICE, "SIM reading failed\n"); + + vty_notify(ms, NULL); + vty_notify(ms, "SIM failed, replace SIM!\n"); + + /* detach simcard */ + subscr->sim_valid = 0; + nmsg = gsm48_mmr_msgb_alloc(GSM48_MMR_NREG_REQ); + if (!nmsg) + return; + gsm48_mmr_downmsg(ms, nmsg); + } + msgb_free(msg); + + return; + } + + /* if pin was successfully unlocked, then resend request */ + if (subscr->sim_pin_required) { + subscr->sim_pin_required = 0; + subscr_sim_request(ms); + return; + } + + /* done when nothing more to read. this happens on PIN requests */ + if (!sf->func) + return; + + /* call function do decode SIM reply */ + rc = sf->func(ms, payload, payload_len); + if (rc) { + LOGP(DMM, LOGL_NOTICE, "SIM reading failed, file invalid\n"); + if (subscr_sim_files[subscr->sim_file_index].mandatory) { + vty_notify(ms, NULL); + vty_notify(ms, "SIM failed, data invalid, replace " + "SIM!\n"); + msgb_free(msg); + + return; + } + } + +ignore: + msgb_free(msg); + + /* trigger next file */ + subscr->sim_file_index++; + subscr_sim_request(ms); +} + +/* enter PIN */ +void gsm_subscr_sim_pin(struct osmocom_ms *ms, char *pin1, char *pin2, + int8_t mode) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct msgb *nmsg; + uint8_t job; + + /* skip, if no real valid SIM */ + if (subscr->sim_type != GSM_SIM_TYPE_READER) + return; + + switch (mode) { + case -1: + job = SIM_JOB_PIN1_DISABLE; + LOGP(DMM, LOGL_INFO, "disabling PIN %s\n", pin1); + break; + case 1: + job = SIM_JOB_PIN1_ENABLE; + LOGP(DMM, LOGL_INFO, "enabling PIN %s\n", pin1); + break; + case 2: + job = SIM_JOB_PIN1_CHANGE; + LOGP(DMM, LOGL_INFO, "changing PIN %s to %s\n", pin1, pin2); + break; + case 99: + job = SIM_JOB_PIN1_UNBLOCK; + LOGP(DMM, LOGL_INFO, "unblocking PIN %s with PUC %s\n", pin1, + pin2); + break; + default: + if (!subscr->sim_pin_required) { + LOGP(DMM, LOGL_ERROR, "No PIN required now\n"); + return; + } + LOGP(DMM, LOGL_INFO, "entering PIN %s\n", pin1); + job = SIM_JOB_PIN1_UNLOCK; + } + + nmsg = gsm_sim_msgb_alloc(subscr->sim_handle_query, job); + if (!nmsg) + return; + memcpy(msgb_put(nmsg, strlen(pin1) + 1), pin1, strlen(pin1) + 1); + memcpy(msgb_put(nmsg, strlen(pin2) + 1), pin2, strlen(pin2) + 1); + sim_job(ms, nmsg); +} + +/* Attach SIM reader, no SIM must be currently attached */ +int gsm_subscr_simcard(struct osmocom_ms *ms) +{ + struct gsm_subscriber *subscr = &ms->subscr; + + if (subscr->sim_valid) { + LOGP(DMM, LOGL_ERROR, "Cannot attach card, until current card " + "is detached.\n"); + return -EBUSY; + } + + /* reset subscriber */ + gsm_subscr_exit(ms); + gsm_subscr_init(ms); + + subscr->sim_type = GSM_SIM_TYPE_READER; + sprintf(subscr->sim_name, "sim"); + subscr->sim_valid = 1; + subscr->ustate = GSM_SIM_U2_NOT_UPDATED; + + /* start with first index */ + subscr->sim_file_index = 0; + return subscr_sim_request(ms); +} + +/* update plmn not allowed list on SIM */ +static int subscr_write_plmn_na(struct osmocom_ms *ms) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct msgb *nmsg; + struct sim_hdr *nsh; + struct gsm_sub_plmn_na *na, *nas[4] = { NULL, NULL, NULL, NULL }; + int count = 0, i; + uint8_t *data; + uint8_t lai[5]; + +#ifdef TEST_EMPTY_FPLMN + return 0; +#endif + + /* skip, if no real valid SIM */ + if (subscr->sim_type != GSM_SIM_TYPE_READER || !subscr->sim_valid) + return 0; + + /* get tail list from "PLMN not allowed" */ + llist_for_each_entry(na, &subscr->plmn_na, entry) { + if (count < 4) + nas[count] = na; + else { + nas[0] = nas[1]; + nas[1] = nas[2]; + nas[2] = nas[3]; + nas[3] = na; + } + count++; + } + + /* write to SIM */ + LOGP(DMM, LOGL_INFO, "Updating FPLMN on SIM\n"); + nmsg = gsm_sim_msgb_alloc(subscr->sim_handle_update, + SIM_JOB_UPDATE_BINARY); + if (!nmsg) + return -ENOMEM; + nsh = (struct sim_hdr *) nmsg->data; + data = msgb_put(nmsg, 12); + nsh->path[0] = 0x7f20; + nsh->path[1] = 0; + nsh->file = 0x6f7b; + for (i = 0; i < 4; i++) { + if (nas[i]) { + gsm48_encode_lai_hex((struct gsm48_loc_area_id *)lai, + nas[i]->mcc, nas[i]->mnc, 0); + *data++ = lai[0]; + *data++ = lai[1]; + *data++ = lai[2]; + } else { + *data++ = 0xff; + *data++ = 0xff; + *data++ = 0xff; + } + } + sim_job(ms, nmsg); + + return 0; +} + +/* update LOCI on SIM */ +int gsm_subscr_write_loci(struct osmocom_ms *ms) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct msgb *nmsg; + struct sim_hdr *nsh; + struct gsm1111_ef_loci *loci; + + /* skip, if no real valid SIM */ + if (subscr->sim_type != GSM_SIM_TYPE_READER || !subscr->sim_valid) + return 0; + + LOGP(DMM, LOGL_INFO, "Updating LOCI on SIM\n"); + + /* write to SIM */ + nmsg = gsm_sim_msgb_alloc(subscr->sim_handle_update, + SIM_JOB_UPDATE_BINARY); + if (!nmsg) + return -ENOMEM; + nsh = (struct sim_hdr *) nmsg->data; + nsh->path[0] = 0x7f20; + nsh->path[1] = 0; + nsh->file = 0x6f7e; + loci = (struct gsm1111_ef_loci *)msgb_put(nmsg, sizeof(*loci)); + + /* TMSI */ + loci->tmsi = htonl(subscr->tmsi); + + /* LAI */ + gsm48_encode_lai_hex(&loci->lai, subscr->mcc, subscr->mnc, subscr->lac); + + /* TMSI time */ + loci->tmsi_time = 0xff; + + /* location update status */ + switch (subscr->ustate) { + case GSM_SIM_U1_UPDATED: + loci->lupd_status = 0x00; + break; + case GSM_SIM_U3_ROAMING_NA: + loci->lupd_status = 0x03; + break; + default: + loci->lupd_status = 0x01; + } + + sim_job(ms, nmsg); + + return 0; +} + +static void subscr_sim_update_cb(struct osmocom_ms *ms, struct msgb *msg) +{ + struct sim_hdr *sh = (struct sim_hdr *) msg->data; + uint8_t *payload = msg->data + sizeof(*sh); + + /* error handling */ + if (sh->job_type == SIM_JOB_ERROR) + LOGP(DMM, LOGL_NOTICE, "SIM update failed (cause %d)\n", + *payload); + + msgb_free(msg); +} + +int gsm_subscr_generate_kc(struct osmocom_ms *ms, uint8_t key_seq, + uint8_t *rand, uint8_t no_sim) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct msgb *nmsg; + struct sim_hdr *nsh; + + /* not a SIM */ + if ((subscr->sim_type != GSM_SIM_TYPE_READER + && subscr->sim_type != GSM_SIM_TYPE_TEST) + || !subscr->sim_valid || no_sim) { + struct gsm48_mm_event *nmme; + + LOGP(DMM, LOGL_INFO, "Sending dummy authentication response\n"); + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_AUTH_RESPONSE); + if (!nmsg) + return -ENOMEM; + nmme = (struct gsm48_mm_event *) nmsg->data; + nmme->sres[0] = 0x12; + nmme->sres[1] = 0x34; + nmme->sres[2] = 0x56; + nmme->sres[3] = 0x78; + gsm48_mmevent_msg(ms, nmsg); + + return 0; + } + + /* test SIM */ + if (subscr->sim_type == GSM_SIM_TYPE_TEST) { + struct gsm48_mm_event *nmme; + uint8_t sres[4]; + struct gsm_settings *set = &ms->settings; + + if (set->test_ki_type == GSM_SIM_KEY_COMP128) + comp128(set->test_ki, rand, sres, subscr->key); + else + xor96(set->test_ki, rand, sres, subscr->key); + /* store sequence */ + subscr->key_seq = key_seq; + + LOGP(DMM, LOGL_INFO, "Sending authentication response\n"); + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_AUTH_RESPONSE); + if (!nmsg) + return -ENOMEM; + nmme = (struct gsm48_mm_event *) nmsg->data; + memcpy(nmme->sres, sres, 4); + gsm48_mmevent_msg(ms, nmsg); + + return 0; + } + + LOGP(DMM, LOGL_INFO, "Generating KEY at SIM\n"); + + /* command to SIM */ + nmsg = gsm_sim_msgb_alloc(subscr->sim_handle_key, SIM_JOB_RUN_GSM_ALGO); + if (!nmsg) + return -ENOMEM; + nsh = (struct sim_hdr *) nmsg->data; + nsh->path[0] = 0x7f20; + nsh->path[1] = 0; + + /* random */ + memcpy(msgb_put(nmsg, 16), rand, 16); + + /* store sequence */ + subscr->key_seq = key_seq; + + sim_job(ms, nmsg); + + return 0; +} + +static void subscr_sim_key_cb(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct sim_hdr *sh = (struct sim_hdr *) msg->data; + uint8_t *payload = msg->data + sizeof(*sh); + uint16_t payload_len = msg->len - sizeof(*sh); + struct msgb *nmsg; + struct sim_hdr *nsh; + struct gsm48_mm_event *nmme; + uint8_t *data; + + /* error handling */ + if (sh->job_type == SIM_JOB_ERROR) { + LOGP(DMM, LOGL_NOTICE, "key generation on SIM failed " + "(cause %d)\n", *payload); + + msgb_free(msg); + + return; + } + + if (payload_len < 12) { + LOGP(DMM, LOGL_NOTICE, "response from SIM too short\n"); + return; + } + + /* store key */ + memcpy(subscr->key, payload + 4, 8); + + /* write to SIM */ + LOGP(DMM, LOGL_INFO, "Updating KC on SIM\n"); + nmsg = gsm_sim_msgb_alloc(subscr->sim_handle_update, + SIM_JOB_UPDATE_BINARY); + if (!nmsg) + return; + nsh = (struct sim_hdr *) nmsg->data; + nsh->path[0] = 0x7f20; + nsh->path[1] = 0; + nsh->file = 0x6f20; + data = msgb_put(nmsg, 9); + memcpy(data, subscr->key, 8); + data[8] = subscr->key_seq; + sim_job(ms, nmsg); + + /* return signed response */ + nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_AUTH_RESPONSE); + if (!nmsg) + return; + nmme = (struct gsm48_mm_event *) nmsg->data; + memcpy(nmme->sres, payload, 4); + gsm48_mmevent_msg(ms, nmsg); + + msgb_free(msg); +} + +/* + * detach + */ + +/* Detach card */ +int gsm_subscr_remove(struct osmocom_ms *ms) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct msgb *nmsg; + + if (!subscr->sim_valid) { + LOGP(DMM, LOGL_ERROR, "Cannot remove card, no card present\n"); + return -EINVAL; + } + + /* remove card */ + nmsg = gsm48_mmr_msgb_alloc(GSM48_MMR_NREG_REQ); + if (!nmsg) + return -ENOMEM; + gsm48_mmr_downmsg(ms, nmsg); + + return 0; +} + +/* + * state and lists + */ + +static const char *subscr_ustate_names[] = { + "U0_NULL", + "U1_UPDATED", + "U2_NOT_UPDATED", + "U3_ROAMING_NA" +}; + +/* change to new U state */ +void new_sim_ustate(struct gsm_subscriber *subscr, int state) +{ + LOGP(DMM, LOGL_INFO, "(ms %s) new state %s -> %s\n", subscr->ms->name, + subscr_ustate_names[subscr->ustate], + subscr_ustate_names[state]); + + subscr->ustate = state; +} + +/* del forbidden PLMN. if MCC==0, flush complete list */ +int gsm_subscr_del_forbidden_plmn(struct gsm_subscriber *subscr, uint16_t mcc, + uint16_t mnc) +{ + struct gsm_sub_plmn_na *na, *na2; + int deleted = 0; + + llist_for_each_entry_safe(na, na2, &subscr->plmn_na, entry) { + if (!mcc || (na->mcc == mcc && na->mnc == mnc)) { + LOGP(DPLMN, LOGL_INFO, "Delete from list of forbidden " + "PLMNs (mcc=%s, mnc=%s)\n", + gsm_print_mcc(mcc), gsm_print_mnc(mnc)); + llist_del(&na->entry); + talloc_free(na); + deleted = 1; + if (mcc) + break; + } + } + + if (deleted) { + /* update plmn not allowed list on SIM */ + subscr_write_plmn_na(subscr->ms); + } + + return -EINVAL; +} + +/* add forbidden PLMN */ +int gsm_subscr_add_forbidden_plmn(struct gsm_subscriber *subscr, uint16_t mcc, + uint16_t mnc, uint8_t cause) +{ + struct gsm_sub_plmn_na *na; + + /* if already in the list, remove and add to tail */ + gsm_subscr_del_forbidden_plmn(subscr, mcc, mnc); + + LOGP(DPLMN, LOGL_INFO, "Add to list of forbidden PLMNs " + "(mcc=%s, mnc=%s)\n", gsm_print_mcc(mcc), gsm_print_mnc(mnc)); + na = talloc_zero(l23_ctx, struct gsm_sub_plmn_na); + if (!na) + return -ENOMEM; + na->mcc = mcc; + na->mnc = mnc; + na->cause = cause ? : -1; /* cause 0 is not allowed */ + llist_add_tail(&na->entry, &subscr->plmn_na); + + /* don't add Home PLMN to SIM */ + if (subscr->sim_valid && gsm_match_mnc(mcc, mnc, subscr->imsi)) + return -EINVAL; + + /* update plmn not allowed list on SIM */ + subscr_write_plmn_na(subscr->ms); + + return 0; +} + +/* search forbidden PLMN */ +int gsm_subscr_is_forbidden_plmn(struct gsm_subscriber *subscr, uint16_t mcc, + uint16_t mnc) +{ + struct gsm_sub_plmn_na *na; + + llist_for_each_entry(na, &subscr->plmn_na, entry) { + if (na->mcc == mcc && na->mnc == mnc) + return 1; + } + + return 0; +} + +int gsm_subscr_get_key_seq(struct osmocom_ms *ms, struct gsm_subscriber *subscr) +{ + if (ms->settings.force_rekey) + return 7; + else + return subscr->key_seq; +} + +int gsm_subscr_dump_forbidden_plmn(struct osmocom_ms *ms, + void (*print)(void *, const char *, ...), void *priv) +{ + struct gsm_subscriber *subscr = &ms->subscr; + struct gsm_sub_plmn_na *temp; + + print(priv, "MCC |MNC |cause\n"); + print(priv, "-------+-------+-------\n"); + llist_for_each_entry(temp, &subscr->plmn_na, entry) + print(priv, "%s |%s%s |#%d\n", + gsm_print_mcc(temp->mcc), gsm_print_mnc(temp->mnc), + ((temp->mnc & 0x00f) == 0x00f) ? " ":"", temp->cause); + + return 0; +} + +/* dump subscriber */ +void gsm_subscr_dump(struct gsm_subscriber *subscr, + void (*print)(void *, const char *, ...), void *priv) +{ + int i; + struct gsm_sub_plmn_list *plmn_list; + struct gsm_sub_plmn_na *plmn_na; + + print(priv, "Mobile Subscriber of MS '%s':\n", subscr->ms->name); + + if (!subscr->sim_valid) { + print(priv, " No SIM present.\n"); + return; + } + + print(priv, " IMSI: %s\n", subscr->imsi); + if (subscr->iccid[0]) + print(priv, " ICCID: %s\n", subscr->iccid); + if (subscr->sim_spn[0]) + print(priv, " Service Provider Name: %s\n", subscr->sim_spn); + if (subscr->msisdn[0]) + print(priv, " MSISDN: %s\n", subscr->msisdn); + if (subscr->sms_sca[0]) + print(priv, " SMS Service Center Address: %s\n", + subscr->sms_sca); + print(priv, " Status: %s IMSI %s", subscr_ustate_names[subscr->ustate], + (subscr->imsi_attached) ? "attached" : "detached"); + if (subscr->tmsi != 0xffffffff) + print(priv, " TSMI 0x%08x", subscr->tmsi); + if (subscr->lac > 0x0000 && subscr->lac < 0xfffe) { + print(priv, "\n"); + print(priv, " LAI: MCC %s MNC %s LAC 0x%04x " + "(%s, %s)\n", gsm_print_mcc(subscr->mcc), + gsm_print_mnc(subscr->mnc), subscr->lac, + gsm_get_mcc(subscr->mcc), + gsm_get_mnc(subscr->mcc, subscr->mnc)); + } else + print(priv, " LAI: invalid\n"); + if (subscr->key_seq != 7) { + print(priv, " Key: sequence %d ", subscr->key_seq); + for (i = 0; i < sizeof(subscr->key); i++) + print(priv, " %02x", subscr->key[i]); + print(priv, "\n"); + } + if (subscr->plmn_valid) + print(priv, " Registered PLMN: MCC %s MNC %s (%s, %s)\n", + gsm_print_mcc(subscr->plmn_mcc), + gsm_print_mnc(subscr->plmn_mnc), + gsm_get_mcc(subscr->plmn_mcc), + gsm_get_mnc(subscr->plmn_mcc, subscr->plmn_mnc)); + print(priv, " Access barred cells: %s\n", + (subscr->acc_barr) ? "yes" : "no"); + print(priv, " Access classes:"); + for (i = 0; i < 16; i++) + if ((subscr->acc_class & (1 << i))) + print(priv, " C%d", i); + print(priv, "\n"); + if (!llist_empty(&subscr->plmn_list)) { + print(priv, " List of preferred PLMNs:\n"); + print(priv, " MCC |MNC\n"); + print(priv, " -------+-------\n"); + llist_for_each_entry(plmn_list, &subscr->plmn_list, entry) + print(priv, " %s |%s (%s, %s)\n", + gsm_print_mcc(plmn_list->mcc), + gsm_print_mnc(plmn_list->mnc), + gsm_get_mcc(plmn_list->mcc), + gsm_get_mnc(plmn_list->mcc, plmn_list->mnc)); + } + if (!llist_empty(&subscr->plmn_na)) { + print(priv, " List of forbidden PLMNs:\n"); + print(priv, " MCC |MNC |cause\n"); + print(priv, " -------+-------+-------\n"); + llist_for_each_entry(plmn_na, &subscr->plmn_na, entry) + print(priv, " %s |%s%s |#%d " + "(%s, %s)\n", gsm_print_mcc(plmn_na->mcc), + gsm_print_mnc(plmn_na->mnc), + ((plmn_na->mnc & 0x00f) == 0x00f) ? " ":"", + plmn_na->cause, gsm_get_mcc(plmn_na->mcc), + gsm_get_mnc(plmn_na->mcc, plmn_na->mnc)); + } +} + diff --git a/src/host/layer23/src/mobile/support.c b/src/host/layer23/src/mobile/support.c new file mode 100644 index 00000000..bfc61805 --- /dev/null +++ b/src/host/layer23/src/mobile/support.c @@ -0,0 +1,182 @@ +/* + * (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdio.h> +#include <stdint.h> +#include <string.h> + +#include <osmocom/bb/common/osmocom_data.h> + +void gsm_support_init(struct osmocom_ms *ms) +{ + struct gsm_support *sup = &ms->support; + + memset(sup, 0, sizeof(*sup)); + sup->ms = ms; + + /* controlled early classmark sending */ + sup->es_ind = 0; /* no */ + /* revision level */ + sup->rev_lev = 1; /* phase 2 mobile station */ + /* support of VGCS */ + sup->vgcs = 0; /* no */ + /* support of VBS */ + sup->vbs = 0; /* no */ + /* support of SMS */ + sup->sms_ptp = 1; /* no */ + /* screening indicator */ + sup->ss_ind = 1; /* phase 2 error handling */ + /* pseudo synchronised capability */ + sup->ps_cap = 0; /* no */ + /* CM service prompt */ + sup->cmsp = 0; /* no */ + /* solsa support */ + sup->solsa = 0; /* no */ + /* location service support */ + sup->lcsva = 0; /* no */ + sup->loc_serv = 0; /* no */ + /* codec supprot */ + sup->a5_1 = 1; + sup->a5_2 = 1; + sup->a5_3 = 0; + sup->a5_4 = 0; + sup->a5_5 = 0; + sup->a5_6 = 0; + sup->a5_7 = 0; + /* radio support */ + sup->p_gsm = 1; /* P-GSM */ + sup->e_gsm = 1; /* E-GSM */ + sup->r_gsm = 1; /* R-GSM */ + sup->dcs = 1; + sup->gsm_850 = 1; + sup->pcs = 1; + sup->gsm_480 = 0; + sup->gsm_450 = 0; + /* rf power capability */ + sup->class_900 = 4; /* CLASS 4: Handheld 2W */ + sup->class_850 = 4; + sup->class_400 = 4; + sup->class_dcs = 1; /* CLASS 1: Handheld 1W */ + sup->class_pcs = 1; + /* multi slot support */ + sup->ms_sup = 0; /* no */ + /* ucs2 treatment */ + sup->ucs2_treat = 0; /* default */ + /* support extended measurements */ + sup->ext_meas = 0; /* no */ + /* support switched measurement capability */ + sup->meas_cap = 0; /* no */ + //sup->sms_val = ; + //sup->sm_val = ; + + /* radio */ + sup->ch_cap = GSM_CAP_SDCCH_TCHF_TCHH; + sup->min_rxlev_db = -106; // TODO + sup->sync_to = 6; /* how long to wait sync (0.9 s) */ + sup->scan_to = 4; /* how long to wait for all sysinfos (>=4 s) */ + sup->dsc_max = 90; /* the specs defines 90 */ + + /* codec */ + sup->full_v1 = 1; + sup->full_v2 = 1; + sup->full_v3 = 0; + sup->half_v1 = 1; + sup->half_v3 = 0; +} + +/* (3.2.1) maximum channels to scan within each band */ +struct gsm_support_scan_max gsm_sup_smax[] = { + { 259, 293, 15, 0 }, /* GSM 450 */ + { 306, 340, 15, 0 }, /* GSM 480 */ + { 438, 511, 25, 0 }, + { 128, 251, 30, 0 }, /* GSM 850 */ + { 955, 124, 30, 0 }, /* P,E,R GSM */ + { 512, 885, 40, 0 }, /* DCS 1800 */ + { 1024, 1322, 40, 0 }, /* PCS 1900 */ + { 0, 0, 0, 0 } +}; + +#define SUP_SET(item) \ + ((sup->item) ? ((set->item) ? "yes" : "disabled") : "no") +/* dump support */ +void gsm_support_dump(struct osmocom_ms *ms, + void (*print)(void *, const char *, ...), void *priv) +{ + struct gsm_support *sup = &ms->support; + struct gsm_settings *set = &ms->settings; + + print(priv, "Supported features of MS '%s':\n", sup->ms->name); + print(priv, " Phase %d mobile station\n", sup->rev_lev + 1); + print(priv, " R-GSM : %s\n", SUP_SET(r_gsm)); + print(priv, " E-GSM : %s\n", SUP_SET(e_gsm)); + print(priv, " P-GSM : %s\n", SUP_SET(p_gsm)); + if (set->r_gsm || set->e_gsm || set->p_gsm) + print(priv, " GSM900 Class : %d\n", set->class_900); + print(priv, " DCS 1800 : %s\n", SUP_SET(dcs)); + if (set->dcs) + print(priv, " DCS Class : %d\n", set->class_dcs); + print(priv, " GSM 850 : %s\n", SUP_SET(gsm_850)); + if (set->gsm_850) + print(priv, " GSM 850 Class: %d\n", set->class_850); + print(priv, " PCS 1900 : %s\n", SUP_SET(pcs)); + if (set->pcs) + print(priv, " PCS Class : %d\n", set->class_pcs); + print(priv, " GSM 480 : %s\n", SUP_SET(gsm_480)); + print(priv, " GSM 450 : %s\n", SUP_SET(gsm_450)); + if (set->gsm_480 | set->gsm_450) + print(priv, " GSM 400 Class: %d\n", set->class_400); + print(priv, " CECS : %s\n", (sup->es_ind) ? "yes" : "no"); + print(priv, " VGCS : %s\n", (sup->vgcs) ? "yes" : "no"); + print(priv, " VBS : %s\n", (sup->vbs) ? "yes" : "no"); + print(priv, " SMS : %s\n", SUP_SET(sms_ptp)); + print(priv, " SS_IND : %s\n", (sup->ss_ind) ? "yes" : "no"); + print(priv, " PS_CAP : %s\n", (sup->ps_cap) ? "yes" : "no"); + print(priv, " CMSP : %s\n", (sup->cmsp) ? "yes" : "no"); + print(priv, " SoLSA : %s\n", (sup->solsa) ? "yes" : "no"); + print(priv, " LCSVA : %s\n", (sup->lcsva) ? "yes" : "no"); + print(priv, " LOC_SERV : %s\n", (sup->loc_serv) ? "yes" : "no"); + print(priv, " A5/1 : %s\n", SUP_SET(a5_1)); + print(priv, " A5/2 : %s\n", SUP_SET(a5_2)); + print(priv, " A5/3 : %s\n", SUP_SET(a5_3)); + print(priv, " A5/4 : %s\n", SUP_SET(a5_4)); + print(priv, " A5/5 : %s\n", SUP_SET(a5_5)); + print(priv, " A5/6 : %s\n", SUP_SET(a5_6)); + print(priv, " A5/7 : %s\n", SUP_SET(a5_7)); + print(priv, " A5/1 : %s\n", SUP_SET(a5_1)); + switch (set->ch_cap) { + case GSM_CAP_SDCCH: + print(priv, " Channels : SDCCH only\n"); + break; + case GSM_CAP_SDCCH_TCHF: + print(priv, " Channels : SDCCH + TCH/F\n"); + break; + case GSM_CAP_SDCCH_TCHF_TCHH: + print(priv, " Channels : SDCCH + TCH/F + TCH/H\n"); + break; + } + print(priv, " Full-Rate V1 : %s\n", SUP_SET(full_v1)); + print(priv, " Full-Rate V2 : %s\n", SUP_SET(full_v2)); + print(priv, " Full-Rate V3 : %s\n", SUP_SET(full_v3)); + print(priv, " Half-Rate V1 : %s\n", SUP_SET(half_v1)); + print(priv, " Half-Rate V3 : %s\n", SUP_SET(half_v3)); + print(priv, " Min RXLEV : %d\n", set->min_rxlev_db); +} + diff --git a/src/host/layer23/src/mobile/transaction.c b/src/host/layer23/src/mobile/transaction.c new file mode 100644 index 00000000..45bf2b41 --- /dev/null +++ b/src/host/layer23/src/mobile/transaction.c @@ -0,0 +1,143 @@ +/* GSM 04.07 Transaction handling */ + +/* (C) 2009 by Harald Welte <laforge@gnumonks.org> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdint.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/mobile/mncc.h> +#include <osmocom/bb/mobile/transaction.h> + +extern void *l23_ctx; + +void _gsm48_cc_trans_free(struct gsm_trans *trans); +void _gsm480_ss_trans_free(struct gsm_trans *trans); +void _gsm411_sms_trans_free(struct gsm_trans *trans); + +struct gsm_trans *trans_find_by_id(struct osmocom_ms *ms, + uint8_t proto, uint8_t trans_id) +{ + struct gsm_trans *trans; + + llist_for_each_entry(trans, &ms->trans_list, entry) { + if (trans->protocol == proto && + trans->transaction_id == trans_id) + return trans; + } + return NULL; +} + +struct gsm_trans *trans_find_by_callref(struct osmocom_ms *ms, + uint32_t callref) +{ + struct gsm_trans *trans; + + llist_for_each_entry(trans, &ms->trans_list, entry) { + if (trans->callref == callref) + return trans; + } + return NULL; +} + +struct gsm_trans *trans_alloc(struct osmocom_ms *ms, + uint8_t protocol, uint8_t trans_id, + uint32_t callref) +{ + struct gsm_trans *trans; + + trans = talloc_zero(l23_ctx, struct gsm_trans); + if (!trans) + return NULL; + + DEBUGP(DCC, "ms %s allocates transaction (proto %d trans_id %d " + "callref %x mem %p)\n", ms->name, protocol, trans_id, callref, + trans); + + trans->ms = ms; + + trans->protocol = protocol; + trans->transaction_id = trans_id; + trans->callref = callref; + + llist_add_tail(&trans->entry, &ms->trans_list); + + return trans; +} + +void trans_free(struct gsm_trans *trans) +{ + switch (trans->protocol) { + case GSM48_PDISC_CC: + _gsm48_cc_trans_free(trans); + break; + case GSM48_PDISC_NC_SS: + _gsm480_ss_trans_free(trans); + break; + case GSM48_PDISC_SMS: + _gsm411_sms_trans_free(trans); + break; + } + + DEBUGP(DCC, "ms %s frees transaction (mem %p)\n", trans->ms->name, + trans); + + llist_del(&trans->entry); + + talloc_free(trans); +} + +/* allocate an unused transaction ID + * in the given protocol using the ti_flag specified */ +int trans_assign_trans_id(struct osmocom_ms *ms, + uint8_t protocol, uint8_t ti_flag) +{ + struct gsm_trans *trans; + unsigned int used_tid_bitmask = 0; + int i, j, h; + + if (ti_flag) + ti_flag = 0x8; + + /* generate bitmask of already-used TIDs for this (proto) */ + llist_for_each_entry(trans, &ms->trans_list, entry) { + if (trans->protocol != protocol || + trans->transaction_id == 0xff) + continue; + used_tid_bitmask |= (1 << trans->transaction_id); + } + + /* find a new one, trying to go in a 'circular' pattern */ + for (h = 6; h > 0; h--) + if (used_tid_bitmask & (1 << (h | ti_flag))) + break; + for (i = 0; i < 7; i++) { + j = ((h + i) % 7) | ti_flag; + if ((used_tid_bitmask & (1 << j)) == 0) + return j; + } + + return -1; +} + diff --git a/src/host/layer23/src/mobile/voice.c b/src/host/layer23/src/mobile/voice.c new file mode 100644 index 00000000..b7678337 --- /dev/null +++ b/src/host/layer23/src/mobile/voice.c @@ -0,0 +1,78 @@ +/* + * (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdlib.h> + +#include <osmocom/core/msgb.h> + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/mobile/mncc.h> +#include <osmocom/bb/mobile/voice.h> + + +/* + * receive voice + */ + +static int gsm_recv_voice(struct osmocom_ms *ms, struct msgb *msg) +{ + struct gsm_data_frame *mncc; + + /* distribute and then free */ + if (ms->mncc_entity.mncc_recv && ms->mncc_entity.ref) { + /* push mncc header in front of data */ + mncc = (struct gsm_data_frame *) + msgb_push(msg, sizeof(struct gsm_data_frame)); + mncc->msg_type = GSM_TCHF_FRAME; + mncc->callref = ms->mncc_entity.ref; + ms->mncc_entity.mncc_recv(ms, mncc->msg_type, mncc); + } + + msgb_free(msg); + return 0; +} + +/* + * send voice + */ +int gsm_send_voice(struct osmocom_ms *ms, struct gsm_data_frame *data) +{ + struct msgb *nmsg; + + nmsg = msgb_alloc_headroom(33 + 64, 64, "TCH/F"); + if (!nmsg) + return -ENOMEM; + nmsg->l2h = msgb_put(nmsg, 33); + memcpy(nmsg->l2h, data->data, 33); + + return gsm48_rr_tx_voice(ms, nmsg); +} + +/* + * init + */ + +int gsm_voice_init(struct osmocom_ms *ms) +{ + ms->l1_entity.l1_traffic_ind = gsm_recv_voice; + + return 0; +} diff --git a/src/host/layer23/src/mobile/vty_interface.c b/src/host/layer23/src/mobile/vty_interface.c new file mode 100644 index 00000000..dc9e09d9 --- /dev/null +++ b/src/host/layer23/src/mobile/vty_interface.c @@ -0,0 +1,2946 @@ +/* + * (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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <sys/types.h> + +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm48.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/signal.h> + +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/networks.h> +#include <osmocom/bb/common/gps.h> +#include <osmocom/bb/mobile/mncc.h> +#include <osmocom/bb/mobile/transaction.h> +#include <osmocom/bb/mobile/vty.h> +#include <osmocom/bb/mobile/app_mobile.h> +#include <osmocom/bb/mobile/gsm480_ss.h> +#include <osmocom/bb/mobile/gsm411_sms.h> +#include <osmocom/vty/telnet_interface.h> + +void *l23_ctx; + +int mncc_call(struct osmocom_ms *ms, char *number); +int mncc_hangup(struct osmocom_ms *ms); +int mncc_answer(struct osmocom_ms *ms); +int mncc_hold(struct osmocom_ms *ms); +int mncc_retrieve(struct osmocom_ms *ms, int number); +int mncc_dtmf(struct osmocom_ms *ms, char *dtmf); + +extern struct llist_head ms_list; +extern struct llist_head active_connections; + +struct cmd_node ms_node = { + MS_NODE, + "%s(ms)#", + 1 +}; + +struct cmd_node testsim_node = { + TESTSIM_NODE, + "%s(test-sim)#", + 1 +}; + +struct cmd_node support_node = { + SUPPORT_NODE, + "%s(support)#", + 1 +}; + +static void print_vty(void *priv, const char *fmt, ...) +{ + char buffer[1000]; + struct vty *vty = priv; + va_list args; + + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer) - 1, fmt, args); + buffer[sizeof(buffer) - 1] = '\0'; + va_end(args); + + if (buffer[0]) { + if (buffer[strlen(buffer) - 1] == '\n') { + buffer[strlen(buffer) - 1] = '\0'; + vty_out(vty, "%s%s", buffer, VTY_NEWLINE); + } else + vty_out(vty, "%s", buffer); + } +} + +int vty_check_number(struct vty *vty, const char *number) +{ + int i; + + for (i = 0; i < strlen(number); i++) { + /* allow international notation with + */ + if (i == 0 && number[i] == '+') + continue; + if (!(number[i] >= '0' && number[i] <= '9') + && number[i] != '*' + && number[i] != '#' + && !(number[i] >= 'a' && number[i] <= 'c')) { + vty_out(vty, "Invalid digit '%c' of number!%s", + number[i], VTY_NEWLINE); + return -EINVAL; + } + } + if (number[0] == '\0' || (number[0] == '+' && number[1] == '\0')) { + vty_out(vty, "Given number has no digits!%s", VTY_NEWLINE); + return -EINVAL; + } + + return 0; +} + +int vty_reading = 0; +static int hide_default = 0; + +static void vty_restart(struct vty *vty, struct osmocom_ms *ms) +{ + if (vty_reading) + return; + if (ms->shutdown != 0) + return; + vty_out(vty, "You must restart MS '%s' ('shutdown / no shutdown') for " + "change to take effect!%s", ms->name, VTY_NEWLINE); +} + +static void vty_restart_if_started(struct vty *vty, struct osmocom_ms *ms) +{ + if (!ms->started) + return; + vty_restart(vty, ms); +} + +static struct osmocom_ms *get_ms(const char *name, struct vty *vty) +{ + struct osmocom_ms *ms; + + llist_for_each_entry(ms, &ms_list, entity) { + if (!strcmp(ms->name, name)) { + if (ms->shutdown) { + vty_out(vty, "MS '%s' is admin down.%s", name, + VTY_NEWLINE); + return NULL; + } + return ms; + } + } + vty_out(vty, "MS name '%s' does not exits.%s", name, VTY_NEWLINE); + + return NULL; +} + +static void gsm_ms_dump(struct osmocom_ms *ms, struct vty *vty) +{ + struct gsm_settings *set = &ms->settings; + struct gsm_trans *trans; + char *service = ""; + + if (!ms->started) + service = ", radio is not started"; + else if (ms->mmlayer.state == GSM48_MM_ST_MM_IDLE) { + /* current MM idle state */ + switch (ms->mmlayer.substate) { + case GSM48_MM_SST_NORMAL_SERVICE: + case GSM48_MM_SST_PLMN_SEARCH_NORMAL: + service = ", service is normal"; + break; + case GSM48_MM_SST_LOC_UPD_NEEDED: + case GSM48_MM_SST_ATTEMPT_UPDATE: + service = ", service is limited (pending)"; + break; + case GSM48_MM_SST_NO_CELL_AVAIL: + service = ", service is unavailable"; + break; + default: + if (ms->subscr.sim_valid) + service = ", service is limited"; + else + service = ", service is limited " + "(IMSI detached)"; + break; + } + } else + service = ", MM connection active"; + + vty_out(vty, "MS '%s' is %s%s%s%s", ms->name, + (ms->shutdown) ? "administratively " : "", + (ms->shutdown || !ms->started) ? "down" : "up", + (!ms->shutdown) ? service : "", + VTY_NEWLINE); + vty_out(vty, " IMEI: %s%s", set->imei, VTY_NEWLINE); + vty_out(vty, " IMEISV: %s%s", set->imeisv, VTY_NEWLINE); + if (set->imei_random) + vty_out(vty, " IMEI generation: random (%d trailing " + "digits)%s", set->imei_random, VTY_NEWLINE); + else + vty_out(vty, " IMEI generation: fixed%s", VTY_NEWLINE); + + if (ms->shutdown) + return; + + if (set->plmn_mode == PLMN_MODE_AUTO) + vty_out(vty, " automatic network selection state: %s%s", + get_a_state_name(ms->plmn.state), VTY_NEWLINE); + else + vty_out(vty, " manual network selection state : %s%s", + get_m_state_name(ms->plmn.state), VTY_NEWLINE); + if (ms->plmn.mcc) + vty_out(vty, " MCC=%s " + "MNC=%s (%s, %s)%s", gsm_print_mcc(ms->plmn.mcc), + gsm_print_mnc(ms->plmn.mnc), gsm_get_mcc(ms->plmn.mcc), + gsm_get_mnc(ms->plmn.mcc, ms->plmn.mnc), VTY_NEWLINE); + vty_out(vty, " cell selection state: %s%s", + get_cs_state_name(ms->cellsel.state), VTY_NEWLINE); + if (ms->cellsel.sel_mcc) { + vty_out(vty, " ARFCN=%s MCC=%s MNC=%s " + "LAC=0x%04x CELLID=0x%04x%s", + gsm_print_arfcn(ms->cellsel.sel_arfcn), + gsm_print_mcc(ms->cellsel.sel_mcc), + gsm_print_mnc(ms->cellsel.sel_mnc), + ms->cellsel.sel_lac, ms->cellsel.sel_id, VTY_NEWLINE); + vty_out(vty, " (%s, %s)%s", + gsm_get_mcc(ms->cellsel.sel_mcc), + gsm_get_mnc(ms->cellsel.sel_mcc, ms->cellsel.sel_mnc), + VTY_NEWLINE); + } + vty_out(vty, " radio ressource layer state: %s%s", + gsm48_rr_state_names[ms->rrlayer.state], VTY_NEWLINE); + vty_out(vty, " mobility management layer state: %s", + gsm48_mm_state_names[ms->mmlayer.state]); + if (ms->mmlayer.state == GSM48_MM_ST_MM_IDLE) + vty_out(vty, ", %s", + gsm48_mm_substate_names[ms->mmlayer.substate]); + vty_out(vty, "%s", VTY_NEWLINE); + llist_for_each_entry(trans, &ms->trans_list, entry) { + vty_out(vty, " call control state: %s%s", + gsm48_cc_state_name(trans->cc.state), VTY_NEWLINE); + } +} + + +DEFUN(show_ms, show_ms_cmd, "show ms [MS_NAME]", + SHOW_STR "Display available MS entities\n") +{ + struct osmocom_ms *ms; + + if (argc) { + llist_for_each_entry(ms, &ms_list, entity) { + if (!strcmp(ms->name, argv[0])) { + gsm_ms_dump(ms, vty); + return CMD_SUCCESS; + } + } + vty_out(vty, "MS name '%s' does not exits.%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } else { + llist_for_each_entry(ms, &ms_list, entity) { + gsm_ms_dump(ms, vty); + vty_out(vty, "%s", VTY_NEWLINE); + } + } + + return CMD_SUCCESS; +} + +DEFUN(show_support, show_support_cmd, "show support [MS_NAME]", + SHOW_STR "Display information about MS support\n" + "Name of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + + if (argc) { + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + gsm_support_dump(ms, print_vty, vty); + } else { + llist_for_each_entry(ms, &ms_list, entity) { + gsm_support_dump(ms, print_vty, vty); + vty_out(vty, "%s", VTY_NEWLINE); + } + } + + return CMD_SUCCESS; +} + +DEFUN(show_subscr, show_subscr_cmd, "show subscriber [MS_NAME]", + SHOW_STR "Display information about subscriber\n" + "Name of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + + if (argc) { + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + gsm_subscr_dump(&ms->subscr, print_vty, vty); + } else { + llist_for_each_entry(ms, &ms_list, entity) { + if (!ms->shutdown) { + gsm_subscr_dump(&ms->subscr, print_vty, vty); + vty_out(vty, "%s", VTY_NEWLINE); + } + } + } + + return CMD_SUCCESS; +} + +DEFUN(show_cell, show_cell_cmd, "show cell MS_NAME", + SHOW_STR "Display information about received cells\n" + "Name of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + gsm322_dump_cs_list(&ms->cellsel, GSM322_CS_FLAG_SUPPORT, print_vty, + vty); + + return CMD_SUCCESS; +} + +DEFUN(show_cell_si, show_cell_si_cmd, "show cell MS_NAME <0-1023> [pcs]", + SHOW_STR "Display information about received cell\n" + "Name of MS (see \"show ms\")\nRadio frequency number\n" + "Given frequency is PCS band (1900) rather than DCS band.") +{ + struct osmocom_ms *ms; + struct gsm48_sysinfo *s; + uint16_t arfcn = atoi(argv[1]); + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (argc > 2) { + if (arfcn < 512 || arfcn > 810) { + vty_out(vty, "Given ARFCN not in PCS band%s", + VTY_NEWLINE); + return CMD_WARNING; + } + arfcn |= ARFCN_PCS; + } + + s = ms->cellsel.list[arfcn2index(arfcn)].sysinfo; + if (!s) { + vty_out(vty, "Given ARFCN '%s' has no sysinfo available%s", + argv[1], VTY_NEWLINE); + return CMD_SUCCESS; + } + + gsm48_sysinfo_dump(s, arfcn, print_vty, vty, ms->settings.freq_map); + + return CMD_SUCCESS; +} + +DEFUN(show_nbcells, show_nbcells_cmd, "show neighbour-cells MS_NAME", + SHOW_STR "Display information about current neighbour cells\n" + "Name of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + gsm322_dump_nb_list(&ms->cellsel, print_vty, vty); + + return CMD_SUCCESS; +} + +DEFUN(show_ba, show_ba_cmd, "show ba MS_NAME [MCC] [MNC]", + SHOW_STR "Display information about band allocations\n" + "Name of MS (see \"show ms\")\nMobile Country Code\n" + "Mobile Network Code") +{ + struct osmocom_ms *ms; + uint16_t mcc = 0, mnc = 0; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (argc >= 3) { + mcc = gsm_input_mcc((char *)argv[1]); + mnc = gsm_input_mnc((char *)argv[2]); + if (mcc == GSM_INPUT_INVALID) { + vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (mnc == GSM_INPUT_INVALID) { + vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + } + + gsm322_dump_ba_list(&ms->cellsel, mcc, mnc, print_vty, vty); + + return CMD_SUCCESS; +} + +DEFUN(show_forb_plmn, show_forb_plmn_cmd, "show forbidden plmn MS_NAME", + SHOW_STR "Display information about forbidden cells / networks\n" + "Display forbidden PLMNs\nName of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + gsm_subscr_dump_forbidden_plmn(ms, print_vty, vty); + + return CMD_SUCCESS; +} + +DEFUN(show_forb_la, show_forb_la_cmd, "show forbidden location-area MS_NAME", + SHOW_STR "Display information about forbidden cells / networks\n" + "Display forbidden location areas\nName of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + gsm322_dump_forbidden_la(ms, print_vty, vty); + + return CMD_SUCCESS; +} + +DEFUN(monitor_network, monitor_network_cmd, "monitor network MS_NAME", + "Monitor...\nMonitor network information\nName of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + gsm48_rr_start_monitor(ms); + + return CMD_SUCCESS; +} + +DEFUN(no_monitor_network, no_monitor_network_cmd, "no monitor network MS_NAME", + NO_STR "Monitor...\nDeactivate monitor of network information\n" + "Name of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + gsm48_rr_stop_monitor(ms); + + return CMD_SUCCESS; +} + +DEFUN(sim_test, sim_test_cmd, "sim testcard MS_NAME [MCC] [MNC] [LAC] [TMSI]", + "SIM actions\nAttach bulit in test SIM\nName of MS (see \"show ms\")\n" + "Mobile Country Code of RPLMN\nMobile Network Code of RPLMN\n" + "Optionally location area code\nOptionally current assigned TMSI") +{ + struct osmocom_ms *ms; + uint16_t mcc = 0x001, mnc = 0x01f, lac = 0x0000; + uint32_t tmsi = 0xffffffff; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (ms->subscr.sim_valid) { + vty_out(vty, "SIM already attached, remove first!%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if (argc >= 3) { + mcc = gsm_input_mcc((char *)argv[1]); + mnc = gsm_input_mnc((char *)argv[2]); + if (mcc == GSM_INPUT_INVALID) { + vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (mnc == GSM_INPUT_INVALID) { + vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + } + + if (argc >= 4) + lac = strtoul(argv[3], NULL, 16); + + if (argc >= 5) + tmsi = strtoul(argv[4], NULL, 16); + + gsm_subscr_testcard(ms, mcc, mnc, lac, tmsi); + + return CMD_SUCCESS; +} + +DEFUN(sim_reader, sim_reader_cmd, "sim reader MS_NAME", + "SIM actions\nAttach SIM from reader\nName of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (ms->subscr.sim_valid) { + vty_out(vty, "SIM already attached, remove first!%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + gsm_subscr_simcard(ms); + + return CMD_SUCCESS; +} + +DEFUN(sim_remove, sim_remove_cmd, "sim remove MS_NAME", + "SIM actions\nDetach SIM card\nName of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (!ms->subscr.sim_valid) { + vty_out(vty, "No SIM attached!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gsm_subscr_remove(ms); + + return CMD_SUCCESS; +} + +DEFUN(sim_pin, sim_pin_cmd, "sim pin MS_NAME PIN", + "SIM actions\nEnter PIN for SIM card\nName of MS (see \"show ms\")\n" + "PIN number") +{ + struct osmocom_ms *ms; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (strlen(argv[1]) < 4 || strlen(argv[1]) > 8) { + vty_out(vty, "PIN must be in range 4..8!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!ms->subscr.sim_pin_required) { + vty_out(vty, "No PIN is required at this time!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gsm_subscr_sim_pin(ms, (char *)argv[1], "", 0); + + return CMD_SUCCESS; +} + +DEFUN(sim_disable_pin, sim_disable_pin_cmd, "sim disable-pin MS_NAME PIN", + "SIM actions\nDisable PIN of SIM card\nName of MS (see \"show ms\")\n" + "PIN number") +{ + struct osmocom_ms *ms; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (strlen(argv[1]) < 4 || strlen(argv[1]) > 8) { + vty_out(vty, "PIN must be in range 4..8!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gsm_subscr_sim_pin(ms, (char *)argv[1], "", -1); + + return CMD_SUCCESS; +} + +DEFUN(sim_enable_pin, sim_enable_pin_cmd, "sim enable-pin MS_NAME PIN", + "SIM actions\nEnable PIN of SIM card\nName of MS (see \"show ms\")\n" + "PIN number") +{ + struct osmocom_ms *ms; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (strlen(argv[1]) < 4 || strlen(argv[1]) > 8) { + vty_out(vty, "PIN must be in range 4..8!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gsm_subscr_sim_pin(ms, (char *)argv[1], "", 1); + + return CMD_SUCCESS; +} + +DEFUN(sim_change_pin, sim_change_pin_cmd, "sim change-pin MS_NAME OLD NEW", + "SIM actions\nChange PIN of SIM card\nName of MS (see \"show ms\")\n" + "Old PIN number\nNew PIN number") +{ + struct osmocom_ms *ms; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (strlen(argv[1]) < 4 || strlen(argv[1]) > 8) { + vty_out(vty, "Old PIN must be in range 4..8!%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (strlen(argv[2]) < 4 || strlen(argv[2]) > 8) { + vty_out(vty, "New PIN must be in range 4..8!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gsm_subscr_sim_pin(ms, (char *)argv[1], (char *)argv[2], 2); + + return CMD_SUCCESS; +} + +DEFUN(sim_unblock_pin, sim_unblock_pin_cmd, "sim unblock-pin MS_NAME PUC NEW", + "SIM actions\nChange PIN of SIM card\nName of MS (see \"show ms\")\n" + "Personal Unblock Key\nNew PIN number") +{ + struct osmocom_ms *ms; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (strlen(argv[1]) != 8) { + vty_out(vty, "PUC must be 8 digits!%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (strlen(argv[2]) < 4 || strlen(argv[2]) > 8) { + vty_out(vty, "PIN must be in range 4..8!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gsm_subscr_sim_pin(ms, (char *)argv[1], (char *)argv[2], 99); + + return CMD_SUCCESS; +} + +DEFUN(sim_lai, sim_lai_cmd, "sim lai MS_NAME MCC MNC LAC", + "SIM actions\nChange LAI of SIM card\nName of MS (see \"show ms\")\n" + "Mobile Country Code\nMobile Network Code\nLocation Area Code " + " (use 0000 to remove LAI)") +{ + struct osmocom_ms *ms; + uint16_t mcc = gsm_input_mcc((char *)argv[1]), + mnc = gsm_input_mnc((char *)argv[2]), + lac = strtoul(argv[3], NULL, 16); + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (mcc == GSM_INPUT_INVALID) { + vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (mnc == GSM_INPUT_INVALID) { + vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + + ms->subscr.mcc = mcc; + ms->subscr.mnc = mnc; + ms->subscr.lac = lac; + ms->subscr.tmsi = 0xffffffff; + + gsm_subscr_write_loci(ms); + + return CMD_SUCCESS; +} + +DEFUN(network_select, network_select_cmd, + "network select MS_NAME MCC MNC [force]", + "Select ...\nSelect Network\nName of MS (see \"show ms\")\n" + "Mobile Country Code\nMobile Network Code\n" + "Force selecting a network that is not in the list") +{ + struct osmocom_ms *ms; + struct gsm322_plmn *plmn; + struct msgb *nmsg; + struct gsm322_msg *ngm; + struct gsm322_plmn_list *temp; + uint16_t mcc = gsm_input_mcc((char *)argv[1]), + mnc = gsm_input_mnc((char *)argv[2]); + int found = 0; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + plmn = &ms->plmn; + + if (ms->settings.plmn_mode != PLMN_MODE_MANUAL) { + vty_out(vty, "Not in manual network selection mode%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if (mcc == GSM_INPUT_INVALID) { + vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (mnc == GSM_INPUT_INVALID) { + vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (argc < 4) { + llist_for_each_entry(temp, &plmn->sorted_plmn, entry) + if (temp->mcc == mcc && temp->mnc == mnc) + found = 1; + if (!found) { + vty_out(vty, "Network not in list!%s", VTY_NEWLINE); + vty_out(vty, "To force selecting this network, use " + "'force' keyword%s", VTY_NEWLINE); + return CMD_WARNING; + } + } + + nmsg = gsm322_msgb_alloc(GSM322_EVENT_CHOOSE_PLMN); + if (!nmsg) + return CMD_WARNING; + ngm = (struct gsm322_msg *) nmsg->data; + ngm->mcc = mcc; + ngm->mnc = mnc; + gsm322_plmn_sendmsg(ms, nmsg); + + return CMD_SUCCESS; +} + +DEFUN(call, call_cmd, "call MS_NAME (NUMBER|emergency|answer|hangup|hold)", + "Make a call\nName of MS (see \"show ms\")\nPhone number to call " + "(Use digits '0123456789*#abc', and '+' to dial international)\n" + "Make an emergency call\nAnswer an incomming call\nHangup a call\n" + "Hold current active call\n") +{ + struct osmocom_ms *ms; + struct gsm_settings *set; + struct gsm_settings_abbrev *abbrev; + char *number; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + set = &ms->settings; + + if (set->ch_cap == GSM_CAP_SDCCH) { + vty_out(vty, "Basic call is not supported for SDCCH only " + "mobile%s", VTY_NEWLINE); + return CMD_WARNING; + } + + number = (char *)argv[1]; + if (!strcmp(number, "emergency")) + mncc_call(ms, number); + else if (!strcmp(number, "answer")) + mncc_answer(ms); + else if (!strcmp(number, "hangup")) + mncc_hangup(ms); + else if (!strcmp(number, "hold")) + mncc_hold(ms); + else { + llist_for_each_entry(abbrev, &set->abbrev, list) { + if (!strcmp(number, abbrev->abbrev)) { + number = abbrev->number; + vty_out(vty, "Dialing number '%s'%s", number, + VTY_NEWLINE); + break; + } + } + if (vty_check_number(vty, number)) + return CMD_WARNING; + mncc_call(ms, number); + } + + return CMD_SUCCESS; +} + +DEFUN(call_retr, call_retr_cmd, "call MS_NAME retrieve [NUMBER]", + "Make a call\nName of MS (see \"show ms\")\n" + "Retrieve call on hold\nNumber of call to retrieve") +{ + struct osmocom_ms *ms; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + mncc_retrieve(ms, (argc > 1) ? atoi(argv[1]) : 0); + + return CMD_SUCCESS; +} + +DEFUN(call_dtmf, call_dtmf_cmd, "call MS_NAME dtmf DIGITS", + "Make a call\nName of MS (see \"show ms\")\n" + "One or more DTMF digits to transmit") +{ + struct osmocom_ms *ms; + struct gsm_settings *set; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + set = &ms->settings; + + if (!set->cc_dtmf) { + vty_out(vty, "DTMF not supported, please enable!%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + mncc_dtmf(ms, (char *)argv[1]); + + return CMD_SUCCESS; +} + +DEFUN(sms, sms_cmd, "sms MS_NAME NUMBER .LINE", + "Send an SMS\nName of MS (see \"show ms\")\nPhone number to send SMS " + "(Use digits '0123456789*#abc', and '+' to dial international)\n" + "SMS text\n") +{ + struct osmocom_ms *ms; + struct gsm_settings *set; + struct gsm_settings_abbrev *abbrev; + char *number, *sms_sca = NULL; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + set = &ms->settings; + + if (!set->sms_ptp) { + vty_out(vty, "SMS not supported by this mobile, please enable " + "SMS support%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (ms->subscr.sms_sca[0]) + sms_sca = ms->subscr.sms_sca; + else if (set->sms_sca[0]) + sms_sca = set->sms_sca; + + if (!sms_sca) { + vty_out(vty, "SMS sms-service-center not defined on SIM card, " + "please define one at settings.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + number = (char *)argv[1]; + llist_for_each_entry(abbrev, &set->abbrev, list) { + if (!strcmp(number, abbrev->abbrev)) { + number = abbrev->number; + vty_out(vty, "Using number '%s'%s", number, + VTY_NEWLINE); + break; + } + } + if (vty_check_number(vty, number)) + return CMD_WARNING; + + sms_send(ms, sms_sca, number, argv_concat(argv, argc, 2)); + + return CMD_SUCCESS; +} + +DEFUN(service, service_cmd, "service MS_NAME (*#06#|*#21#|*#67#|*#61#|*#62#" + "|*#002#|*#004#|*xx*number#|*xx#|#xx#|##xx#|STRING|hangup)", + "Send a Supplementary Service request\nName of MS (see \"show ms\")\n" + "Query IMSI\n" + "Query Call Forwarding Unconditional (CFU)\n" + "Query Call Forwarding when Busy (CFB)\n" + "Query Call Forwarding when No Response (CFNR)\n" + "Query Call Forwarding when Not Reachable\n" + "Query all Call Forwardings\n" + "Query all conditional Call Forwardings\n" + "Set and activate Call Forwarding (xx = Service Code, see above)\n" + "Activate Call Forwarding (xx = Service Code, see above)\n" + "Deactivate Call Forwarding (xx = Service Code, see above)\n" + "Erase and deactivate Call Forwarding (xx = Service Code, see above)\n" + "Service string " + "(Example: '*100#' requests account balace on some networks.)\n" + "Hangup existing service connection") +{ + struct osmocom_ms *ms; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + ss_send(ms, argv[1], 0); + + return CMD_SUCCESS; +} + +DEFUN(test_reselection, test_reselection_cmd, "test re-selection NAME", + "Manually trigger cell re-selection\nName of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + struct gsm_settings *set; + struct msgb *nmsg; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + set = &ms->settings; + + if (set->stick) { + vty_out(vty, "Cannot trigger cell re-selection, because we " + "stick to a cell!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + nmsg = gsm322_msgb_alloc(GSM322_EVENT_CELL_RESEL); + if (!nmsg) + return CMD_WARNING; + gsm322_c_event(ms, nmsg); + + + return CMD_SUCCESS; +} + +DEFUN(delete_forbidden_plmn, delete_forbidden_plmn_cmd, + "delete forbidden plmn NAME MCC MNC", + "Delete\nForbidden\nplmn\nName of MS (see \"show ms\")\n" + "Mobile Country Code\nMobile Network Code") +{ + struct osmocom_ms *ms; + uint16_t mcc = gsm_input_mcc((char *)argv[1]), + mnc = gsm_input_mnc((char *)argv[2]); + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + if (mcc == GSM_INPUT_INVALID) { + vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (mnc == GSM_INPUT_INVALID) { + vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + + gsm_subscr_del_forbidden_plmn(&ms->subscr, mcc, mnc); + + return CMD_SUCCESS; +} + +DEFUN(network_show, network_show_cmd, "network show MS_NAME", + "Network ...\nShow results of network search (again)\n" + "Name of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + struct gsm_settings *set; + struct gsm322_plmn *plmn; + struct gsm322_plmn_list *temp; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + set = &ms->settings; + plmn = &ms->plmn; + + if (set->plmn_mode != PLMN_MODE_AUTO + && plmn->state != GSM322_M3_NOT_ON_PLMN) { + vty_out(vty, "Start network search first!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + llist_for_each_entry(temp, &plmn->sorted_plmn, entry) + vty_out(vty, " Network %s, %s (%s, %s)%s", + gsm_print_mcc(temp->mcc), gsm_print_mnc(temp->mnc), + gsm_get_mcc(temp->mcc), + gsm_get_mnc(temp->mcc, temp->mnc), VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(network_search, network_search_cmd, "network search MS_NAME", + "Network ...\nTrigger network search\nName of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + struct msgb *nmsg; + + ms = get_ms(argv[0], vty); + if (!ms) + return CMD_WARNING; + + nmsg = gsm322_msgb_alloc(GSM322_EVENT_USER_RESEL); + if (!nmsg) + return CMD_WARNING; + gsm322_plmn_sendmsg(ms, nmsg); + + return CMD_SUCCESS; +} + +DEFUN(cfg_gps_enable, cfg_gps_enable_cmd, "gps enable", + "GPS receiver") +{ + if (osmo_gps_open()) { + g.enable = 1; + vty_out(vty, "Failed to open GPS device!%s", VTY_NEWLINE); + return CMD_WARNING; + } + g.enable = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_gps_enable, cfg_no_gps_enable_cmd, "no gps enable", + NO_STR "Disable GPS receiver") +{ + if (g.enable) + osmo_gps_close(); + g.enable = 0; + + return CMD_SUCCESS; +} + +#ifdef _HAVE_GPSD +DEFUN(cfg_gps_host, cfg_gps_host_cmd, "gps host HOST:PORT", + "GPS receiver\nSelect gpsd host and port\n" + "IP and port (optional) of the host running gpsd") +{ + char* colon = strstr(argv[0], ":"); + if (colon != NULL) { + memcpy(g.gpsd_host, argv[0], colon - argv[0] - 1); + g.gpsd_host[colon - argv[0]] = '\0'; + memcpy(g.gpsd_port, colon, strlen(colon)); + g.gpsd_port[strlen(colon)] = '\0'; + } else { + snprintf(g.gpsd_host, ARRAY_SIZE(g.gpsd_host), "%s", argv[0]); + g.gpsd_host[ARRAY_SIZE(g.gpsd_host) - 1] = '\0'; + snprintf(g.gpsd_port, ARRAY_SIZE(g.gpsd_port), "2947"); + g.gpsd_port[ARRAY_SIZE(g.gpsd_port) - 1] = '\0'; + } + g.gps_type = GPS_TYPE_GPSD; + if (g.enable) { + osmo_gps_close(); + if (osmo_gps_open()) { + vty_out(vty, "Failed to connect to gpsd host!%s", + VTY_NEWLINE); + return CMD_WARNING; + } + } + + return CMD_SUCCESS; +} +#endif + +DEFUN(cfg_gps_device, cfg_gps_device_cmd, "gps device DEVICE", + "GPS receiver\nSelect serial device\n" + "Full path of serial device including /dev/") +{ + strncpy(g.device, argv[0], sizeof(g.device)); + g.device[sizeof(g.device) - 1] = '\0'; + g.gps_type = GPS_TYPE_SERIAL; + if (g.enable) { + osmo_gps_close(); + if (osmo_gps_open()) { + vty_out(vty, "Failed to open GPS device!%s", + VTY_NEWLINE); + return CMD_WARNING; + } + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_gps_baud, cfg_gps_baud_cmd, "gps baudrate " + "(default|4800|""9600|19200|38400|57600|115200)", + "GPS receiver\nSelect baud rate\nDefault, don't modify\n\n\n\n\n\n") +{ + if (argv[0][0] == 'd') + g.baud = 0; + else + g.baud = atoi(argv[0]); + if (g.enable) { + osmo_gps_close(); + if (osmo_gps_open()) { + g.enable = 0; + vty_out(vty, "Failed to open GPS device!%s", + VTY_NEWLINE); + return CMD_WARNING; + } + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_hide_default, cfg_hide_default_cmd, "hide-default", + "Hide most default values in config to make it more compact") +{ + hide_default = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_hide_default, cfg_no_hide_default_cmd, "no hide-default", + NO_STR "Show default values in config") +{ + hide_default = 0; + + return CMD_SUCCESS; +} + +/* per MS config */ +DEFUN(cfg_ms, cfg_ms_cmd, "ms MS_NAME", + "Select a mobile station to configure\nName of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + int found = 0; + + llist_for_each_entry(ms, &ms_list, entity) { + if (!strcmp(ms->name, argv[0])) { + found = 1; + break; + } + } + + if (!found) { + if (!vty_reading) { + vty_out(vty, "MS name '%s' does not exits, try " + "'ms %s create'%s", argv[0], argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + ms = mobile_new((char *)argv[0]); + if (!ms) { + vty_out(vty, "Failed to add MS name '%s'%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + } + + vty->index = ms; + vty->node = MS_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_create, cfg_ms_create_cmd, "ms MS_NAME create", + "Select a mobile station to configure\nName of MS (see \"show ms\")\n" + "Create if MS does not exists") +{ + struct osmocom_ms *ms; + int found = 0; + + llist_for_each_entry(ms, &ms_list, entity) { + if (!strcmp(ms->name, argv[0])) { + found = 1; + break; + } + } + + if (!found) { + ms = mobile_new((char *)argv[0]); + if (!ms) { + vty_out(vty, "Failed to add MS name '%s'%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + } + + vty->index = ms; + vty->node = MS_NODE; + + vty_out(vty, "MS '%s' created, after configuration, do 'no shutdown'%s", + argv[0], VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_rename, cfg_ms_rename_cmd, "ms MS_NAME rename MS_NAME", + "Select a mobile station to configure\nName of MS (see \"show ms\")\n" + "Rename MS\nNew name of MS") +{ + struct osmocom_ms *ms; + int found = 0; + + llist_for_each_entry(ms, &ms_list, entity) { + if (!strcmp(ms->name, argv[0])) { + found = 1; + break; + } + } + + if (!found) { + vty_out(vty, "MS name '%s' does not exist%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + + strncpy(ms->name, argv[1], sizeof(ms->name) - 1); + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_ms, cfg_no_ms_cmd, "no ms MS_NAME", + NO_STR "Select a mobile station to remove\n" + "Name of MS (see \"show ms\")") +{ + struct osmocom_ms *ms; + int found = 0; + + llist_for_each_entry(ms, &ms_list, entity) { + if (!strcmp(ms->name, argv[0])) { + found = 1; + break; + } + } + + if (!found) { + vty_out(vty, "MS name '%s' does not exist%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + + mobile_delete(ms, 1); + + return CMD_SUCCESS; +} + +#define SUP_WRITE(item, cmd) \ + if (sup->item) \ + if (!hide_default || !set->item) \ + vty_out(vty, " %s%s%s", (set->item) ? "" : "no ", \ + cmd, VTY_NEWLINE); + +static void config_write_ms(struct vty *vty, struct osmocom_ms *ms) +{ + struct gsm_settings *set = &ms->settings; + struct gsm_support *sup = &ms->support; + struct gsm_settings_abbrev *abbrev; + + vty_out(vty, "ms %s%s", ms->name, VTY_NEWLINE); + vty_out(vty, " layer2-socket %s%s", set->layer2_socket_path, + VTY_NEWLINE); + vty_out(vty, " sap-socket %s%s", set->sap_socket_path, VTY_NEWLINE); + switch(set->sim_type) { + case GSM_SIM_TYPE_NONE: + vty_out(vty, " sim none%s", VTY_NEWLINE); + break; + case GSM_SIM_TYPE_READER: + vty_out(vty, " sim reader%s", VTY_NEWLINE); + break; + case GSM_SIM_TYPE_TEST: + vty_out(vty, " sim test%s", VTY_NEWLINE); + break; + } + vty_out(vty, " network-selection-mode %s%s", (set->plmn_mode + == PLMN_MODE_AUTO) ? "auto" : "manual", VTY_NEWLINE); + vty_out(vty, " imei %s %s%s", set->imei, + set->imeisv + strlen(set->imei), VTY_NEWLINE); + if (set->imei_random) + vty_out(vty, " imei-random %d%s", set->imei_random, + VTY_NEWLINE); + else + if (!hide_default) + vty_out(vty, " imei-fixed%s", VTY_NEWLINE); + if (set->emergency_imsi[0]) + vty_out(vty, " emergency-imsi %s%s", set->emergency_imsi, + VTY_NEWLINE); + else + if (!hide_default) + vty_out(vty, " no emergency-imsi%s", VTY_NEWLINE); + if (set->sms_sca[0]) + vty_out(vty, " sms-service-center %s%s", set->sms_sca, + VTY_NEWLINE); + else + if (!hide_default) + vty_out(vty, " no sms-service-center%s", VTY_NEWLINE); + if (!hide_default || set->cw) + vty_out(vty, " %scall-waiting%s", (set->cw) ? "" : "no ", + VTY_NEWLINE); + if (!hide_default || set->auto_answer) + vty_out(vty, " %sauto-answer%s", + (set->auto_answer) ? "" : "no ", VTY_NEWLINE); + if (!hide_default || set->force_rekey) + vty_out(vty, " %sforce-rekey%s", + (set->force_rekey) ? "" : "no ", VTY_NEWLINE); + if (!hide_default || set->clip) + vty_out(vty, " %sclip%s", (set->clip) ? "" : "no ", + VTY_NEWLINE); + if (!hide_default || set->clir) + vty_out(vty, " %sclir%s", (set->clir) ? "" : "no ", + VTY_NEWLINE); + if (set->alter_tx_power) + if (set->alter_tx_power_value) + vty_out(vty, " tx-power %d%s", + set->alter_tx_power_value, VTY_NEWLINE); + else + vty_out(vty, " tx-power full%s", VTY_NEWLINE); + else + if (!hide_default) + vty_out(vty, " tx-power auto%s", VTY_NEWLINE); + if (set->alter_delay) + vty_out(vty, " simulated-delay %d%s", set->alter_delay, + VTY_NEWLINE); + else + if (!hide_default) + vty_out(vty, " no simulated-delay%s", VTY_NEWLINE); + if (set->stick) + vty_out(vty, " stick %d%s%s", set->stick_arfcn & 1023, + (set->stick_arfcn & ARFCN_PCS) ? " pcs" : "", + VTY_NEWLINE); + else + if (!hide_default) + vty_out(vty, " no stick%s", VTY_NEWLINE); + if (!hide_default || set->no_lupd) + vty_out(vty, " %slocation-updating%s", + (set->no_lupd) ? "no " : "", VTY_NEWLINE); + if (!hide_default || set->no_neighbour) + vty_out(vty, " %sneighbour-measurement%s", + (set->no_neighbour) ? "no " : "", VTY_NEWLINE); + if (set->full_v1 || set->full_v2 || set->full_v3) { + /* mandatory anyway */ + vty_out(vty, " codec full-speed%s%s", + (!set->half_prefer) ? " prefer" : "", + VTY_NEWLINE); + } + if (set->half_v1 || set->half_v3) { + if (set->half) + vty_out(vty, " codec half-speed%s%s", + (set->half_prefer) ? " prefer" : "", + VTY_NEWLINE); + else + vty_out(vty, " no codec half-speed%s", VTY_NEWLINE); + } + if (llist_empty(&set->abbrev)) { + if (!hide_default) + vty_out(vty, " no abbrev%s", VTY_NEWLINE); + } else { + llist_for_each_entry(abbrev, &set->abbrev, list) + vty_out(vty, " abbrev %s %s%s%s%s", abbrev->abbrev, + abbrev->number, (abbrev->name[0]) ? " " : "", + abbrev->name, VTY_NEWLINE); + } + vty_out(vty, " support%s", VTY_NEWLINE); + SUP_WRITE(sms_ptp, "sms"); + SUP_WRITE(a5_1, "a5/1"); + SUP_WRITE(a5_2, "a5/2"); + SUP_WRITE(a5_3, "a5/3"); + SUP_WRITE(a5_4, "a5/4"); + SUP_WRITE(a5_5, "a5/5"); + SUP_WRITE(a5_6, "a5/6"); + SUP_WRITE(a5_7, "a5/7"); + SUP_WRITE(p_gsm, "p-gsm"); + SUP_WRITE(e_gsm, "e-gsm"); + SUP_WRITE(r_gsm, "r-gsm"); + SUP_WRITE(pcs, "gsm-850"); + SUP_WRITE(gsm_480, "gsm-480"); + SUP_WRITE(gsm_450, "gsm-450"); + SUP_WRITE(dcs, "dcs"); + SUP_WRITE(pcs, "pcs"); + if (sup->r_gsm || sup->e_gsm || sup->p_gsm) + if (!hide_default || sup->class_900 != set->class_900) + vty_out(vty, " class-900 %d%s", set->class_900, + VTY_NEWLINE); + if (sup->gsm_850) + if (!hide_default || sup->class_850 != set->class_850) + vty_out(vty, " class-850 %d%s", set->class_850, + VTY_NEWLINE); + if (sup->gsm_480 || sup->gsm_450) + if (!hide_default || sup->class_400 != set->class_400) + vty_out(vty, " class-400 %d%s", set->class_400, + VTY_NEWLINE); + if (sup->dcs) + if (!hide_default || sup->class_dcs != set->class_dcs) + vty_out(vty, " class-dcs %d%s", set->class_dcs, + VTY_NEWLINE); + if (sup->pcs) + if (!hide_default || sup->class_pcs != set->class_pcs) + vty_out(vty, " class-pcs %d%s", set->class_pcs, + VTY_NEWLINE); + if (!hide_default || sup->ch_cap != set->ch_cap) { + switch (set->ch_cap) { + case GSM_CAP_SDCCH: + vty_out(vty, " channel-capability sdcch%s", + VTY_NEWLINE); + break; + case GSM_CAP_SDCCH_TCHF: + vty_out(vty, " channel-capability sdcch+tchf%s", + VTY_NEWLINE); + break; + case GSM_CAP_SDCCH_TCHF_TCHH: + vty_out(vty, " channel-capability sdcch+tchf+tchh%s", + VTY_NEWLINE); + break; + } + } + SUP_WRITE(full_v1, "full-speech-v1"); + SUP_WRITE(full_v2, "full-speech-v2"); + SUP_WRITE(full_v3, "full-speech-v3"); + SUP_WRITE(half_v1, "half-speech-v1"); + SUP_WRITE(half_v3, "half-speech-v3"); + if (!hide_default || sup->min_rxlev_db != set->min_rxlev_db) + vty_out(vty, " min-rxlev %d%s", set->min_rxlev_db, + VTY_NEWLINE); + if (!hide_default || sup->dsc_max != set->dsc_max) + vty_out(vty, " dsc-max %d%s", set->dsc_max, VTY_NEWLINE); + if (!hide_default || set->skip_max_per_band) + vty_out(vty, " %sskip-max-per-band%s", + (set->skip_max_per_band) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " exit%s", VTY_NEWLINE); + vty_out(vty, " test-sim%s", VTY_NEWLINE); + vty_out(vty, " imsi %s%s", set->test_imsi, VTY_NEWLINE); + switch (set->test_ki_type) { + case GSM_SIM_KEY_XOR: + vty_out(vty, " ki xor %s%s", + osmo_hexdump(set->test_ki, 12), VTY_NEWLINE); + break; + case GSM_SIM_KEY_COMP128: + vty_out(vty, " ki comp128 %s%s", + osmo_hexdump(set->test_ki, 16), VTY_NEWLINE); + break; + } + if (!hide_default || set->test_barr) + vty_out(vty, " %sbarred-access%s", + (set->test_barr) ? "" : "no ", VTY_NEWLINE); + if (set->test_rplmn_valid) { + vty_out(vty, " rplmn %s %s", + gsm_print_mcc(set->test_rplmn_mcc), + gsm_print_mnc(set->test_rplmn_mnc)); + if (set->test_lac > 0x0000 && set->test_lac < 0xfffe) + vty_out(vty, " 0x%04x", set->test_lac); + if (set->test_tmsi != 0xffffffff) + vty_out(vty, " 0x%08x", set->test_tmsi); + vty_out(vty, "%s", VTY_NEWLINE); + } else + if (!hide_default) + vty_out(vty, " no rplmn%s", VTY_NEWLINE); + if (!hide_default || set->test_always) + vty_out(vty, " hplmn-search %s%s", + (set->test_always) ? "everywhere" : "foreign-country", + VTY_NEWLINE); + vty_out(vty, " exit%s", VTY_NEWLINE); + /* no shutdown must be written to config, because shutdown is default */ + vty_out(vty, " %sshutdown%s", (ms->shutdown) ? "" : "no ", + VTY_NEWLINE); + vty_out(vty, "exit%s", VTY_NEWLINE); + vty_out(vty, "!%s", VTY_NEWLINE); +} + +static int config_write(struct vty *vty) +{ + struct osmocom_ms *ms; + +#ifdef _HAVE_GPSD + vty_out(vty, "gpsd host %s%s", g.gpsd_host, VTY_NEWLINE); + vty_out(vty, "gpsd port %s%s", g.gpsd_port, VTY_NEWLINE); +#endif + vty_out(vty, "gps device %s%s", g.device, VTY_NEWLINE); + if (g.baud) + vty_out(vty, "gps baudrate %d%s", g.baud, VTY_NEWLINE); + else + vty_out(vty, "gps baudrate default%s", VTY_NEWLINE); + vty_out(vty, "%sgps enable%s", (g.enable) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, "!%s", VTY_NEWLINE); + + vty_out(vty, "%shide-default%s", (hide_default) ? "": "no ", + VTY_NEWLINE); + vty_out(vty, "!%s", VTY_NEWLINE); + + llist_for_each_entry(ms, &ms_list, entity) + config_write_ms(vty, ms); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_show_this, cfg_ms_show_this_cmd, "show this", + SHOW_STR "Show config of this MS") +{ + struct osmocom_ms *ms = vty->index; + + config_write_ms(vty, ms); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_layer2, cfg_ms_layer2_cmd, "layer2-socket PATH", + "Define socket path to connect between layer 2 and layer 1\n" + "Unix socket, default '/tmp/osmocom_l2'") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + strncpy(set->layer2_socket_path, argv[0], + sizeof(set->layer2_socket_path) - 1); + + vty_restart(vty, ms); + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_sap, cfg_ms_sap_cmd, "sap-socket PATH", + "Define socket path to connect to SIM reader\n" + "Unix socket, default '/tmp/osmocom_sap'") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + strncpy(set->sap_socket_path, argv[0], + sizeof(set->sap_socket_path) - 1); + + vty_restart(vty, ms); + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_sim, cfg_ms_sim_cmd, "sim (none|reader|test)", + "Set SIM card to attach when powering on\nAttach no SIM\n" + "Attach SIM from reader\nAttach bulit in test SIM") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + switch (argv[0][0]) { + case 'n': + set->sim_type = GSM_SIM_TYPE_NONE; + break; + case 'r': + set->sim_type = GSM_SIM_TYPE_READER; + break; + case 't': + set->sim_type = GSM_SIM_TYPE_TEST; + break; + default: + vty_out(vty, "unknown SIM type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + vty_restart_if_started(vty, ms); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_mode, cfg_ms_mode_cmd, "network-selection-mode (auto|manual)", + "Set network selection mode\nAutomatic network selection\n" + "Manual network selection") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + struct msgb *nmsg; + + if (!ms->started) { + if (argv[0][0] == 'a') + set->plmn_mode = PLMN_MODE_AUTO; + else + set->plmn_mode = PLMN_MODE_MANUAL; + } else { + if (argv[0][0] == 'a') + nmsg = gsm322_msgb_alloc(GSM322_EVENT_SEL_AUTO); + else + nmsg = gsm322_msgb_alloc(GSM322_EVENT_SEL_MANUAL); + if (!nmsg) + return CMD_WARNING; + gsm322_plmn_sendmsg(ms, nmsg); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_imei, cfg_ms_imei_cmd, "imei IMEI [SV]", + "Set IMEI (enter without control digit)\n15 Digits IMEI\n" + "Software version digit") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + char *error, *sv = "0"; + + if (argc >= 2) + sv = (char *)argv[1]; + + error = gsm_check_imei(argv[0], sv); + if (error) { + vty_out(vty, "%s%s", error, VTY_NEWLINE); + return CMD_WARNING; + } + + strcpy(set->imei, argv[0]); + strcpy(set->imeisv, argv[0]); + strcpy(set->imeisv + 15, sv); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_imei_fixed, cfg_ms_imei_fixed_cmd, "imei-fixed", + "Use fixed IMEI on every power on") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->imei_random = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_imei_random, cfg_ms_imei_random_cmd, "imei-random <0-15>", + "Use random IMEI on every power on\n" + "Number of trailing digits to randomize") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->imei_random = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_emerg_imsi, cfg_ms_emerg_imsi_cmd, "emergency-imsi IMSI", + "Use special IMSI for emergency calls\n15 digits IMSI") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + char *error; + + error = gsm_check_imsi(argv[0]); + if (error) { + vty_out(vty, "%s%s", error, VTY_NEWLINE); + return CMD_WARNING; + } + strcpy(set->emergency_imsi, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_no_emerg_imsi, cfg_ms_no_emerg_imsi_cmd, "no emergency-imsi", + NO_STR "Use IMSI of SIM or IMEI for emergency calls") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->emergency_imsi[0] = '\0'; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_sms_sca, cfg_ms_sms_sca_cmd, "sms-service-center NUMBER", + "Use Service center address for outgoing SMS\nNumber of service center " + "(Use digits '0123456789*#abc', and '+' to dial international)") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + const char *number = argv[0]; + + if ((strlen(number) > 20 && number[0] != '+') || strlen(number) > 21) { + vty_out(vty, "Number too long%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (vty_check_number(vty, number)) + return CMD_WARNING; + + strcpy(set->sms_sca, number); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_no_sms_sca, cfg_ms_no_sms_sca_cmd, "no sms-service-center", + NO_STR "Use Service center address for outgoing SMS") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->sms_sca[0] = '\0'; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_cw, cfg_ms_no_cw_cmd, "no call-waiting", + NO_STR "Disallow waiting calls") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->cw = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_cw, cfg_ms_cw_cmd, "call-waiting", + "Allow waiting calls") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->cw = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_auto_answer, cfg_ms_no_auto_answer_cmd, "no auto-answer", + NO_STR "Disable auto-answering calls") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->auto_answer = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_auto_answer, cfg_ms_auto_answer_cmd, "auto-answer", + "Enable auto-answering calls") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->auto_answer = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_force_rekey, cfg_ms_no_force_rekey_cmd, "no force-rekey", + NO_STR "Disable key renew forcing after every event") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->force_rekey = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_force_rekey, cfg_ms_force_rekey_cmd, "force-rekey", + "Enable key renew forcing after every event") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->force_rekey = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_clip, cfg_ms_clip_cmd, "clip", + "Force caller ID presentation") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->clip = 1; + set->clir = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_clir, cfg_ms_clir_cmd, "clir", + "Force caller ID restriction") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->clip = 0; + set->clir = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_clip, cfg_ms_no_clip_cmd, "no clip", + NO_STR "Disable forcing of caller ID presentation") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->clip = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_clir, cfg_ms_no_clir_cmd, "no clir", + NO_STR "Disable forcing of caller ID restriction") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->clir = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_tx_power, cfg_ms_tx_power_cmd, "tx-power (auto|full)", + "Set the way to choose transmit power\nControlled by BTS\n" + "Always full power\nFixed GSM power value if supported") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + switch (argv[0][0]) { + case 'a': + set->alter_tx_power = 0; + break; + case 'f': + set->alter_tx_power = 1; + set->alter_tx_power_value = 0; + break; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_tx_power_val, cfg_ms_tx_power_val_cmd, "tx-power <0-31>", + "Set the way to choose transmit power\n" + "Fixed GSM power value if supported") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->alter_tx_power = 1; + set->alter_tx_power_value = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_sim_delay, cfg_ms_sim_delay_cmd, "simulated-delay <-128-127>", + "Simulate a lower or higher distance from the BTS\n" + "Delay in half bits (distance in 553.85 meter steps)") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->alter_delay = atoi(argv[0]); + gsm48_rr_alter_delay(ms); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_no_sim_delay, cfg_ms_no_sim_delay_cmd, "no simulated-delay", + NO_STR "Do not simulate a lower or higher distance from the BTS") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->alter_delay = 0; + gsm48_rr_alter_delay(ms); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_stick, cfg_ms_stick_cmd, "stick <0-1023> [pcs]", + "Stick to the given cell\nARFCN of the cell to stick to\n" + "Given frequency is PCS band (1900) rather than DCS band.") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + uint16_t arfcn = atoi(argv[0]); + + if (argc > 1) { + if (arfcn < 512 || arfcn > 810) { + vty_out(vty, "Given ARFCN not in PCS band%s", + VTY_NEWLINE); + return CMD_WARNING; + } + arfcn |= ARFCN_PCS; + } + set->stick = 1; + set->stick_arfcn = arfcn; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_no_stick, cfg_ms_no_stick_cmd, "no stick", + NO_STR "Do not stick to any cell") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->stick = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_lupd, cfg_ms_lupd_cmd, "location-updating", + "Allow location updating") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->no_lupd = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_no_lupd, cfg_ms_no_lupd_cmd, "no location-updating", + NO_STR "Do not allow location updating") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->no_lupd = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_codec_full, cfg_ms_codec_full_cmd, "codec full-speed", + "Enable codec\nFull speed speech codec") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + if (!set->full_v1 && !set->full_v2 && !set->full_v3) { + vty_out(vty, "Full-rate codec not supported%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_codec_full_pref, cfg_ms_codec_full_pref_cmd, "codec full-speed " + "prefer", + "Enable codec\nFull speed speech codec\nPrefer this codec") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + if (!set->full_v1 && !set->full_v2 && !set->full_v3) { + vty_out(vty, "Full-rate codec not supported%s", VTY_NEWLINE); + return CMD_WARNING; + } + + set->half_prefer = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_codec_half, cfg_ms_codec_half_cmd, "codec half-speed", + "Enable codec\nHalf speed speech codec") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + if (!set->half_v1 && !set->half_v3) { + vty_out(vty, "Half-rate codec not supported%s", VTY_NEWLINE); + return CMD_WARNING; + } + + set->half = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_codec_half_pref, cfg_ms_codec_half_pref_cmd, "codec half-speed " + "prefer", + "Enable codec\nHalf speed speech codec\nPrefer this codec") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + if (!set->half_v1 && !set->half_v3) { + vty_out(vty, "Half-rate codec not supported%s", VTY_NEWLINE); + return CMD_WARNING; + } + + set->half = 1; + set->half_prefer = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_codec_half, cfg_ms_no_codec_half_cmd, "no codec half-speed", + NO_STR "Disable codec\nHalf speed speech codec") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + if (!set->half_v1 && !set->half_v3) { + vty_out(vty, "Half-rate codec not supported%s", VTY_NEWLINE); + return CMD_WARNING; + } + + set->half = 0; + set->half_prefer = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_abbrev, cfg_ms_abbrev_cmd, "abbrev ABBREVIATION NUMBER [NAME]", + "Store given abbreviation number\n1-3 digits abbreviation\n" + "Number to store for the abbreviation " + "(Use digits '0123456789*#abc', and '+' to dial international)\n" + "Name of the abbreviation") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + struct gsm_settings_abbrev *abbrev; + int i; + + llist_for_each_entry(abbrev, &set->abbrev, list) { + if (!strcmp(argv[0], abbrev->abbrev)) { + vty_out(vty, "Given abbreviation '%s' already stored, " + "delete first!%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + } + + if (strlen(argv[0]) >= sizeof(abbrev->abbrev)) { + vty_out(vty, "Given abbreviation too long%s", VTY_NEWLINE); + return CMD_WARNING; + } + + for (i = 0; i < strlen(argv[0]); i++) { + if (argv[0][i] < '0' || argv[0][i] > '9') { + vty_out(vty, "Given abbreviation must have digits " + "0..9 only!%s", VTY_NEWLINE); + return CMD_WARNING; + } + } + + if (vty_check_number(vty, argv[1])) + return CMD_WARNING; + + abbrev = talloc_zero(l23_ctx, struct gsm_settings_abbrev); + if (!abbrev) { + vty_out(vty, "No Memory!%s", VTY_NEWLINE); + return CMD_WARNING; + } + llist_add_tail(&abbrev->list, &set->abbrev); + strncpy(abbrev->abbrev, argv[0], sizeof(abbrev->abbrev) - 1); + strncpy(abbrev->number, argv[1], sizeof(abbrev->number) - 1); + if (argc >= 3) + strncpy(abbrev->name, argv[2], sizeof(abbrev->name) - 1); + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_abbrev, cfg_ms_no_abbrev_cmd, "no abbrev [ABBREVIATION]", + NO_STR "Remove given abbreviation number or all numbers\n" + "Abbreviation number to remove") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + struct gsm_settings_abbrev *abbrev, *abbrev2; + uint8_t deleted = 0; + + llist_for_each_entry_safe(abbrev, abbrev2, &set->abbrev, list) { + if (argc < 1 || !strcmp(argv[0], abbrev->abbrev)) { + llist_del(&abbrev->list); + deleted = 1; + } + } + + if (argc >= 1 && !deleted) { + vty_out(vty, "Given abbreviation '%s' not found!%s", + argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_neighbour, cfg_ms_neighbour_cmd, "neighbour-measurement", + "Allow neighbour cell measurement in idle mode") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->no_neighbour = 0; + + vty_restart_if_started(vty, ms); + + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_no_neighbour, cfg_ms_no_neighbour_cmd, "no neighbour-measurement", + NO_STR "Do not allow neighbour cell measurement in idle mode") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->no_neighbour = 1; + + vty_restart_if_started(vty, ms); + + return CMD_SUCCESS; +} + +static int config_write_dummy(struct vty *vty) +{ + return CMD_SUCCESS; +} + +/* per support config */ +DEFUN(cfg_ms_support, cfg_ms_support_cmd, "support", + "Define supported features") +{ + vty->node = SUPPORT_NODE; + + return CMD_SUCCESS; +} + +#define SUP_EN(cfg, cfg_cmd, item, cmd, desc, restart) \ +DEFUN(cfg, cfg_cmd, cmd, "Enable " desc "support") \ +{ \ + struct osmocom_ms *ms = vty->index; \ + struct gsm_settings *set = &ms->settings; \ + struct gsm_support *sup = &ms->support; \ + if (!sup->item) { \ + vty_out(vty, desc " not supported%s", VTY_NEWLINE); \ + if (vty_reading) \ + return CMD_SUCCESS; \ + return CMD_WARNING; \ + } \ + if (restart) \ + vty_restart(vty, ms); \ + set->item = 1; \ + return CMD_SUCCESS; \ +} + +#define SUP_DI(cfg, cfg_cmd, item, cmd, desc, restart) \ +DEFUN(cfg, cfg_cmd, "no " cmd, NO_STR "Disable " desc " support") \ +{ \ + struct osmocom_ms *ms = vty->index; \ + struct gsm_settings *set = &ms->settings; \ + struct gsm_support *sup = &ms->support; \ + if (!sup->item) { \ + vty_out(vty, desc " not supported%s", VTY_NEWLINE); \ + if (vty_reading) \ + return CMD_SUCCESS; \ + return CMD_WARNING; \ + } \ + if (restart) \ + vty_restart(vty, ms); \ + set->item = 0; \ + return CMD_SUCCESS; \ +} + +#define SET_EN(cfg, cfg_cmd, item, cmd, desc, restart) \ +DEFUN(cfg, cfg_cmd, cmd, "Enable " desc "support") \ +{ \ + struct osmocom_ms *ms = vty->index; \ + struct gsm_settings *set = &ms->settings; \ + if (restart) \ + vty_restart(vty, ms); \ + set->item = 1; \ + return CMD_SUCCESS; \ +} + +#define SET_DI(cfg, cfg_cmd, item, cmd, desc, restart) \ +DEFUN(cfg, cfg_cmd, "no " cmd, NO_STR "Disable " desc " support") \ +{ \ + struct osmocom_ms *ms = vty->index; \ + struct gsm_settings *set = &ms->settings; \ + if (restart) \ + vty_restart(vty, ms); \ + set->item = 0; \ + return CMD_SUCCESS; \ +} + +SET_EN(cfg_ms_sup_dtmf, cfg_ms_sup_dtmf_cmd, cc_dtmf, "dtmf", "DTMF", 0); +SET_DI(cfg_ms_sup_no_dtmf, cfg_ms_sup_no_dtmf_cmd, cc_dtmf, "dtmf", "DTMF", 0); +SUP_EN(cfg_ms_sup_sms, cfg_ms_sup_sms_cmd, sms_ptp, "sms", "SMS", 0); +SUP_DI(cfg_ms_sup_no_sms, cfg_ms_sup_no_sms_cmd, sms_ptp, "sms", "SMS", 0); +SUP_EN(cfg_ms_sup_a5_1, cfg_ms_sup_a5_1_cmd, a5_1, "a5/1", "A5/1", 0); +SUP_DI(cfg_ms_sup_no_a5_1, cfg_ms_sup_no_a5_1_cmd, a5_1, "a5/1", "A5/1", 0); +SUP_EN(cfg_ms_sup_a5_2, cfg_ms_sup_a5_2_cmd, a5_2, "a5/2", "A5/2", 0); +SUP_DI(cfg_ms_sup_no_a5_2, cfg_ms_sup_no_a5_2_cmd, a5_2, "a5/2", "A5/2", 0); +SUP_EN(cfg_ms_sup_a5_3, cfg_ms_sup_a5_3_cmd, a5_3, "a5/3", "A5/3", 0); +SUP_DI(cfg_ms_sup_no_a5_3, cfg_ms_sup_no_a5_3_cmd, a5_3, "a5/3", "A5/3", 0); +SUP_EN(cfg_ms_sup_a5_4, cfg_ms_sup_a5_4_cmd, a5_4, "a5/4", "A5/4", 0); +SUP_DI(cfg_ms_sup_no_a5_4, cfg_ms_sup_no_a5_4_cmd, a5_4, "a5/4", "A5/4", 0); +SUP_EN(cfg_ms_sup_a5_5, cfg_ms_sup_a5_5_cmd, a5_5, "a5/5", "A5/5", 0); +SUP_DI(cfg_ms_sup_no_a5_5, cfg_ms_sup_no_a5_5_cmd, a5_5, "a5/5", "A5/5", 0); +SUP_EN(cfg_ms_sup_a5_6, cfg_ms_sup_a5_6_cmd, a5_6, "a5/6", "A5/6", 0); +SUP_DI(cfg_ms_sup_no_a5_6, cfg_ms_sup_no_a5_6_cmd, a5_6, "a5/6", "A5/6", 0); +SUP_EN(cfg_ms_sup_a5_7, cfg_ms_sup_a5_7_cmd, a5_7, "a5/7", "A5/7", 0); +SUP_DI(cfg_ms_sup_no_a5_7, cfg_ms_sup_no_a5_7_cmd, a5_7, "a5/7", "A5/7", 0); +SUP_EN(cfg_ms_sup_p_gsm, cfg_ms_sup_p_gsm_cmd, p_gsm, "p-gsm", "P-GSM (900)", + 1); +SUP_DI(cfg_ms_sup_no_p_gsm, cfg_ms_sup_no_p_gsm_cmd, p_gsm, "p-gsm", + "P-GSM (900)", 1); +SUP_EN(cfg_ms_sup_e_gsm, cfg_ms_sup_e_gsm_cmd, e_gsm, "e-gsm", "E-GSM (850)", + 1); +SUP_DI(cfg_ms_sup_no_e_gsm, cfg_ms_sup_no_e_gsm_cmd, e_gsm, "e-gsm", + "E-GSM (850)", 1); +SUP_EN(cfg_ms_sup_r_gsm, cfg_ms_sup_r_gsm_cmd, r_gsm, "r-gsm", "R-GSM (850)", + 1); +SUP_DI(cfg_ms_sup_no_r_gsm, cfg_ms_sup_no_r_gsm_cmd, r_gsm, "r-gsm", + "R-GSM (850)", 1); +SUP_EN(cfg_ms_sup_dcs, cfg_ms_sup_dcs_cmd, dcs, "dcs", "DCS (1800)", 1); +SUP_DI(cfg_ms_sup_no_dcs, cfg_ms_sup_no_dcs_cmd, dcs, "dcs", "DCS (1800)", 1); +SUP_EN(cfg_ms_sup_gsm_850, cfg_ms_sup_gsm_850_cmd, gsm_850, "gsm-850", + "GSM 850", 1); +SUP_DI(cfg_ms_sup_no_gsm_850, cfg_ms_sup_no_gsm_850_cmd, gsm_850, "gsm-850", + "GSM 850", 1); +SUP_EN(cfg_ms_sup_pcs, cfg_ms_sup_pcs_cmd, pcs, "pcs", "PCS (1900)", 1); +SUP_DI(cfg_ms_sup_no_pcs, cfg_ms_sup_no_pcs_cmd, pcs, "pcs", "PCS (1900)", 1); +SUP_EN(cfg_ms_sup_gsm_480, cfg_ms_sup_gsm_480_cmd, gsm_480, "gsm-480", + "GSM 480", 1); +SUP_DI(cfg_ms_sup_no_gsm_480, cfg_ms_sup_no_gsm_480_cmd, gsm_480, "gsm-480", + "GSM 480", 1); +SUP_EN(cfg_ms_sup_gsm_450, cfg_ms_sup_gsm_450_cmd, gsm_450, "gsm-450", + "GSM 450", 1); +SUP_DI(cfg_ms_sup_no_gsm_450, cfg_ms_sup_no_gsm_450_cmd, gsm_450, "gsm-450", + "GSM 450", 1); + +DEFUN(cfg_ms_sup_class_900, cfg_ms_sup_class_900_cmd, "class-900 (1|2|3|4|5)", + "Select power class for GSM 900\n" + "20 Watts\n" + "8 Watts\n" + "5 Watts\n" + "2 Watts\n" + "0.8 Watts") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + struct gsm_support *sup = &ms->support; + + set->class_900 = atoi(argv[0]); + + if (set->class_900 < sup->class_900 && !vty_reading) + vty_out(vty, "Note: You selected a higher class than supported " + " by hardware!%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_sup_class_850, cfg_ms_sup_class_850_cmd, "class-850 (1|2|3|4|5)", + "Select power class for GSM 850\n" + "20 Watts\n" + "8 Watts\n" + "5 Watts\n" + "2 Watts\n" + "0.8 Watts") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + struct gsm_support *sup = &ms->support; + + set->class_850 = atoi(argv[0]); + + if (set->class_850 < sup->class_850 && !vty_reading) + vty_out(vty, "Note: You selected a higher class than supported " + " by hardware!%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_sup_class_400, cfg_ms_sup_class_400_cmd, "class-400 (1|2|3|4|5)", + "Select power class for GSM 400 (480 and 450)\n" + "20 Watts\n" + "8 Watts\n" + "5 Watts\n" + "2 Watts\n" + "0.8 Watts") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + struct gsm_support *sup = &ms->support; + + set->class_400 = atoi(argv[0]); + + if (set->class_400 < sup->class_400 && !vty_reading) + vty_out(vty, "Note: You selected a higher class than supported " + " by hardware!%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_sup_class_dcs, cfg_ms_sup_class_dcs_cmd, "class-dcs (1|2|3)", + "Select power class for DCS 1800\n" + "1 Watt\n" + "0.25 Watts\n" + "4 Watts") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + struct gsm_support *sup = &ms->support; + + set->class_dcs = atoi(argv[0]); + + if (((set->class_dcs + 1) & 3) < ((sup->class_dcs + 1) & 3) + && !vty_reading) + vty_out(vty, "Note: You selected a higher class than supported " + " by hardware!%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_sup_class_pcs, cfg_ms_sup_class_pcs_cmd, "class-pcs (1|2|3)", + "Select power class for PCS 1900\n" + "1 Watt\n" + "0.25 Watts\n" + "2 Watts") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + struct gsm_support *sup = &ms->support; + + set->class_pcs = atoi(argv[0]); + + if (((set->class_pcs + 1) & 3) < ((sup->class_pcs + 1) & 3) + && !vty_reading) + vty_out(vty, "Note: You selected a higher class than supported " + " by hardware!%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_sup_ch_cap, cfg_ms_sup_ch_cap_cmd, "channel-capability " + "(sdcch|sdcch+tchf|sdcch+tchf+tchh)", + "Select channel capability\nSDCCH only\nSDCCH + TCH/F\nSDCCH + TCH/H") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + struct gsm_support *sup = &ms->support; + uint8_t ch_cap; + + if (!strcmp(argv[0], "sdcch+tchf+tchh")) + ch_cap = GSM_CAP_SDCCH_TCHF_TCHH; + else if (!strcmp(argv[0], "sdcch+tchf")) + ch_cap = GSM_CAP_SDCCH_TCHF; + else + ch_cap = GSM_CAP_SDCCH; + + if (ch_cap > sup->ch_cap && !vty_reading) { + vty_out(vty, "You selected an higher capability than supported " + " by hardware!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (ms->started && ch_cap != set->ch_cap + && (ch_cap == GSM_CAP_SDCCH || set->ch_cap == GSM_CAP_SDCCH)) + vty_restart_if_started(vty, ms); + + set->ch_cap = ch_cap; + + return CMD_SUCCESS; +} + +SUP_EN(cfg_ms_sup_full_v1, cfg_ms_sup_full_v1_cmd, full_v1, "full-speech-v1", + "Full rate speech V1", 0); +SUP_DI(cfg_ms_sup_no_full_v1, cfg_ms_sup_no_full_v1_cmd, full_v1, + "full-speech-v1", "Full rate speech V1", 0); +SUP_EN(cfg_ms_sup_full_v2, cfg_ms_sup_full_v2_cmd, full_v2, "full-speech-v2", + "Full rate speech V2 (EFR)", 0); +SUP_DI(cfg_ms_sup_no_full_v2, cfg_ms_sup_no_full_v2_cmd, full_v2, + "full-speech-v2", "Full rate speech V2 (EFR)", 0); +SUP_EN(cfg_ms_sup_full_v3, cfg_ms_sup_full_v3_cmd, full_v3, "full-speech-v3", + "Full rate speech V3 (AMR)", 0); +SUP_DI(cfg_ms_sup_no_full_v3, cfg_ms_sup_no_full_v3_cmd, full_v3, + "full-speech-v3", "Full rate speech V3 (AMR)", 0); +SUP_EN(cfg_ms_sup_half_v1, cfg_ms_sup_half_v1_cmd, half_v1, "half-speech-v1", + "Half rate speech V1", 0); +SUP_DI(cfg_ms_sup_no_half_v1, cfg_ms_sup_no_half_v1_cmd, half_v1, + "half-speech-v1", "Half rate speech V1", 0); +SUP_EN(cfg_ms_sup_half_v3, cfg_ms_sup_half_v3_cmd, half_v3, "half-speech-v3", + "Half rate speech V3 (AMR)", 0); +SUP_DI(cfg_ms_sup_no_half_v3, cfg_ms_sup_no_half_v3_cmd, half_v3, + "half-speech-v3", "Half rate speech V3 (AMR)", 0); + +DEFUN(cfg_ms_sup_min_rxlev, cfg_ms_sup_min_rxlev_cmd, "min-rxlev <-110--47>", + "Set the minimum receive level to select a cell\n" + "Minimum receive level from -110 dBm to -47 dBm") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->min_rxlev_db = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_sup_dsc_max, cfg_ms_sup_dsc_max_cmd, "dsc-max <90-500>", + "Set the maximum DSC value. Standard is 90. Increase to make mobile " + "more reliable against bad RX signal. This increase the propability " + "of missing a paging requests\n" + "DSC initial and maximum value (standard is 90)") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->dsc_max = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_sup_skip_max_per_band, cfg_ms_sup_skip_max_per_band_cmd, + "skip-max-per-band", + "Scan all frequencies per band, not only a maximum number") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->skip_max_per_band = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_sup_no_skip_max_per_band, cfg_ms_sup_no_skip_max_per_band_cmd, + "no skip-max-per-band", + NO_STR "Scan only a maximum number of frequencies per band") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->skip_max_per_band = 0; + + return CMD_SUCCESS; +} + +/* per testsim config */ +DEFUN(cfg_ms_testsim, cfg_ms_testsim_cmd, "test-sim", + "Configure test SIM emulation") +{ + vty->node = TESTSIM_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_test_imsi, cfg_test_imsi_cmd, "imsi IMSI", + "Set IMSI on test card\n15 digits IMSI") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + char *error = gsm_check_imsi(argv[0]); + + if (error) { + vty_out(vty, "%s%s", error, VTY_NEWLINE); + return CMD_WARNING; + } + + strcpy(set->test_imsi, argv[0]); + + vty_restart_if_started(vty, ms); + + return CMD_SUCCESS; +} + +#define HEX_STR "\nByte as two digits hexadecimal" +DEFUN(cfg_test_ki_xor, cfg_test_ki_xor_cmd, "ki xor HEX HEX HEX HEX HEX HEX " + "HEX HEX HEX HEX HEX HEX", + "Set Key (Kc) on test card\nUse XOR algorithm" HEX_STR HEX_STR HEX_STR + HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR) +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + uint8_t ki[12]; + const char *p; + int i; + + for (i = 0; i < 12; i++) { + p = argv[i]; + if (!strncmp(p, "0x", 2)) + p += 2; + if (strlen(p) != 2) { + vty_out(vty, "Expecting two digits hex value (with or " + "without 0x in front)%s", VTY_NEWLINE); + return CMD_WARNING; + } + ki[i] = strtoul(p, NULL, 16); + } + + set->test_ki_type = GSM_SIM_KEY_XOR; + memcpy(set->test_ki, ki, 12); + return CMD_SUCCESS; +} + +DEFUN(cfg_test_ki_comp128, cfg_test_ki_comp128_cmd, "ki comp128 HEX HEX HEX " + "HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX HEX", + "Set Key (Kc) on test card\nUse XOR algorithm" HEX_STR HEX_STR HEX_STR + HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR HEX_STR + HEX_STR HEX_STR HEX_STR HEX_STR) +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + uint8_t ki[16]; + const char *p; + int i; + + for (i = 0; i < 16; i++) { + p = argv[i]; + if (!strncmp(p, "0x", 2)) + p += 2; + if (strlen(p) != 2) { + vty_out(vty, "Expecting two digits hex value (with or " + "without 0x in front)%s", VTY_NEWLINE); + return CMD_WARNING; + } + ki[i] = strtoul(p, NULL, 16); + } + + set->test_ki_type = GSM_SIM_KEY_COMP128; + memcpy(set->test_ki, ki, 16); + return CMD_SUCCESS; +} + +DEFUN(cfg_test_barr, cfg_test_barr_cmd, "barred-access", + "Allow access to barred cells") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->test_barr = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_test_no_barr, cfg_test_no_barr_cmd, "no barred-access", + NO_STR "Deny access to barred cells") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->test_barr = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_test_no_rplmn, cfg_test_no_rplmn_cmd, "no rplmn", + NO_STR "Unset Registered PLMN") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->test_rplmn_valid = 0; + + vty_restart_if_started(vty, ms); + + return CMD_SUCCESS; +} + +DEFUN(cfg_test_rplmn, cfg_test_rplmn_cmd, "rplmn MCC MNC [LAC] [TMSI]", + "Set Registered PLMN\nMobile Country Code\nMobile Network Code\n" + "Optionally set locatio area code\n" + "Optionally set current assigned TMSI") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + uint16_t mcc = gsm_input_mcc((char *)argv[0]), + mnc = gsm_input_mnc((char *)argv[1]); + + if (mcc == GSM_INPUT_INVALID) { + vty_out(vty, "Given MCC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (mnc == GSM_INPUT_INVALID) { + vty_out(vty, "Given MNC invalid%s", VTY_NEWLINE); + return CMD_WARNING; + } + set->test_rplmn_valid = 1; + set->test_rplmn_mcc = mcc; + set->test_rplmn_mnc = mnc; + + if (argc >= 3) + set->test_lac = strtoul(argv[2], NULL, 16); + else + set->test_lac = 0xfffe; + + if (argc >= 4) + set->test_tmsi = strtoul(argv[3], NULL, 16); + else + set->test_tmsi = 0xffffffff; + + vty_restart_if_started(vty, ms); + + return CMD_SUCCESS; +} + +DEFUN(cfg_test_hplmn, cfg_test_hplmn_cmd, "hplmn-search (everywhere|foreign-country)", + "Set Home PLMN search mode\n" + "Search for HPLMN when on any other network\n" + "Search for HPLMN when in a different country") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + switch (argv[0][0]) { + case 'e': + set->test_always = 1; + break; + case 'f': + set->test_always = 0; + break; + } + + vty_restart_if_started(vty, ms); + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_shutdown, cfg_ms_no_shutdown_cmd, "no shutdown", + NO_STR "Activate and run MS") +{ + struct osmocom_ms *ms = vty->index, *tmp; + int rc; + + if (ms->shutdown != 2) + return CMD_SUCCESS; + + llist_for_each_entry(tmp, &ms_list, entity) { + if (tmp->shutdown == 2) + continue; + if (!strcmp(ms->settings.layer2_socket_path, + tmp->settings.layer2_socket_path)) { + vty_out(vty, "Cannot start MS '%s', because MS '%s' " + "use the same layer2-socket.%sPlease shutdown " + "MS '%s' first.%s", ms->name, tmp->name, + VTY_NEWLINE, tmp->name, VTY_NEWLINE); + return CMD_WARNING; + } + if (!strcmp(ms->settings.sap_socket_path, + tmp->settings.sap_socket_path)) { + vty_out(vty, "Cannot start MS '%s', because MS '%s' " + "use the same sap-socket.%sPlease shutdown " + "MS '%s' first.%s", ms->name, tmp->name, + VTY_NEWLINE, tmp->name, VTY_NEWLINE); + return CMD_WARNING; + } + } + + rc = mobile_init(ms); + if (rc < 0) { + vty_out(vty, "Connection to layer 1 failed!%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_shutdown, cfg_ms_shutdown_cmd, "shutdown", + "Shut down and deactivate MS") +{ + struct osmocom_ms *ms = vty->index; + + if (ms->shutdown == 0) + mobile_exit(ms, 0); + + return CMD_SUCCESS; +} + +DEFUN(cfg_shutdown_force, cfg_ms_shutdown_force_cmd, "shutdown force", + "Shut down and deactivate MS\nDo not perform IMSI detach") +{ + struct osmocom_ms *ms = vty->index; + + if (ms->shutdown <= 1) + mobile_exit(ms, 1); + + return CMD_SUCCESS; +} + +enum node_type ms_vty_go_parent(struct vty *vty) +{ + switch (vty->node) { + case MS_NODE: + vty->node = CONFIG_NODE; + vty->index = NULL; + break; + case TESTSIM_NODE: + case SUPPORT_NODE: + vty->node = MS_NODE; + break; + default: + vty->node = CONFIG_NODE; + } + + return vty->node; +} + +/* Down vty node level. */ +gDEFUN(ournode_exit, + ournode_exit_cmd, "exit", "Exit current mode and down to previous mode\n") +{ + switch (vty->node) { + case MS_NODE: + vty->node = CONFIG_NODE; + vty->index = NULL; + break; + case TESTSIM_NODE: + case SUPPORT_NODE: + vty->node = MS_NODE; + break; + default: + break; + } + return CMD_SUCCESS; +} + +/* End of configuration. */ +gDEFUN(ournode_end, + ournode_end_cmd, "end", "End current mode and change to enable mode.") +{ + switch (vty->node) { + case VIEW_NODE: + case ENABLE_NODE: + /* Nothing to do. */ + break; + case CONFIG_NODE: + case VTY_NODE: + case MS_NODE: + case TESTSIM_NODE: + case SUPPORT_NODE: + vty_config_unlock(vty); + vty->node = ENABLE_NODE; + vty->index = NULL; + vty->index_sub = NULL; + break; + default: + break; + } + return CMD_SUCCESS; +} + +DEFUN(off, off_cmd, "off", + "Turn mobiles off (shutdown) and exit") +{ + osmo_signal_dispatch(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL); + + return CMD_SUCCESS; +} + +#define SUP_NODE(item) \ + install_element(SUPPORT_NODE, &cfg_ms_sup_item_cmd); + +int ms_vty_init(void) +{ + install_element_ve(&show_ms_cmd); + install_element_ve(&show_subscr_cmd); + install_element_ve(&show_support_cmd); + install_element_ve(&show_cell_cmd); + install_element_ve(&show_cell_si_cmd); + install_element_ve(&show_nbcells_cmd); + install_element_ve(&show_ba_cmd); + install_element_ve(&show_forb_la_cmd); + install_element_ve(&show_forb_plmn_cmd); + install_element_ve(&monitor_network_cmd); + install_element_ve(&no_monitor_network_cmd); + install_element(ENABLE_NODE, &off_cmd); + + install_element(ENABLE_NODE, &sim_test_cmd); + install_element(ENABLE_NODE, &sim_reader_cmd); + install_element(ENABLE_NODE, &sim_remove_cmd); + install_element(ENABLE_NODE, &sim_pin_cmd); + install_element(ENABLE_NODE, &sim_disable_pin_cmd); + install_element(ENABLE_NODE, &sim_enable_pin_cmd); + install_element(ENABLE_NODE, &sim_change_pin_cmd); + install_element(ENABLE_NODE, &sim_unblock_pin_cmd); + install_element(ENABLE_NODE, &sim_lai_cmd); + install_element(ENABLE_NODE, &network_search_cmd); + install_element(ENABLE_NODE, &network_show_cmd); + install_element(ENABLE_NODE, &network_select_cmd); + install_element(ENABLE_NODE, &call_cmd); + install_element(ENABLE_NODE, &call_retr_cmd); + install_element(ENABLE_NODE, &call_dtmf_cmd); + install_element(ENABLE_NODE, &sms_cmd); + install_element(ENABLE_NODE, &service_cmd); + install_element(ENABLE_NODE, &test_reselection_cmd); + install_element(ENABLE_NODE, &delete_forbidden_plmn_cmd); + +#ifdef _HAVE_GPSD + install_element(CONFIG_NODE, &cfg_gps_host_cmd); +#endif + install_element(CONFIG_NODE, &cfg_gps_device_cmd); + install_element(CONFIG_NODE, &cfg_gps_baud_cmd); + install_element(CONFIG_NODE, &cfg_gps_enable_cmd); + install_element(CONFIG_NODE, &cfg_no_gps_enable_cmd); + + install_element(CONFIG_NODE, &cfg_hide_default_cmd); + install_element(CONFIG_NODE, &cfg_no_hide_default_cmd); + + install_element(CONFIG_NODE, &cfg_ms_cmd); + install_element(CONFIG_NODE, &cfg_ms_create_cmd); + install_element(CONFIG_NODE, &cfg_ms_rename_cmd); + install_element(CONFIG_NODE, &cfg_no_ms_cmd); + install_element(CONFIG_NODE, &ournode_end_cmd); + install_node(&ms_node, config_write); + install_default(MS_NODE); + install_element(MS_NODE, &ournode_exit_cmd); + install_element(MS_NODE, &ournode_end_cmd); + install_element(MS_NODE, &cfg_ms_show_this_cmd); + install_element(MS_NODE, &cfg_ms_layer2_cmd); + install_element(MS_NODE, &cfg_ms_sap_cmd); + install_element(MS_NODE, &cfg_ms_sim_cmd); + install_element(MS_NODE, &cfg_ms_mode_cmd); + install_element(MS_NODE, &cfg_ms_imei_cmd); + install_element(MS_NODE, &cfg_ms_imei_fixed_cmd); + install_element(MS_NODE, &cfg_ms_imei_random_cmd); + install_element(MS_NODE, &cfg_ms_no_emerg_imsi_cmd); + install_element(MS_NODE, &cfg_ms_emerg_imsi_cmd); + install_element(MS_NODE, &cfg_ms_no_sms_sca_cmd); + install_element(MS_NODE, &cfg_ms_sms_sca_cmd); + install_element(MS_NODE, &cfg_ms_cw_cmd); + install_element(MS_NODE, &cfg_ms_no_cw_cmd); + install_element(MS_NODE, &cfg_ms_auto_answer_cmd); + install_element(MS_NODE, &cfg_ms_no_auto_answer_cmd); + install_element(MS_NODE, &cfg_ms_force_rekey_cmd); + install_element(MS_NODE, &cfg_ms_no_force_rekey_cmd); + install_element(MS_NODE, &cfg_ms_clip_cmd); + install_element(MS_NODE, &cfg_ms_clir_cmd); + install_element(MS_NODE, &cfg_ms_no_clip_cmd); + install_element(MS_NODE, &cfg_ms_no_clir_cmd); + install_element(MS_NODE, &cfg_ms_tx_power_cmd); + install_element(MS_NODE, &cfg_ms_tx_power_val_cmd); + install_element(MS_NODE, &cfg_ms_sim_delay_cmd); + install_element(MS_NODE, &cfg_ms_no_sim_delay_cmd); + install_element(MS_NODE, &cfg_ms_stick_cmd); + install_element(MS_NODE, &cfg_ms_no_stick_cmd); + install_element(MS_NODE, &cfg_ms_lupd_cmd); + install_element(MS_NODE, &cfg_ms_no_lupd_cmd); + install_element(MS_NODE, &cfg_ms_codec_full_cmd); + install_element(MS_NODE, &cfg_ms_codec_full_pref_cmd); + install_element(MS_NODE, &cfg_ms_codec_half_cmd); + install_element(MS_NODE, &cfg_ms_codec_half_pref_cmd); + install_element(MS_NODE, &cfg_ms_no_codec_half_cmd); + install_element(MS_NODE, &cfg_ms_abbrev_cmd); + install_element(MS_NODE, &cfg_ms_no_abbrev_cmd); + install_element(MS_NODE, &cfg_ms_testsim_cmd); + install_element(MS_NODE, &cfg_ms_neighbour_cmd); + install_element(MS_NODE, &cfg_ms_no_neighbour_cmd); + install_element(MS_NODE, &cfg_ms_support_cmd); + install_node(&support_node, config_write_dummy); + install_default(SUPPORT_NODE); + install_element(SUPPORT_NODE, &ournode_exit_cmd); + install_element(SUPPORT_NODE, &ournode_end_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_dtmf_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_dtmf_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_sms_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_sms_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_a5_1_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_a5_1_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_a5_2_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_a5_2_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_a5_3_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_a5_3_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_a5_4_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_a5_4_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_a5_5_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_a5_5_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_a5_6_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_a5_6_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_a5_7_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_a5_7_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_p_gsm_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_p_gsm_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_e_gsm_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_e_gsm_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_r_gsm_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_r_gsm_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_dcs_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_dcs_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_gsm_850_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_gsm_850_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_pcs_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_pcs_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_gsm_480_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_gsm_480_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_gsm_450_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_gsm_450_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_class_900_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_class_dcs_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_class_850_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_class_pcs_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_class_400_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_ch_cap_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_full_v1_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_full_v1_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_full_v2_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_full_v2_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_full_v3_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_full_v3_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_half_v1_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_half_v1_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_half_v3_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_half_v3_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_min_rxlev_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_dsc_max_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_skip_max_per_band_cmd); + install_element(SUPPORT_NODE, &cfg_ms_sup_no_skip_max_per_band_cmd); + install_node(&testsim_node, config_write_dummy); + install_default(TESTSIM_NODE); + install_element(TESTSIM_NODE, &ournode_exit_cmd); + install_element(TESTSIM_NODE, &ournode_end_cmd); + install_element(TESTSIM_NODE, &cfg_test_imsi_cmd); + install_element(TESTSIM_NODE, &cfg_test_ki_xor_cmd); + install_element(TESTSIM_NODE, &cfg_test_ki_comp128_cmd); + install_element(TESTSIM_NODE, &cfg_test_barr_cmd); + install_element(TESTSIM_NODE, &cfg_test_no_barr_cmd); + install_element(TESTSIM_NODE, &cfg_test_no_rplmn_cmd); + install_element(TESTSIM_NODE, &cfg_test_rplmn_cmd); + install_element(TESTSIM_NODE, &cfg_test_hplmn_cmd); + install_element(MS_NODE, &cfg_ms_shutdown_cmd); + install_element(MS_NODE, &cfg_ms_shutdown_force_cmd); + install_element(MS_NODE, &cfg_ms_no_shutdown_cmd); + + return 0; +} + +void vty_notify(struct osmocom_ms *ms, const char *fmt, ...) +{ + struct telnet_connection *connection; + char buffer[1000]; + va_list args; + struct vty *vty; + + if (fmt) { + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer) - 1, fmt, args); + buffer[sizeof(buffer) - 1] = '\0'; + va_end(args); + + if (!buffer[0]) + return; + } + + llist_for_each_entry(connection, &active_connections, entry) { + vty = connection->vty; + if (!vty) + continue; + if (!fmt) { + vty_out(vty, "%s%% (MS %s)%s", VTY_NEWLINE, ms->name, + VTY_NEWLINE); + continue; + } + if (buffer[strlen(buffer) - 1] == '\n') { + buffer[strlen(buffer) - 1] = '\0'; + vty_out(vty, "%% %s%s", buffer, VTY_NEWLINE); + buffer[strlen(buffer)] = '\n'; + } else + vty_out(vty, "%% %s", buffer); + } +} + |