/* Synchronous part of GSM Layer 1: API to Layer2+ */ /* (C) 2010 by Holger Hans Peter Freyther * * 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. * */ #define DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* the size we will allocate struct msgb* for HDLC */ #define L3_MSG_HEAD 4 #define L3_MSG_DATA 200 #define L3_MSG_SIZE (L3_MSG_HEAD + sizeof(struct l1ctl_hdr) + L3_MSG_DATA) void (*l1a_l23_tx_cb)(struct msgb *msg) = NULL; void l1_queue_for_l2(struct msgb *msg) { if (l1a_l23_tx_cb) { l1a_l23_tx_cb(msg); return; } /* forward via serial for now */ sercomm_sendmsg(SC_DLCI_L1A_L23, msg); } enum mf_type { MFNONE, MF51, MF26ODD, MF26EVEN }; static uint32_t chan_nr2mf_task_mask(uint8_t chan_nr, uint8_t neigh_mode) { uint8_t cbits = chan_nr >> 3; uint8_t tn = chan_nr & 0x7; uint8_t lch_idx; enum mframe_task master_task = 0; uint32_t neigh_task = 0; enum mf_type multiframe = 0; if (cbits == 0x01) { lch_idx = 0; master_task = (tn & 1) ? MF_TASK_TCH_F_ODD : MF_TASK_TCH_F_EVEN; multiframe = (tn & 1) ? MF26ODD : MF26EVEN; } else if ((cbits & 0x1e) == 0x02) { lch_idx = cbits & 0x1; master_task = MF_TASK_TCH_H_0 + lch_idx; multiframe = (lch_idx & 1) ? MF26ODD : MF26EVEN; } else if ((cbits & 0x1c) == 0x04) { lch_idx = cbits & 0x3; master_task = MF_TASK_SDCCH4_0 + lch_idx; multiframe = MF51; } else if ((cbits & 0x18) == 0x08) { lch_idx = cbits & 0x7; master_task = MF_TASK_SDCCH8_0 + lch_idx; multiframe = MF51; #if 0 } else if (cbits == 0x10) { /* FIXME: when to do extended BCCH? */ master_task = MF_TASK_BCCH_NORM; } else if (cbits == 0x11 || cbits == 0x12) { /* FIXME: how to decide CCCH norm/extd? */ master_task = MF_TASK_BCCH_CCCH; #endif } switch (neigh_mode) { case NEIGH_MODE_PM: switch (multiframe) { case MF51: neigh_task = (1 << MF_TASK_NEIGH_PM51); break; case MF26EVEN: neigh_task = (1 << MF_TASK_NEIGH_PM26E); break; case MF26ODD: neigh_task = (1 << MF_TASK_NEIGH_PM26O); break; } break; } return (1 << master_task) | neigh_task; } static int chan_nr2dchan_type(uint8_t chan_nr) { uint8_t cbits = chan_nr >> 3; if (cbits == 0x01) { return GSM_DCHAN_TCH_F; } else if ((cbits & 0x1e) == 0x02) { return GSM_DCHAN_TCH_H; } else if ((cbits & 0x1c) == 0x04) { return GSM_DCHAN_SDCCH_4; } else if ((cbits & 0x18) == 0x08) { return GSM_DCHAN_SDCCH_8; } return GSM_DCHAN_UNKNOWN; } static int chan_nr_is_tch(uint8_t chan_nr) { return ((chan_nr >> 3) == 0x01 || /* TCH/F */ ((chan_nr >> 3) & 0x1e) == 0x02); /* TCH/H */ } static void audio_set_enabled(uint8_t tch_mode, uint8_t audio_mode) { if (tch_mode == GSM48_CMODE_SIGN) { twl3025_unit_enable(TWL3025_UNIT_VUL, 0); twl3025_unit_enable(TWL3025_UNIT_VDL, 0); } else { twl3025_unit_enable(TWL3025_UNIT_VUL, !!(audio_mode & AUDIO_TX_MICROPHONE)); twl3025_unit_enable(TWL3025_UNIT_VDL, !!(audio_mode & AUDIO_RX_SPEAKER)); } } struct msgb *l1ctl_msgb_alloc(uint8_t msg_type) { struct msgb *msg; struct l1ctl_hdr *l1h; msg = msgb_alloc_headroom(L3_MSG_SIZE, L3_MSG_HEAD, "l1ctl"); if (!msg) { while (1) { puts("OOPS. Out of buffers...\n"); } return NULL; } l1h = (struct l1ctl_hdr *) msgb_put(msg, sizeof(*l1h)); l1h->msg_type = msg_type; l1h->flags = 0; msg->l1h = (uint8_t *)l1h; return msg; } struct msgb *l1_create_l2_msg(int msg_type, uint32_t fn, uint16_t snr, uint16_t arfcn) { struct l1ctl_info_dl *dl; struct msgb *msg = l1ctl_msgb_alloc(msg_type); dl = (struct l1ctl_info_dl *) msgb_put(msg, sizeof(*dl)); dl->frame_nr = htonl(fn); dl->snr = snr; dl->band_arfcn = htons(arfcn); return msg; } /* receive a L1CTL_FBSB_REQ from L23 */ static void l1ctl_rx_fbsb_req(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; struct l1ctl_fbsb_req *sync_req = (struct l1ctl_fbsb_req *) l1h->data; if (sizeof(*sync_req) > msg->len) { printf("Short sync msg. %u\n", msg->len); return; } printd("L1CTL_FBSB_REQ (arfcn=%u, flags=0x%x)\n", ntohs(sync_req->band_arfcn), sync_req->flags); /* reset scheduler and hardware */ l1s_reset(); /* pre-set the CCCH mode */ l1s.serving_cell.ccch_mode = sync_req->ccch_mode; printd("Starting FCCH Recognition\n"); l1s_fbsb_req(1, sync_req); } /* receive a L1CTL_DM_EST_REQ from L23 */ static void l1ctl_rx_dm_est_req(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data; struct l1ctl_dm_est_req *est_req = (struct l1ctl_dm_est_req *) ul->payload; uint8_t old_tn; printd("L1CTL_DM_EST_REQ (arfcn=%u, chan_nr=0x%02x, tsc=%u tn=%u)\n", ntohs(est_req->h0.band_arfcn), ul->chan_nr, est_req->tsc, ul->chan_nr & 0x7); /* disable neighbour cell measurement of C0 TS 0 */ mframe_disable(MF_TASK_NEIGH_PM51_C0T0); /* configure dedicated channel state */ l1s.dedicated.chan_nr = ul->chan_nr; l1s.dedicated.type = chan_nr2dchan_type(ul->chan_nr); l1s.dedicated.tsc = est_req->tsc; old_tn = l1s.dedicated.tn; l1s.dedicated.tn = ul->chan_nr & 0x7; l1s.dedicated.h = est_req->h; if (est_req->h) { int i; l1s.dedicated.h1.hsn = est_req->h1.hsn; l1s.dedicated.h1.maio = est_req->h1.maio; l1s.dedicated.h1.n = est_req->h1.n; for (i=0; ih1.n; i++) l1s.dedicated.h1.ma[i] = ntohs(est_req->h1.ma[i]); } else { l1s.dedicated.h0.arfcn = ntohs(est_req->h0.band_arfcn); } /* TCH config */ if (chan_nr_is_tch(ul->chan_nr)) { /* Mode */ l1a_tch_mode_set(est_req->tch_mode); l1a_audio_mode_set(est_req->audio_mode); /* Sync */ l1s.tch_sync = 1; /* can be set without locking */ /* Audio path */ audio_set_enabled(est_req->tch_mode, est_req->audio_mode); } /* Handover config */ if ((est_req->flags & L1CTL_EST_F_RXONLY)) l1s.dedicated.rx_only = 1; else l1s.dedicated.rx_only = 0; /* figure out which MF tasks to enable */ l1s.neigh_pm.n = 0; l1a_mftask_set(chan_nr2mf_task_mask(ul->chan_nr, NEIGH_MODE_PM)); /* shift TPU according to chnage in TN */ if (l1s.dedicated.tn != old_tn) { l1s.tpu_offset_correction += (l1s.dedicated.tn - old_tn) * 625; printf("Shift TPU by %d TN (%d qbits)\n", l1s.dedicated.tn - old_tn, l1s.tpu_offset_correction); } } /* receive a L1CTL_DM_FREQ_REQ from L23 */ static void l1ctl_rx_dm_freq_req(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data; struct l1ctl_dm_freq_req *freq_req = (struct l1ctl_dm_freq_req *) ul->payload; printd("L1CTL_DM_FREQ_REQ (arfcn=%u, tsc=%u)\n", ntohs(freq_req->h0.band_arfcn), freq_req->tsc); /* configure dedicated channel state */ l1s.dedicated.st_tsc = freq_req->tsc; l1s.dedicated.st_h = freq_req->h; if (freq_req->h) { int i; l1s.dedicated.st_h1.hsn = freq_req->h1.hsn; l1s.dedicated.st_h1.maio = freq_req->h1.maio; l1s.dedicated.st_h1.n = freq_req->h1.n; for (i=0; ih1.n; i++) l1s.dedicated.st_h1.ma[i] = ntohs(freq_req->h1.ma[i]); } else { l1s.dedicated.st_h0.arfcn = ntohs(freq_req->h0.band_arfcn); } l1a_freq_req(ntohs(freq_req->fn)); } /* receive a L1CTL_CRYPTO_REQ from L23 */ static void l1ctl_rx_crypto_req(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data; struct l1ctl_crypto_req *cr = (struct l1ctl_crypto_req *) ul->payload; uint8_t key_len = msg->len - sizeof(*l1h) - sizeof(*ul) - sizeof(*cr); printd("L1CTL_CRYPTO_REQ (algo=A5/%u, len=%u)\n", cr->algo, key_len); if (cr->algo && key_len != 8) { printd("L1CTL_CRYPTO_REQ -> Invalid key\n"); return; } dsp_load_ciph_param(cr->algo, cr->key); } /* receive a L1CTL_DM_REL_REQ from L23 */ static void l1ctl_rx_dm_rel_req(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; printd("L1CTL_DM_REL_REQ\n"); l1a_mftask_set(0); l1s.dedicated.type = GSM_DCHAN_NONE; l1a_txq_msgb_flush(&l1s.tx_queue[L1S_CHAN_MAIN]); l1a_txq_msgb_flush(&l1s.tx_queue[L1S_CHAN_SACCH]); l1a_txq_msgb_flush(&l1s.tx_queue[L1S_CHAN_TRAFFIC]); l1a_meas_msgb_set(NULL); dsp_load_ciph_param(0, NULL); l1a_tch_mode_set(GSM48_CMODE_SIGN); audio_set_enabled(GSM48_CMODE_SIGN, 0); l1s.neigh_pm.n = 0; } /* receive a L1CTL_PARAM_REQ from L23 */ static void l1ctl_rx_param_req(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data; struct l1ctl_par_req *par_req = (struct l1ctl_par_req *) ul->payload; printd("L1CTL_PARAM_REQ (ta=%d, tx_power=%d)\n", par_req->ta, par_req->tx_power); l1s.ta = par_req->ta; l1s.tx_power = par_req->tx_power; l1s.dedicated.rx_only = 0; } /* receive a L1CTL_RACH_REQ from L23 */ static void l1ctl_rx_rach_req(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data; struct l1ctl_rach_req *rach_req = (struct l1ctl_rach_req *) ul->payload; printd("L1CTL_RACH_REQ (ra=0x%02x, offset=%d combined=%d)\n", rach_req->ra, ntohs(rach_req->offset), rach_req->combined); l1a_rach_req(ntohs(rach_req->offset), rach_req->combined, rach_req->ra); } /* receive a L1CTL_DATA_REQ from L23 */ static void l1ctl_rx_data_req(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data; struct l1ctl_data_ind *data_ind = (struct l1ctl_data_ind *) ul->payload; struct llist_head *tx_queue; printd("L1CTL_DATA_REQ (link_id=0x%02x)\n", ul->link_id); msg->l3h = data_ind->data; if (ul->link_id & 0x40) { struct gsm48_hdr *gh = (struct gsm48_hdr *)(data_ind->data + 5); if (gh->proto_discr == GSM48_PDISC_RR && gh->msg_type == GSM48_MT_RR_MEAS_REP) { printd("updating measurement report\n"); l1a_meas_msgb_set(msg); return; } tx_queue = &l1s.tx_queue[L1S_CHAN_SACCH]; } else tx_queue = &l1s.tx_queue[L1S_CHAN_MAIN]; printd("ul=%p, ul->payload=%p, data_ind=%p, data_ind->data=%p l3h=%p\n", ul, ul->payload, data_ind, data_ind->data, msg->l3h); l1a_txq_msgb_enq(tx_queue, msg); } /* receive a L1CTL_PM_REQ from L23 */ static void l1ctl_rx_pm_req(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; struct l1ctl_pm_req *pm_req = (struct l1ctl_pm_req *) l1h->data; switch (pm_req->type) { case 1: l1s.pm.mode = 1; l1s.pm.range.arfcn_start = ntohs(pm_req->range.band_arfcn_from); l1s.pm.range.arfcn_next = ntohs(pm_req->range.band_arfcn_from); l1s.pm.range.arfcn_end = ntohs(pm_req->range.band_arfcn_to); printf("L1CTL_PM_REQ start=%u end=%u\n", l1s.pm.range.arfcn_start, l1s.pm.range.arfcn_end); break; } l1s_reset_hw(); /* must reset, otherwise measurement results are delayed */ l1s_pm_test(1, l1s.pm.range.arfcn_next); } /* Transmit a L1CTL_RESET_IND or L1CTL_RESET_CONF */ void l1ctl_tx_reset(uint8_t msg_type, uint8_t reset_type) { struct msgb *msg = l1ctl_msgb_alloc(msg_type); struct l1ctl_reset *reset_resp; reset_resp = (struct l1ctl_reset *) msgb_put(msg, sizeof(*reset_resp)); reset_resp->type = reset_type; l1_queue_for_l2(msg); } /* receive a L1CTL_RESET_REQ from L23 */ static void l1ctl_rx_reset_req(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; struct l1ctl_reset *reset_req = (struct l1ctl_reset *) l1h->data; switch (reset_req->type) { case L1CTL_RES_T_FULL: printf("L1CTL_RESET_REQ: FULL!\n"); l1s_reset(); l1s_reset_hw(); audio_set_enabled(GSM48_CMODE_SIGN, 0); l1ctl_tx_reset(L1CTL_RESET_CONF, reset_req->type); break; case L1CTL_RES_T_SCHED: printf("L1CTL_RESET_REQ: SCHED!\n"); l1ctl_tx_reset(L1CTL_RESET_CONF, reset_req->type); sched_gsmtime_reset(); break; default: printf("unknown L1CTL_RESET_REQ type\n"); break; } } /* Transmit a L1CTL_CCCH_MODE_CONF */ static void l1ctl_tx_ccch_mode_conf(uint8_t ccch_mode) { struct msgb *msg = l1ctl_msgb_alloc(L1CTL_CCCH_MODE_CONF); struct l1ctl_ccch_mode_conf *mode_conf; mode_conf = (struct l1ctl_ccch_mode_conf *) msgb_put(msg, sizeof(*mode_conf)); mode_conf->ccch_mode = ccch_mode; l1_queue_for_l2(msg); } /* receive a L1CTL_CCCH_MODE_REQ from L23 */ static void l1ctl_rx_ccch_mode_req(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; struct l1ctl_ccch_mode_req *ccch_mode_req = (struct l1ctl_ccch_mode_req *) l1h->data; uint8_t ccch_mode = ccch_mode_req->ccch_mode; /* pre-set the CCCH mode */ l1s.serving_cell.ccch_mode = ccch_mode; /* Update task */ mframe_disable(MF_TASK_CCCH_COMB); mframe_disable(MF_TASK_CCCH); if (ccch_mode == CCCH_MODE_COMBINED) mframe_enable(MF_TASK_CCCH_COMB); else if (ccch_mode == CCCH_MODE_NON_COMBINED) mframe_enable(MF_TASK_CCCH); l1ctl_tx_ccch_mode_conf(ccch_mode); } /* Transmit a L1CTL_TCH_MODE_CONF */ static void l1ctl_tx_tch_mode_conf(uint8_t tch_mode, uint8_t audio_mode) { struct msgb *msg = l1ctl_msgb_alloc(L1CTL_TCH_MODE_CONF); struct l1ctl_tch_mode_conf *mode_conf; mode_conf = (struct l1ctl_tch_mode_conf *) msgb_put(msg, sizeof(*mode_conf)); mode_conf->tch_mode = tch_mode; mode_conf->audio_mode = audio_mode; l1_queue_for_l2(msg); } /* receive a L1CTL_TCH_MODE_REQ from L23 */ static void l1ctl_rx_tch_mode_req(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; struct l1ctl_tch_mode_req *tch_mode_req = (struct l1ctl_tch_mode_req *) l1h->data; uint8_t tch_mode = tch_mode_req->tch_mode; uint8_t audio_mode = tch_mode_req->audio_mode; printd("L1CTL_TCH_MODE_REQ (tch_mode=0x%02x audio_mode=0x%02x)\n", tch_mode, audio_mode); tch_mode = l1a_tch_mode_set(tch_mode); audio_mode = l1a_audio_mode_set(audio_mode); audio_set_enabled(tch_mode, audio_mode); l1s.tch_sync = 1; /* Needed for audio to work */ l1ctl_tx_tch_mode_conf(tch_mode, audio_mode); } /* receive a L1CTL_NEIGH_PM_REQ from L23 */ static void l1ctl_rx_neigh_pm_req(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; struct l1ctl_neigh_pm_req *pm_req = (struct l1ctl_neigh_pm_req *) l1h->data; int i; /* reset list in order to prevent race condition */ l1s.neigh_pm.n = 0; /* atomic */ /* now reset pointer and fill list */ l1s.neigh_pm.pos = 0; l1s.neigh_pm.valid = 0; l1s.neigh_pm.rounds = 0; l1s.neigh_pm.running = 0; if (pm_req->n > 64) pm_req->n = 64; for (i = 0; i < pm_req->n; i++) { l1s.neigh_pm.band_arfcn[i] = ntohs(pm_req->band_arfcn[i]); l1s.neigh_pm.tn[i] = pm_req->tn[i]; l1s.neigh_pm.level[i] = 0; l1s.neigh_sb.flags_bsic[i] = 0; } l1s.neigh_sb.count = 0; printf("L1CTL_NEIGH_PM_REQ new list with %u entries\n", pm_req->n); l1s.neigh_pm.n = pm_req->n; /* atomic */ /* * IDLE: on C0 enable PM on frame 51 * DEDICATED: add neighbor cell task */ if (l1s.dedicated.type == GSM_DCHAN_NONE) mframe_enable(MF_TASK_NEIGH_PM51_C0T0); else l1a_mftask_set(chan_nr2mf_task_mask(l1s.dedicated.chan_nr, NEIGH_MODE_PM)); } /* receive a L1CTL_TRAFFIC_REQ from L23 */ static void l1ctl_rx_traffic_req(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *) l1h->data; struct l1ctl_traffic_req *tr = (struct l1ctl_traffic_req *) ul->payload; int num = 0; /* printd("L1CTL_TRAFFIC_REQ\n"); */ /* Very verbose, can overwelm serial */ msg->l2h = tr->data; num = l1a_txq_msgb_count(&l1s.tx_queue[L1S_CHAN_TRAFFIC]); if (num >= 4) { printd("dropping traffic frame\n"); msgb_free(msg); return; } l1a_txq_msgb_enq(&l1s.tx_queue[L1S_CHAN_TRAFFIC], msg); } static void l1ctl_sim_req(struct msgb *msg) { uint16_t len = msg->len - sizeof(struct l1ctl_hdr); uint8_t *data = msg->data + sizeof(struct l1ctl_hdr); #if 1 /* for debugging only */ { int i; printf("SIM Request (%u): ", len); for (i = 0; i < len; i++) printf("%02x ", data[i]); puts("\n"); } #endif sim_apdu(len, data); } static void l1ctl_ringer_req(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; struct l1ctl_ringer_req *ring_req = (struct l1ctl_ringer_req *) l1h->data; printf("Ringtone Request: %u\n", ring_req->volume); buzzer_volume(ring_req->volume); buzzer_note(NOTE(NOTE_C, OCTAVE_4)); } /* Transmit a L1CTL_KEYPAD_IND */ void l1ctl_tx_keypad_ind(uint8_t key) { struct msgb *msg = l1ctl_msgb_alloc(L1CTL_KEYPAD_IND); struct l1ctl_keypad_ind *kp_ind; kp_ind = (struct l1ctl_keypad_ind *) msgb_put(msg, sizeof(*kp_ind)); switch (key) { case KEY_0: kp_ind->key = '0'; break; case KEY_1: kp_ind->key = '1'; break; case KEY_2: kp_ind->key = '2'; break; case KEY_3: kp_ind->key = '3'; break; case KEY_4: kp_ind->key = '4'; break; case KEY_5: kp_ind->key = '5'; break; case KEY_6: kp_ind->key = '6'; break; case KEY_7: kp_ind->key = '7'; break; case KEY_8: kp_ind->key = '8'; break; case KEY_9: kp_ind->key = '9'; break; case KEY_STAR: kp_ind->key = '*'; break; case KEY_HASH: kp_ind->key = '#'; break; case KEY_MENU: kp_ind->key = 25; break; case KEY_LEFT_SB: kp_ind->key = 1; break; case KEY_RIGHT_SB: kp_ind->key = 2; break; case KEY_UP: kp_ind->key = 28; break; case KEY_DOWN: kp_ind->key = 29; break; case KEY_LEFT: kp_ind->key = 30; break; case KEY_RIGHT: kp_ind->key = 31; break; case KEY_OK: kp_ind->key = 26; break; case KEY_POWER: kp_ind->key = 27; break; case KEY_MINUS: kp_ind->key = '-'; break; case KEY_PLUS: kp_ind->key = '+'; break; default: kp_ind->key = 0xFF; } l1_queue_for_l2(msg); } static void l1ctl_display_req(struct msgb *msg) { struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data; struct l1ctl_display_req *dr = (struct l1ctl_display_req *) l1h->data; printf("DISPLAY (%d) %s\n", dr->y, dr->text); if (dr->clear) fb_clear(); fb_setfg(FB_COLOR_GREEN); fb_setbg(FB_COLOR_WHITE); fb_setfont(FB_FONT_C64); fb_gotoxy(dr->x, dr->y); fb_putstr(dr->text, 100); if (dr->flush) fb_flush(); } static struct llist_head l23_rx_queue = LLIST_HEAD_INIT(l23_rx_queue); /* callback from SERCOMM when L2 sends a message to L1 */ void l1a_l23_rx(uint8_t dlci, struct msgb *msg) { unsigned long flags; local_firq_save(flags); msgb_enqueue(&l23_rx_queue, msg); local_irq_restore(flags); } void l1a_l23_handler(void) { struct msgb *msg; struct l1ctl_hdr *l1h; unsigned long flags; local_firq_save(flags); msg = msgb_dequeue(&l23_rx_queue); local_irq_restore(flags); if (!msg) return; l1h = (struct l1ctl_hdr *) msg->data; #if 0 { int i; printf("l1a_l23_rx_cb (%u): ", msg->len); for (i = 0; i < msg->len; i++) printf("%02x ", msg->data[i]); puts("\n"); } #endif msg->l1h = msg->data; if (sizeof(*l1h) > msg->len) { printf("l1a_l23_cb: Short message. %u\n", msg->len); goto exit_msgbfree; } switch (l1h->msg_type) { case L1CTL_FBSB_REQ: l1ctl_rx_fbsb_req(msg); break; case L1CTL_DM_EST_REQ: l1ctl_rx_dm_est_req(msg); break; case L1CTL_DM_REL_REQ: l1ctl_rx_dm_rel_req(msg); break; case L1CTL_PARAM_REQ: l1ctl_rx_param_req(msg); break; case L1CTL_DM_FREQ_REQ: l1ctl_rx_dm_freq_req(msg); break; case L1CTL_CRYPTO_REQ: l1ctl_rx_crypto_req(msg); break; case L1CTL_RACH_REQ: l1ctl_rx_rach_req(msg); break; case L1CTL_DATA_REQ: l1ctl_rx_data_req(msg); /* we have to keep the msgb, not free it! */ goto exit_nofree; case L1CTL_PM_REQ: l1ctl_rx_pm_req(msg); break; case L1CTL_RESET_REQ: l1ctl_rx_reset_req(msg); break; case L1CTL_CCCH_MODE_REQ: l1ctl_rx_ccch_mode_req(msg); break; case L1CTL_TCH_MODE_REQ: l1ctl_rx_tch_mode_req(msg); break; case L1CTL_NEIGH_PM_REQ: l1ctl_rx_neigh_pm_req(msg); break; case L1CTL_TRAFFIC_REQ: l1ctl_rx_traffic_req(msg); /* we have to keep the msgb, not free it! */ goto exit_nofree; case L1CTL_RINGER_REQ: l1ctl_ringer_req(msg); break; case L1CTL_SIM_REQ: l1ctl_sim_req(msg); break; case L1CTL_DISPLAY_REQ: l1ctl_display_req(msg); break; } exit_msgbfree: msgb_free(msg); exit_nofree: return; } void l1a_l23api_init(void) { sercomm_register_rx_cb(SC_DLCI_L1A_L23, l1a_l23_rx); }