/* Cell Monitor of Free Software for Calypso Phone */ /* (C) 2012 by Andreas Eversberg * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum key_codes key_code = KEY_INV; int key_pressed = 0; enum key_codes key_pressed_code; unsigned long key_pressed_when; unsigned int key_pressed_delay; enum mode { MODE_MAIN, MODE_SPECTRUM, MODE_ARFCN, MODE_SYNC, MODE_RACH, } mode = MODE_MAIN; enum mode last_mode; /* where to return after entering ARFCN */ static uint16_t arfcn = 0, ul_arfcn; int pcs = 0; int uplink = 0; int max = 0; uint8_t power, max_power; char input[5]; int cursor; char *sync_result = NULL; char *sync_msg = ""; static struct band { int min, max, prev, next, freq_ul, freq_dl; } bands[] = { { 128, 251, 124, 512, 8242, 8692 }, /* GSM 850 */ { 955, 124, 885, 128, 8762, 9212 }, /* P,E,R GSM */ { 512, 885, 251, 955, 17102, 18052 }, /* DCS 1800 */ { 0, 0, 0, 0, 0, 0}, }; struct band *band; #define PCS_MIN 512 #define PCS_MAX 810 #define DCS_MIN 512 #define DCS_MAX 885 #define PCS_UL 18502 #define PCS_DL 19302 enum pm_mode { PM_IDLE, PM_SENT, PM_RANGE_SENT, PM_RANGE_RESULT, PM_RESULT, } pm_mode = PM_IDLE; #define NUM_PM_DL 2 #define NUM_PM_UL 10 int pm_meas[NUM_PM_UL]; int pm_count = 0; int pm_max = 2; uint8_t pm_spectrum[1024]; int pm_scale = 1; /* scale measured power level */ #define TONE_JIFFIES ((HZ < 25) ? 1 : HZ / 25) int tone = 0; unsigned long tone_time; int tone_on = 0; uint8_t bsic; uint8_t ul_levels[8], ul_max[8]; /* 8 uplink levels */ uint8_t si_1[23]; uint8_t si_2[23]; uint8_t si_2bis[23]; uint8_t si_2ter[23]; uint8_t si_3[23]; uint8_t si_4[23]; uint16_t si_new = 0, ul_new; uint16_t mcc, mnc, lac, cell_id; int ccch_conf; int nb_num; struct gsm_sysinfo_freq freq[1024]; #define NEIGH_LINES ((framebuffer->height - 25) / 8) #define FREQ_TYPE_SERV 0x01 /* frequency of the serving cell */ #define FREQ_TYPE_NCELL 0x1c /* frequency of the neighbor cell */ #define FREQ_TYPE_NCELL_2 0x04 /* sub channel of SI 2 */ #define FREQ_TYPE_NCELL_2bis 0x08 /* sub channel of SI 2bis */ #define FREQ_TYPE_NCELL_2ter 0x10 /* sub channel of SI 2ter */ int rach = 0; struct gsm48_req_ref rach_ref; uint8_t rach_ra; unsigned long rach_when; uint8_t ta; enum assign { ASSIGN_NONE, ASSIGN_NO_TX, ASSIGN_RESULT, ASSIGN_REJECT, ASSIGN_TIMEOUT, } assign; /* UI */ static void print_display(char *text, int *y, int c) { /* skip lines, given by cursor */ (*y)++; if (c >= (*y)) return; /* skip, if end of display area is reached */ if ((*y) - c > NEIGH_LINES) return; fb_gotoxy(0, 20 + (((*y) - c - 1) << 3)); fb_putstr(text, framebuffer->width); } static void refresh_display(void) { char text[16]; int bat = battery_info.battery_percent; fb_clear(); /* header */ fb_setbg(FB_COLOR_WHITE); if (mode != MODE_SPECTRUM && !(mode == MODE_SYNC && cursor < 0)) { fb_setfg(FB_COLOR_BLUE); fb_setfont(FB_FONT_HELVR08); fb_gotoxy(0, 7); fb_putstr("Osmocom RSSI", -1); fb_setfg(FB_COLOR_RGB(0xc0, 0xc0, 0x00)); fb_setfont(FB_FONT_SYMBOLS); fb_gotoxy(framebuffer->width - 15, 8); if (bat >= 100 && (battery_info.flags & BATTERY_CHG_ENABLED) && !(battery_info.flags & BATTERY_CHARGING)) fb_putstr("@HHBC", framebuffer->width); else { sprintf(text, "@%c%c%cC", (bat >= 30) ? 'B':'A', (bat >= 60) ? 'B':'A', (bat >= 90) ? 'B':'A'); fb_putstr(text, framebuffer->width); } fb_gotoxy(0, 8); sprintf(text, "%c%cE%c%c", (power >= 40) ? 'D':'G', (power >= 10) ? 'D':'G', (power >= 10) ? 'F':'G', (power >= 40) ? 'F':'G'); fb_putstr(text, framebuffer->width); fb_setfg(FB_COLOR_GREEN); fb_gotoxy(0, 10); fb_boxto(framebuffer->width - 1, 10); } fb_setfg(FB_COLOR_BLACK); fb_setfont(FB_FONT_C64); /* RACH */ if (mode == MODE_RACH) { unsigned long elapsed = jiffies - rach_when; fb_gotoxy(0,28); switch (assign) { case ASSIGN_NONE: fb_putstr("Rach sent...", -1); break; case ASSIGN_RESULT: sprintf(text, "TA = %d", ta); fb_putstr(text, -1); fb_gotoxy(0,36); sprintf(text, "(%dm)", ta * 554); fb_putstr(text, -1); break; case ASSIGN_REJECT: fb_putstr("Rejected!", -1); break; case ASSIGN_NO_TX: fb_putstr("TX disabled", -1); break; case ASSIGN_TIMEOUT: fb_putstr("Timeout", -1); break; } switch (assign) { case ASSIGN_RESULT: case ASSIGN_REJECT: fb_gotoxy(0,44); sprintf(text, "Delay:%ldms", elapsed * 1000 / HZ); fb_putstr(text, -1); break; default: break; } } /* SYNC / UL levels */ if (mode == MODE_SYNC && cursor < 0) { int i, tn, l; int offset = (framebuffer->width - 96) >> 1; int height = framebuffer->height - 25; fb_setfont(FB_FONT_HELVR08); for (i = 0; i < 8; i++) { if (uplink) tn = (i + 3) & 7; /* UL is shifted by 3 */ else tn = i; fb_setbg(FB_COLOR_WHITE); fb_gotoxy(offset + 12 * i, 7); l = (max) ? ul_max[tn] : ul_levels[tn]; l = 110 - l; if (l >= 100) l -= 100; sprintf(text, "%02d", l); fb_putstr(text, framebuffer->width); fb_setbg(FB_COLOR_BLACK); fb_gotoxy(offset + 3 + 12 * i, height + 10); fb_boxto(offset + 3 + 12 * i + 5, height + 10 - ul_levels[tn] * height / 64); if (max) { fb_gotoxy(offset + 3 + 12 * i, height + 10 - ul_max[tn] * height / 64); fb_boxto(offset + 3 + 12 * i + 5, height + 10 - ul_max[tn] * height / 64); } } fb_setbg(FB_COLOR_TRANSP); if (max) { fb_setfg(FB_COLOR_RED); fb_gotoxy(framebuffer->width - 16, 15); fb_putstr("max", framebuffer->width); } fb_setfont(FB_FONT_C64); fb_setfg(FB_COLOR_BLUE); fb_gotoxy(0, 16); if (pcs && ul_arfcn >= PCS_MIN && ul_arfcn <= PCS_MAX) sprintf(text, "%4dP", ul_arfcn); else if (ul_arfcn >= DCS_MIN && ul_arfcn <= DCS_MAX) sprintf(text, "%4dD", ul_arfcn); else sprintf(text, "%4d ", ul_arfcn); fb_putstr(text, framebuffer->width); fb_setbg(FB_COLOR_WHITE); fb_setfg(FB_COLOR_BLACK); } /* SYNC / SI */ if (mode == MODE_SYNC && cursor == 0) { fb_gotoxy(0, 20); if (sync_msg[0] == 'o') sprintf(text, "BSIC%d/%d %4d", bsic >> 3, bsic & 7, power - 110); else sprintf(text, "Sync %s", sync_msg); fb_putstr(text, -1); fb_gotoxy(0,28); text[0] = si_1[2] ? '1' : '-'; text[1] = ' '; text[2] = si_2[2] ? '2' : '-'; text[3] = ' '; text[4] = si_2bis[2] ? '2' : '-'; text[5] = si_2bis[2] ? 'b' : ' '; text[6] = si_2ter[2] ? '2' : '-'; text[7] = si_2ter[2] ? 't' : ' '; text[8] = ' '; text[9] = si_3[2] ? '3' : '-'; text[10] = ' '; text[11] = si_4[2] ? '4' : '-'; text[12] = '\0'; fb_putstr(text, -1); fb_gotoxy(0, 36); fb_putstr("MCC MNC LAC ", -1); fb_gotoxy(0, 44); if (mcc) { if ((mnc & 0x00f) == 0x00f) sprintf(text, "%3x %02x %04x", mcc, mnc >> 4, lac); else sprintf(text, "%3x %03x %04x", mcc, mnc, lac); fb_putstr(text, -1); } else fb_putstr("--- --- ----", -1); fb_gotoxy(0, 52); if (si_3[2]) { sprintf(text, "cell id:%04x", cell_id); fb_putstr(text, -1); } else fb_putstr("cell id:----", -1); } /* SYNC / neighbour cells */ if (mode == MODE_SYNC && cursor > 0) { int i, y = 0; text[0] = '\0'; for (i = 0; i < 1024; i++) { if (freq[i].mask & FREQ_TYPE_SERV) { if (!text[0]) sprintf(text, "S: %4d", i); else { sprintf(text + 7, " %4d", i); print_display(text, &y, cursor - 1); text[0] = '\0'; } } } if (text[0]) print_display(text, &y, cursor - 1); text[0] = '\0'; for (i = 0; i < 1024; i++) { if (freq[i].mask & FREQ_TYPE_NCELL) { if (!text[0]) sprintf(text, "N: %4d", i); else { sprintf(text + 7, " %4d", i); print_display(text, &y, cursor - 1); text[0] = '\0'; } } } if (text[0]) print_display(text, &y, cursor - 1); nb_num = y; } /* ARFCN */ if (mode == MODE_MAIN || mode == MODE_ARFCN) { fb_gotoxy(0, 20); if (mode == MODE_ARFCN) sprintf(text, "ARFCN %s", input); else if (pcs && arfcn >= PCS_MIN && arfcn <= PCS_MAX) sprintf(text, "ARFCN %dPCS", arfcn); else if (arfcn >= DCS_MIN && arfcn <= DCS_MAX) sprintf(text, "ARFCN %dDCS", arfcn); else sprintf(text, "ARFCN %d", arfcn); fb_putstr(text,framebuffer->width); } /* cursor */ if (mode == MODE_ARFCN) { fb_setfg(FB_COLOR_WHITE); fb_setbg(FB_COLOR_BLUE); fb_putstr(" ", framebuffer->width); fb_setfg(FB_COLOR_BLACK); fb_setbg(FB_COLOR_WHITE); } /* Frequency / power */ if (mode == MODE_MAIN) { int f; if (pcs && arfcn >= PCS_MIN && arfcn <= PCS_MAX) { if (uplink) f = PCS_UL; else f = PCS_DL; } else if (uplink) f = band->freq_ul; else f = band->freq_dl; f += ((arfcn - band->min) & 1023) << 1; fb_gotoxy(0, 30); sprintf(text, "Freq. %d.%d", f / 10, f % 10); fb_putstr(text,framebuffer->width); fb_gotoxy(0, 40); sprintf(text, "Power %d", ((max) ? max_power : power) - 110); fb_putstr(text, framebuffer->width); if (max) { fb_setfont(FB_FONT_HELVR08); fb_setfg(FB_COLOR_RED); fb_gotoxy(framebuffer->width - 16, 39); fb_putstr("max", framebuffer->width); fb_setfont(FB_FONT_C64); fb_setfg(FB_COLOR_BLACK); } fb_setbg(FB_COLOR_BLACK); fb_gotoxy(0, 45); fb_boxto(framebuffer->width * power / 64, 50); if (max) { fb_gotoxy(framebuffer->width * max_power / 64 ,45); fb_boxto(framebuffer->width * max_power / 64, 50); } fb_setbg(FB_COLOR_WHITE); } /* spectrum */ if (mode == MODE_SPECTRUM) { int i; uint16_t a, e, p; int height = framebuffer->height - 25; fb_gotoxy(0, 8); if (pcs && arfcn >= PCS_MIN && arfcn <= PCS_MAX) sprintf(text, "%4dP", arfcn); else if (arfcn >= DCS_MIN && arfcn <= DCS_MAX) sprintf(text, "%4dD", arfcn); else sprintf(text, "%4d ", arfcn); sprintf(text + 5, " %4d", pm_spectrum[arfcn & 1023] - 110); fb_putstr(text, -1); fb_setfg(FB_COLOR_RED); if (max) { fb_setfont(FB_FONT_HELVR08); fb_gotoxy(framebuffer->width - 16,15); fb_putstr("max", framebuffer->width); fb_setfont(FB_FONT_C64); } if (pm_scale != 1) { fb_setfont(FB_FONT_HELVR08); fb_gotoxy(1, 15); sprintf(text, "x%d", pm_scale); fb_putstr(text, framebuffer->width); fb_setfont(FB_FONT_C64); } fb_setfg(FB_COLOR_BLACK); if (pcs && arfcn >= PCS_MIN && arfcn <= PCS_MAX) { a = PCS_MIN; e = PCS_MAX; } else { a = band->min; e = band->max; } for (i = 0; i < framebuffer->width; i++) { p = (arfcn + i - (framebuffer->width >> 1)) & 1023; if ((((p - a) & 1023) & 512)) continue; if ((((e - p) & 1023) & 512)) continue; p = (pm_spectrum[p] * pm_scale * height / 64); if (p > height) p = height; if (i == (framebuffer->width >> 1)) fb_setfg(FB_COLOR_RED); fb_gotoxy(i, height + 10 - p); fb_boxto(i, height + 10); if (i == (framebuffer->width >> 1)) fb_setfg(FB_COLOR_BLACK); } i = framebuffer->width >> 1; fb_gotoxy(i, 0); fb_boxto(i, 4); fb_gotoxy(i, height + 10); fb_boxto(i, height + 14); } /* footer */ fb_setfg(FB_COLOR_GREEN); fb_gotoxy(0, framebuffer->height - 10); fb_boxto(framebuffer->width-1, framebuffer->height - 10); fb_gotoxy(0, framebuffer->height - 1); fb_setfg(FB_COLOR_RED); if (mode == MODE_ARFCN) sprintf(text, "%s %s", (cursor) ? "del " : "back", (cursor) ? "enter" : " "); else if (mode == MODE_SYNC && cursor < 0) sprintf(text, "%s %s", "back", (uplink) ? "UL" : "DL"); else if (mode == MODE_SYNC || mode == MODE_RACH) sprintf(text, "%s ", "back"); else sprintf(text, "%s %s", (pcs) ? "PCS" : "DCS", (uplink) ? "UL" : "DL"); fb_putstr(text, -1); fb_setfg(FB_COLOR_BLACK); fb_setfont(FB_FONT_HELVR08); fb_gotoxy(0, framebuffer->height - 2); sprintf(text, "%d", tone / 25); fb_putstr(text, -1); fb_flush(); } static void exit_arfcn(void) { mode = last_mode; refresh_display(); } static void enter_arfcn(enum key_codes code) { /* enter mode */ if (mode != MODE_ARFCN) { last_mode = mode; mode = MODE_ARFCN; input[0] = code - KEY_0 + '0'; input[1] = '\0'; cursor = 1; refresh_display(); return; } if (code == KEY_LEFT_SB) { /* back */ if (cursor == 0) { exit_arfcn(); return; } /* delete */ cursor--; input[cursor] = '\0'; refresh_display(); return; } if (code == KEY_RIGHT_SB) { int check = 0; int i; struct band *temp = NULL; /* nothing entered */ if (cursor == 0) { return; } for (i = 0; i < cursor; i++) check = (check << 3) + (check << 1) + input[i] - '0'; /* check */ for (i = 0; bands[i].max; i++) { temp = &bands[i]; if (temp->min < temp->max) { if (check >= temp->min && check <= temp->max) break; } else { if (check >= temp->min || check <= temp->max) break; } } if (!bands[i].max) return; if (check > 1023) return; arfcn = check; band = temp; mode = last_mode; refresh_display(); return; } if (cursor == 4) return; input[cursor] = code - KEY_0 + '0'; cursor++; input[cursor] = '\0'; refresh_display(); } static int inc_dec_arfcn(int inc) { int i; /* select current band */ for (i = 0; bands[i].max; i++) { band = &bands[i]; if (band->min < band->max) { if (arfcn >= band->min && arfcn <= band->max) break; } else { if (arfcn >= band->min || arfcn <= band->max) break; } } if (!bands[i].max) return -EINVAL; if (inc) { if (arfcn == band->max) arfcn = band->next; else if (arfcn == 1023) arfcn = 0; else arfcn++; } else { if (arfcn == band->min) arfcn = band->prev; else if (arfcn == 0) arfcn = 1023; else arfcn--; } /* select next band */ for (i = 0; bands[i].max; i++) { band = &bands[i]; if (band->min < band->max) { if (arfcn >= band->min && arfcn <= band->max) break; } else { if (arfcn >= band->min || arfcn <= band->max) break; } } if (!bands[i].max) return -EINVAL; refresh_display(); return 0; } static void request_ul_levels(uint16_t a); static int inc_dec_ul_arfcn(int inc) { uint16_t a; /* loop until we hit a serving cell or our current bcch arfcn */ if (inc) { for (a = (ul_arfcn + 1) & 1023; a != (arfcn & 1023); a = (a + 1) & 1023) { if ((freq[a].mask & FREQ_TYPE_SERV)) break; } } else { for (a = (ul_arfcn - 1) & 1023; a != (arfcn & 1023); a = (a - 1) & 1023) { if ((freq[a].mask & FREQ_TYPE_SERV)) break; } } ul_arfcn = a; refresh_display(); request_ul_levels(a); return 0; } static void toggle_dcs_pcs(void) { pcs = !pcs; refresh_display(); } static void toggle_up_down(void) { uplink = !uplink; refresh_display(); if (mode == MODE_SYNC && cursor < 0) request_ul_levels(ul_arfcn); } static void toggle_spectrum(void) { if (mode == MODE_MAIN) { mode = MODE_SPECTRUM; pm_mode = PM_IDLE; } else if (mode == MODE_SPECTRUM) { mode = MODE_MAIN; pm_mode = PM_IDLE; } l1s_reset(); l1s_reset_hw(); pm_count = 0; refresh_display(); } static void tone_inc_dec(int inc) { if (inc) { if (tone + 25 <= 255) tone += 25; } else { if (tone - 25 >= 0) tone -= 25; } refresh_display(); } static void hold_max(void) { max = !max; max_power = power; refresh_display(); } static int inc_dec_neighbour(int inc) { if (inc) { if (cursor > 0 && cursor - 1 >= (nb_num - NEIGH_LINES)) return -EINVAL; cursor++; } else { if (cursor < 0) return -EINVAL; cursor--; } refresh_display(); return 0; } static int inc_dec_spectrum(int inc) { if (inc) { pm_scale <<= 1; if (pm_scale > 8) pm_scale = 8; } else { pm_scale >>= 1; if (pm_scale < 1) pm_scale = 1; } refresh_display(); return 0; } static void enter_sync(void); static void exit_sync(void); static void enter_rach(void); static void exit_rach(void); static void handle_key_code() { /* key repeat */ if (key_pressed) { unsigned long elapsed = jiffies - key_pressed_when; if (elapsed > key_pressed_delay) { key_pressed_when = jiffies; key_pressed_delay = HZ / 10; /* only repeat these keys */ if (key_pressed_code == KEY_LEFT || key_pressed_code == KEY_RIGHT) key_code = key_pressed_code; } } if (key_code == KEY_INV) return; /* do later, do not disturb tone */ if (tone_on) return; switch (key_code) { case KEY_0: case KEY_1: case KEY_2: case KEY_3: case KEY_4: case KEY_5: case KEY_6: case KEY_7: case KEY_8: case KEY_9: if (mode == MODE_MAIN || mode == MODE_SPECTRUM || mode == MODE_ARFCN) enter_arfcn(key_code); break; case KEY_UP: if (mode == MODE_MAIN) tone_inc_dec(1); else if (mode == MODE_SYNC) inc_dec_neighbour(0); else if (mode == MODE_SPECTRUM) inc_dec_spectrum(1); break; case KEY_DOWN: if (mode == MODE_MAIN) tone_inc_dec(0); else if (mode == MODE_SYNC) inc_dec_neighbour(1); else if (mode == MODE_SPECTRUM) inc_dec_spectrum(0); break; case KEY_RIGHT: if (mode == MODE_MAIN || mode == MODE_SPECTRUM) inc_dec_arfcn(1); else if (mode == MODE_SYNC && cursor < 0) inc_dec_ul_arfcn(1); break; case KEY_LEFT: if (mode == MODE_MAIN || mode == MODE_SPECTRUM) inc_dec_arfcn(0); else if (mode == MODE_SYNC && cursor < 0) inc_dec_ul_arfcn(0); break; case KEY_LEFT_SB: if (mode == MODE_MAIN || mode == MODE_SPECTRUM) toggle_dcs_pcs(); else if (mode == MODE_ARFCN) enter_arfcn(key_code); else if (mode == MODE_SYNC) exit_sync(); else if (mode == MODE_RACH) exit_rach(); break; case KEY_RIGHT_SB: if (mode == MODE_MAIN || mode == MODE_SPECTRUM) toggle_up_down(); else if (mode == MODE_ARFCN) enter_arfcn(key_code); else if (mode == MODE_SYNC && cursor < 0) toggle_up_down(); break; case KEY_OK: if (mode == MODE_MAIN || mode == MODE_SPECTRUM) enter_sync(); else if (mode == MODE_SYNC || mode == MODE_RACH) enter_rach(); break; case KEY_MENU: hold_max(); break; case KEY_POWER: if (mode == MODE_ARFCN) exit_arfcn(); else if (mode == MODE_SYNC) exit_sync(); else if (mode == MODE_RACH) exit_rach(); else if (mode == MODE_SPECTRUM) toggle_spectrum(); break; case KEY_STAR: if (mode == MODE_MAIN || mode == MODE_SPECTRUM) toggle_spectrum(); break; default: break; } key_code = KEY_INV; } static void handle_tone(void) { unsigned long elapsed = jiffies - tone_time; if (!tone_on) { if (!tone || mode != MODE_MAIN) return; /* wait depending on power level */ if (elapsed < (uint8_t)(63-power)) return; buzzer_volume(tone); buzzer_note(NOTE(NOTE_C, OCTAVE_5)); tone_time = jiffies; tone_on = 1; return; } if (elapsed >= TONE_JIFFIES) { tone_on = 0; tone_time = jiffies; buzzer_volume(0); } } /* PM handling */ static void handle_pm(void) { /* start power measurement */ if (pm_mode == PM_IDLE && (mode == MODE_MAIN || mode == MODE_SPECTRUM)) { struct msgb *msg = l1ctl_msgb_alloc(L1CTL_PM_REQ); struct l1ctl_pm_req *pm; uint16_t a, e; pm = (struct l1ctl_pm_req *) msgb_put(msg, sizeof(*pm)); pm->type = 1; if (mode == MODE_MAIN) { a = arfcn; if (pcs && arfcn >= PCS_MIN && arfcn <= PCS_MAX) a |= ARFCN_PCS; if (uplink) a |= ARFCN_UPLINK; e = a; pm_mode = PM_SENT; } if (mode == MODE_SPECTRUM) { if (pcs && arfcn >= PCS_MIN && arfcn <= PCS_MAX) { a = PCS_MIN | ARFCN_PCS; e = PCS_MAX | ARFCN_PCS; } else { a = band->min; e = band->max; } pm_mode = PM_RANGE_SENT; } if (uplink) { a |= ARFCN_UPLINK; e |= ARFCN_UPLINK; } pm->range.band_arfcn_from = htons(a); pm->range.band_arfcn_to = htons(e); l1a_l23_rx(SC_DLCI_L1A_L23, msg); return; } if (pm_mode == PM_RESULT) { pm_mode = PM_IDLE; if (pm_count == pm_max) { int i = 0; int sum = 0; if (uplink) { /* find max */ for (i = 0; i < pm_count; i++) { if (pm_meas[i] > sum) sum = pm_meas[i]; } power = sum; } else { for (i = 0; i < pm_count; i++) sum += pm_meas[i]; power = sum / pm_count; } if (power > max_power) max_power = power; pm_count = 0; pm_max = (uplink) ? NUM_PM_UL : NUM_PM_DL; if (!tone_on) refresh_display(); } return; } if (pm_mode == PM_RANGE_RESULT) { pm_mode = PM_IDLE; refresh_display(); buzzer_volume(tone); buzzer_note(NOTE(NOTE_C, OCTAVE_5)); tone_time = jiffies; tone_on = 1; return; } } /* sync / SI */ static void enter_sync(void) { struct msgb *msg = l1ctl_msgb_alloc(L1CTL_FBSB_REQ); struct l1ctl_fbsb_req *req; uint16_t a = arfcn; l1s_reset(); l1s_reset_hw(); pm_count = 0; pm_mode = PM_IDLE; req = (struct l1ctl_fbsb_req *) msgb_put(msg, sizeof(*req)); if (pcs && arfcn >= PCS_MIN && arfcn <= PCS_MAX) a |= ARFCN_PCS; req->band_arfcn = htons(a); req->timeout = htons(100); /* 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 = L1CTL_FBSB_F_FB01SB; req->sync_info_idx = 0; req->ccch_mode = CCCH_MODE_NONE; l1a_l23_rx(SC_DLCI_L1A_L23, msg); mode = MODE_SYNC; memset(ul_levels, 0, sizeof(ul_levels)); si_new = 0; ul_new = 0; ul_arfcn = arfcn; si_1[2] = 0; si_2[2] = 0; si_2bis[2] = 0; si_2ter[2] = 0; si_3[2] = 0; si_4[2] = 0; mcc = mnc = lac = 0; ccch_conf = -1; memset(freq, 0, sizeof(freq)); cursor = 0; nb_num = 0; sync_msg = "trying"; refresh_display(); } static void exit_sync(void) { l1s_reset(); l1s_reset_hw(); pm_count = 0; pm_mode = PM_IDLE; mode = MODE_MAIN; } int gsm48_decode_lai(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; } static void request_ul_levels(uint16_t a) { struct msgb *msg = l1ctl_msgb_alloc(L1CTL_NEIGH_PM_REQ); struct l1ctl_neigh_pm_req *pm_req = (struct l1ctl_neigh_pm_req *) msgb_put(msg, sizeof(*pm_req)); int i; if (pcs && a >= PCS_MIN && a <= PCS_MAX) a |= ARFCN_PCS; if (uplink) a |= ARFCN_UPLINK; pm_req->n = 8; for (i = 0; i < 8; i++) { pm_req->band_arfcn[i] = htons(a); pm_req->tn[i] = i; } l1a_l23_rx(SC_DLCI_L1A_L23, msg); } static void handle_sync(void) { struct gsm48_system_information_type_1 *si1; struct gsm48_system_information_type_2 *si2; struct gsm48_system_information_type_2bis *si2bis; struct gsm48_system_information_type_2ter *si2ter; struct gsm48_system_information_type_3 *si3; struct gsm48_system_information_type_4 *si4; if (mode != MODE_SYNC) return; /* once we synced, we take the result and request UL measurement */ if (sync_result) { uint16_t a = ul_arfcn; sync_msg = sync_result; sync_result = NULL; refresh_display(); if (sync_msg[0] != 'o') return; request_ul_levels(a); return; } if (tone_on) return; /* no UL result, no SI result */ if (!ul_new && !(si_new & 0x100)) return; /* new UL result */ if (ul_new) { ul_new = 0; if (cursor < 0) refresh_display(); return; } /* decode si */ switch (si_new & 0xff) { case GSM48_MT_RR_SYSINFO_1: si1 = (struct gsm48_system_information_type_1 *)si_1; gsm48_decode_freq_list(freq, si1->cell_channel_description, sizeof(si1->cell_channel_description), 0xce, FREQ_TYPE_SERV); break; case GSM48_MT_RR_SYSINFO_2: si2 = (struct gsm48_system_information_type_2 *)si_2; gsm48_decode_freq_list(freq, si2->bcch_frequency_list, sizeof(si2->bcch_frequency_list), 0xce, FREQ_TYPE_NCELL_2); break; case GSM48_MT_RR_SYSINFO_2bis: si2bis = (struct gsm48_system_information_type_2bis *)si_2bis; gsm48_decode_freq_list(freq, si2bis->bcch_frequency_list, sizeof(si2bis->bcch_frequency_list), 0xce, FREQ_TYPE_NCELL_2bis); break; case GSM48_MT_RR_SYSINFO_2ter: si2ter = (struct gsm48_system_information_type_2ter *)si_2ter; gsm48_decode_freq_list(freq, si2ter->ext_bcch_frequency_list, sizeof(si2ter->ext_bcch_frequency_list), 0x8e, FREQ_TYPE_NCELL_2ter); break; case GSM48_MT_RR_SYSINFO_3: si3 = (struct gsm48_system_information_type_3 *)si_3; gsm48_decode_lai(&si3->lai, &mcc, &mnc, &lac); cell_id = ntohs(si3->cell_identity); if (ccch_conf < 0) { struct msgb *msg = l1ctl_msgb_alloc(L1CTL_CCCH_MODE_REQ); struct l1ctl_ccch_mode_req *req = (struct l1ctl_ccch_mode_req *) msgb_put(msg, sizeof(*req)); ccch_conf = si3->control_channel_desc.ccch_conf; req->ccch_mode = (ccch_conf == 1) ? CCCH_MODE_COMBINED : CCCH_MODE_NON_COMBINED; printf("ccch_mode=%d\n", ccch_conf); l1a_l23_rx(SC_DLCI_L1A_L23, msg); } break; case GSM48_MT_RR_SYSINFO_4: si4 = (struct gsm48_system_information_type_4 *)si_4; gsm48_decode_lai(&si4->lai, &mcc, &mnc, &lac); break; } if (cursor >= 0) refresh_display(); /* tone depends on successfully received BCCH */ buzzer_volume(tone); tone_time = jiffies; tone_on = 1; if ((si_new & 0xff) == 0xff) buzzer_note(NOTE(NOTE_C, OCTAVE_2)); else buzzer_note(NOTE(NOTE_C, OCTAVE_5)); si_new = 0; } static void enter_rach(void) { if (ccch_conf < 0) return; if (rach) return; #ifndef CONFIG_TX_ENABLE assign = ASSIGN_NO_TX; mode = MODE_RACH; /* display refresh is done by rach handler */ #else struct msgb *msg1 = l1ctl_msgb_alloc(L1CTL_NEIGH_PM_REQ); struct msgb *msg2 = l1ctl_msgb_alloc(L1CTL_RACH_REQ); struct l1ctl_neigh_pm_req *pm_req = (struct l1ctl_neigh_pm_req *) msgb_put(msg1, sizeof(*pm_req)); struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) msgb_put(msg2, sizeof(*ul));; struct l1ctl_rach_req *rach_req = (struct l1ctl_rach_req *) msgb_put(msg2, sizeof(*rach_req)); l1s.tx_power = 0; pm_req->n = 0; /* disable */ rach_ra = 0x00; rach_req->ra = rach_ra; rach_req->offset = 0; rach_req->combined = (ccch_conf == 1); l1a_l23_rx(SC_DLCI_L1A_L23, msg1); l1a_l23_rx(SC_DLCI_L1A_L23, msg2); rach = 1; rach_when = jiffies; assign = ASSIGN_NONE; mode = MODE_RACH; refresh_display(); #endif } static void exit_rach(void) { rach = 0; request_ul_levels(ul_arfcn); mode = MODE_SYNC; refresh_display(); } static void handle_assign(void) { if (mode != MODE_RACH) return; if (assign == ASSIGN_NONE) { unsigned long elapsed = jiffies - rach_when; if (!rach) return; if (elapsed < HZ * 2) return; assign = ASSIGN_TIMEOUT; rach = 0; } refresh_display(); assign = ASSIGN_NONE; } /* Main Program */ const char *hr = "======================================================================\n"; /* match request reference agains request history */ static int gsm48_match_ra(struct gsm48_req_ref *ref) { uint8_t ia_t1, ia_t2, ia_t3; uint8_t cr_t1, cr_t2, cr_t3; if (rach && ref->ra == rach_ra) { ia_t1 = ref->t1; ia_t2 = ref->t2; ia_t3 = (ref->t3_high << 3) | ref->t3_low; ref = &rach_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) return 1; } return 0; } /* note: called from IRQ context */ static void rx_imm_ass(struct msgb *msg) { struct gsm48_imm_ass *ia = msgb_l3(msg); if (gsm48_match_ra(&ia->req_ref)) { assign = ASSIGN_RESULT; ta = ia->timing_advance; rach = 0; } } /* note: called from IRQ context */ static void rx_imm_ass_ext(struct msgb *msg) { struct gsm48_imm_ass_ext *ia = msgb_l3(msg); if (gsm48_match_ra(&ia->req_ref1)) { assign = ASSIGN_RESULT; ta = ia->timing_advance1; rach = 0; } if (gsm48_match_ra(&ia->req_ref2)) { assign = ASSIGN_RESULT; ta = ia->timing_advance2; rach = 0; } } /* note: called from IRQ context */ static void rx_imm_ass_rej(struct msgb *msg) { struct gsm48_imm_ass_rej *ia = msgb_l3(msg); struct gsm48_req_ref *req_ref; int i; for (i = 0; i < 4; i++) { /* request reference */ req_ref = (struct gsm48_req_ref *) (((uint8_t *)&ia->req_ref1) + i * 4); if (gsm48_match_ra(req_ref)) { assign = ASSIGN_REJECT; rach = 0; } } } /* note: called from IRQ context */ static void rx_pch_agch(struct msgb *msg) { struct gsm48_system_information_type_header *sih; /* store SI */ sih = msgb_l3(msg); switch (sih->system_information) { case GSM48_MT_RR_IMM_ASS: rx_imm_ass(msg); break; case GSM48_MT_RR_IMM_ASS_EXT: rx_imm_ass_ext(msg); break; case GSM48_MT_RR_IMM_ASS_REJ: rx_imm_ass_rej(msg); break; } } /* note: called from IRQ context */ static void rx_bcch(struct msgb *msg) { struct gsm48_system_information_type_header *sih; /* store SI */ sih = msgb_l3(msg); switch (sih->system_information) { case GSM48_MT_RR_SYSINFO_1: memcpy(si_1, msgb_l3(msg), msgb_l3len(msg)); break; case GSM48_MT_RR_SYSINFO_2: memcpy(si_2, msgb_l3(msg), msgb_l3len(msg)); break; case GSM48_MT_RR_SYSINFO_2bis: memcpy(si_2bis, msgb_l3(msg), msgb_l3len(msg)); break; case GSM48_MT_RR_SYSINFO_2ter: memcpy(si_2ter, msgb_l3(msg), msgb_l3len(msg)); break; case GSM48_MT_RR_SYSINFO_3: memcpy(si_3, msgb_l3(msg), msgb_l3len(msg)); break; case GSM48_MT_RR_SYSINFO_4: memcpy(si_4, msgb_l3(msg), msgb_l3len(msg)); break; } si_new = sih->system_information | 0x100; } /* note: called from IRQ context */ static void l1a_l23_tx(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->l1h; struct l1ctl_pm_conf *pmr; struct l1ctl_info_dl *dl; struct l1ctl_fbsb_conf *sb; uint8_t chan_type, chan_ts, chan_ss; struct l1ctl_neigh_pm_ind *pm_ind; struct gsm_time tm; switch (l1h->msg_type) { case L1CTL_PM_CONF: if (pm_mode == PM_SENT) { pmr = (struct l1ctl_pm_conf *) l1h->data; pm_meas[pm_count] = pmr->pm[0]; pm_count++; pm_mode = PM_RESULT; } if (pm_mode == PM_RANGE_SENT) { for (pmr = (struct l1ctl_pm_conf *) l1h->data; (uint8_t *) pmr < msg->tail; pmr++) { if (!max || pm_spectrum[ntohs(pmr->band_arfcn) & 1023] < pmr->pm[0]) pm_spectrum[ntohs(pmr->band_arfcn) & 1023] = pmr->pm[0]; } if ((l1h->flags & L1CTL_F_DONE)) pm_mode = PM_RANGE_RESULT; } l1s.tpu_offset_correction += 5000 / NUM_PM_UL; break; case L1CTL_FBSB_CONF: dl = (struct l1ctl_info_dl *) l1h->data; sb = (struct l1ctl_fbsb_conf *) dl->payload; if (sb->result == 0) sync_result = "ok"; else sync_result = "error"; bsic = sb->bsic; break; case L1CTL_DATA_IND: dl = (struct l1ctl_info_dl *) l1h->data; msg->l2h = dl->payload; rsl_dec_chan_nr(dl->chan_nr, &chan_type, &chan_ss, &chan_ts); power = dl->rx_level; if (dl->fire_crc >= 2) { if (chan_type == RSL_CHAN_BCCH) si_new = 0x1ff; /* error frame indication */ break; /* free, but don't send to sercom */ } switch (chan_type) { case RSL_CHAN_BCCH: msg->l3h = msg->l2h; rx_bcch(msg); break; case RSL_CHAN_PCH_AGCH: msg->l3h = msg->l2h; rx_pch_agch(msg); break; } sercomm_sendmsg(SC_DLCI_L1A_L23, msg); return; /* msg is freed by sercom */ case L1CTL_NEIGH_PM_IND: for (pm_ind = (struct l1ctl_neigh_pm_ind *) l1h->data; (uint8_t *) pm_ind < msg->tail; pm_ind++) { ul_levels[pm_ind->tn] = pm_ind->pm[0]; /* hold max only, if max enabled and level is lower */ if (!max || ul_levels[pm_ind->tn] > ul_max[pm_ind->tn]) ul_max[pm_ind->tn] = ul_levels[pm_ind->tn]; if (pm_ind->tn == 7) ul_new = 1; } break; case L1CTL_RACH_CONF: dl = (struct l1ctl_info_dl *) l1h->data; gsm_fn2gsmtime(&tm, ntohl(dl->frame_nr)); rach_ref.t1 = tm.t1; rach_ref.t2 = tm.t2; rach_ref.t3_low = tm.t3 & 0x7; rach_ref.t3_high = tm.t3 >> 3; break; } msgb_free(msg); } static void console_rx_cb(uint8_t dlci, struct msgb *msg) { if (dlci != SC_DLCI_CONSOLE) { printf("Message for unknown DLCI %u\n", dlci); return; } printf("Message on console DLCI: '%s'\n", msg->data); msgb_free(msg); } static void l1a_l23_rx_cb(uint8_t dlci, struct msgb *msg) { int i; printf("l1a_l23_rx_cb (DLCI %d): ", dlci); for (i = 0; i < msg->len; i++) printf("%02x ", msg->data[i]); puts("\n"); } static void key_handler(enum key_codes code, enum key_states state) { if (state != PRESSED) { key_pressed = 0; return; } /* key repeat */ if (!key_pressed) { key_pressed = 1; key_pressed_when = jiffies; key_pressed_code = code; key_pressed_delay = HZ * 6 / 10; } key_code = code; } int main(void) { board_init(1); puts("\n\nOsmocomBB Monitor Tool (revision " GIT_REVISION ")\n"); puts(hr); /* Dump device identification */ dump_dev_id(); puts(hr); /* Dump clock config before PLL set */ calypso_clk_dump(); puts(hr); keypad_set_handler(&key_handler); /* Dump clock config after PLL set */ calypso_clk_dump(); puts(hr); sercomm_register_rx_cb(SC_DLCI_CONSOLE, console_rx_cb); sercomm_register_rx_cb(SC_DLCI_L1A_L23, l1a_l23_rx_cb); layer1_init(); l1a_l23_tx_cb = l1a_l23_tx; // display_unset_attr(DISP_ATTR_INVERT); tpu_frame_irq_en(1, 1); buzzer_mode_pwt(1); buzzer_volume(0); memset(pm_spectrum, 0, sizeof(pm_spectrum)); memset(ul_max, 0, sizeof(ul_max)); /* inc 0 to 1 and refresh */ inc_dec_arfcn(1); while (1) { l1a_compl_execute(); osmo_timers_update(); handle_key_code(); l1a_l23_handler(); handle_pm(); handle_sync(); handle_assign(); handle_tone(); } /* NOT REACHED */ twl3025_power_off(); }