/* RF Ctl handling socket */ /* (C) 2010 by Harald Welte * (C) 2010-2012 by Holger Hans Peter Freyther * (C) 2010-2012 by On-Waves * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define RF_CMD_QUERY '?' #define RF_CMD_OFF '0' #define RF_CMD_ON '1' #define RF_CMD_D_OFF 'd' #define RF_CMD_ON_G 'g' static const struct value_string opstate_names[] = { { OSMO_BSC_RF_OPSTATE_INOPERATIONAL, "inoperational" }, { OSMO_BSC_RF_OPSTATE_OPERATIONAL, "operational" }, { 0, NULL } }; static const struct value_string adminstate_names[] = { { OSMO_BSC_RF_ADMINSTATE_UNLOCKED, "unlocked" }, { OSMO_BSC_RF_ADMINSTATE_LOCKED, "locked" }, { 0, NULL } }; static const struct value_string policy_names[] = { { OSMO_BSC_RF_POLICY_OFF, "off" }, { OSMO_BSC_RF_POLICY_ON, "on" }, { OSMO_BSC_RF_POLICY_GRACE, "grace" }, { OSMO_BSC_RF_POLICY_UNKNOWN, "unknown" }, { 0, NULL } }; const char *osmo_bsc_rf_get_opstate_name(enum osmo_bsc_rf_opstate opstate) { return get_value_string(opstate_names, opstate); } const char *osmo_bsc_rf_get_adminstate_name(enum osmo_bsc_rf_adminstate adminstate) { return get_value_string(adminstate_names, adminstate); } const char *osmo_bsc_rf_get_policy_name(enum osmo_bsc_rf_policy policy) { return get_value_string(policy_names, policy); } enum osmo_bsc_rf_opstate osmo_bsc_rf_get_opstate_by_bts(struct gsm_bts *bts) { struct gsm_bts_trx *trx; llist_for_each_entry(trx, &bts->trx_list, list) { if (trx->mo.nm_state.operational == NM_OPSTATE_ENABLED) return OSMO_BSC_RF_OPSTATE_OPERATIONAL; } /* No trx were active, so this bts is disabled */ return OSMO_BSC_RF_OPSTATE_INOPERATIONAL; } enum osmo_bsc_rf_adminstate osmo_bsc_rf_get_adminstate_by_bts(struct gsm_bts *bts) { struct gsm_bts_trx *trx; llist_for_each_entry(trx, &bts->trx_list, list) { if (trx->mo.nm_state.administrative == NM_STATE_UNLOCKED) return OSMO_BSC_RF_ADMINSTATE_UNLOCKED; } /* All trx administrative states were locked */ return OSMO_BSC_RF_ADMINSTATE_LOCKED; } enum osmo_bsc_rf_policy osmo_bsc_rf_get_policy_by_bts(struct gsm_bts *bts) { struct osmo_bsc_data *bsc_data = bts->network->bsc_data; if (!bsc_data || !bsc_data->rf_ctrl) return OSMO_BSC_RF_POLICY_UNKNOWN; switch (bsc_data->rf_ctrl->policy) { case S_RF_ON: return OSMO_BSC_RF_POLICY_ON; case S_RF_OFF: return OSMO_BSC_RF_POLICY_OFF; case S_RF_GRACE: return OSMO_BSC_RF_POLICY_GRACE; default: return OSMO_BSC_RF_POLICY_UNKNOWN; } } static int lock_each_trx(struct gsm_network *net, int lock) { struct gsm_bts *bts; llist_for_each_entry(bts, &net->bts_list, list) { struct gsm_bts_trx *trx; /* Exclude the BTS from the global lock */ if (bts->excl_from_rf_lock) { LOGP(DLINP, LOGL_DEBUG, "Excluding BTS(%d) from trx lock.\n", bts->nr); continue; } llist_for_each_entry(trx, &bts->trx_list, list) { gsm_trx_lock_rf(trx, lock); } } return 0; } static void send_resp(struct osmo_bsc_rf_conn *conn, char send) { struct msgb *msg; msg = msgb_alloc(10, "RF Query"); if (!msg) { LOGP(DLINP, LOGL_ERROR, "Failed to allocate response msg.\n"); return; } msg->l2h = msgb_put(msg, 1); msg->l2h[0] = send; if (osmo_wqueue_enqueue(&conn->queue, msg) != 0) { LOGP(DLINP, LOGL_ERROR, "Failed to enqueue the answer.\n"); msgb_free(msg); return; } return; } /* * Send a * 'g' when we are in grace mode * '1' when one TRX is online, * '0' otherwise */ static void handle_query(struct osmo_bsc_rf_conn *conn) { struct gsm_bts *bts; char send = RF_CMD_OFF; if (conn->rf->policy == S_RF_GRACE) return send_resp(conn, RF_CMD_ON_G); llist_for_each_entry(bts, &conn->rf->gsm_network->bts_list, list) { struct gsm_bts_trx *trx; /* Exclude the BTS from the global lock */ if (bts->excl_from_rf_lock) { LOGP(DLINP, LOGL_DEBUG, "Excluding BTS(%d) from query.\n", bts->nr); continue; } llist_for_each_entry(trx, &bts->trx_list, list) { if (trx->mo.nm_state.availability == NM_AVSTATE_OK && trx->mo.nm_state.operational != NM_OPSTATE_DISABLED) { send = RF_CMD_ON; break; } } } send_resp(conn, send); } static void rf_check_cb(void *_data) { struct gsm_bts *bts; struct osmo_bsc_rf *rf = _data; llist_for_each_entry(bts, &rf->gsm_network->bts_list, list) { struct gsm_bts_trx *trx; /* don't bother to check a booting or missing BTS */ if (!bts->oml_link || !is_ipaccess_bts(bts)) continue; /* Exclude the BTS from the global lock */ if (bts->excl_from_rf_lock) { LOGP(DLINP, LOGL_DEBUG, "Excluding BTS(%d) from query.\n", bts->nr); continue; } llist_for_each_entry(trx, &bts->trx_list, list) { if (trx->mo.nm_state.availability != NM_AVSTATE_OK || trx->mo.nm_state.operational != NM_OPSTATE_ENABLED || trx->mo.nm_state.administrative != NM_STATE_UNLOCKED) { LOGP(DNM, LOGL_ERROR, "RF activation failed. Starting again.\n"); ipaccess_drop_oml(bts); break; } } } } static void send_signal(struct osmo_bsc_rf *rf, int val) { struct rf_signal_data sig; sig.net = rf->gsm_network; rf->policy = val; osmo_signal_dispatch(SS_RF, val, &sig); } static int switch_rf_off(struct osmo_bsc_rf *rf) { lock_each_trx(rf->gsm_network, 1); send_signal(rf, S_RF_OFF); return 0; } static void grace_timeout(void *_data) { struct osmo_bsc_rf *rf = (struct osmo_bsc_rf *) _data; LOGP(DLINP, LOGL_NOTICE, "Grace timeout. Going to disable all BTS/TRX.\n"); switch_rf_off(rf); } static int enter_grace(struct osmo_bsc_rf *rf) { if (osmo_timer_pending(&rf->grace_timeout)) { LOGP(DLINP, LOGL_NOTICE, "RF Grace timer is pending. Not restarting.\n"); return 0; } rf->grace_timeout.cb = grace_timeout; rf->grace_timeout.data = rf; osmo_timer_schedule(&rf->grace_timeout, rf->gsm_network->bsc_data->mid_call_timeout, 0); LOGP(DLINP, LOGL_NOTICE, "Going to switch RF off in %d seconds.\n", rf->gsm_network->bsc_data->mid_call_timeout); send_signal(rf, S_RF_GRACE); return 0; } static void rf_delay_cmd_cb(void *data) { struct osmo_bsc_rf *rf = data; switch (rf->last_request) { case RF_CMD_D_OFF: rf->last_state_command = "RF Direct Off"; osmo_timer_del(&rf->rf_check); osmo_timer_del(&rf->grace_timeout); switch_rf_off(rf); break; case RF_CMD_ON: rf->last_state_command = "RF Direct On"; osmo_timer_del(&rf->grace_timeout); lock_each_trx(rf->gsm_network, 0); send_signal(rf, S_RF_ON); osmo_timer_schedule(&rf->rf_check, 3, 0); break; case RF_CMD_OFF: rf->last_state_command = "RF Scheduled Off"; osmo_timer_del(&rf->rf_check); enter_grace(rf); break; } } static int rf_read_cmd(struct osmo_fd *fd) { struct osmo_bsc_rf_conn *conn = fd->data; char buf[1]; int rc; rc = read(fd->fd, buf, sizeof(buf)); if (rc != sizeof(buf)) { LOGP(DLINP, LOGL_ERROR, "Short read %d/%s\n", errno, strerror(errno)); osmo_fd_unregister(fd); close(fd->fd); osmo_wqueue_clear(&conn->queue); talloc_free(conn); return -1; } switch (buf[0]) { case RF_CMD_QUERY: handle_query(conn); break; case RF_CMD_D_OFF: case RF_CMD_ON: case RF_CMD_OFF: osmo_bsc_rf_schedule_lock(conn->rf, buf[0]); break; default: conn->rf->last_state_command = "Unknown command"; LOGP(DLINP, LOGL_ERROR, "Unknown command %d\n", buf[0]); break; } return 0; } static int rf_write_cmd(struct osmo_fd *fd, struct msgb *msg) { int rc; rc = write(fd->fd, msg->data, msg->len); if (rc != msg->len) { LOGP(DLINP, LOGL_ERROR, "Short write %d/%s\n", errno, strerror(errno)); return -1; } return 0; } static int rf_ctrl_accept(struct osmo_fd *bfd, unsigned int what) { struct osmo_bsc_rf_conn *conn; struct osmo_bsc_rf *rf = bfd->data; struct sockaddr_un addr; socklen_t len = sizeof(addr); int fd; fd = accept(bfd->fd, (struct sockaddr *) &addr, &len); if (fd < 0) { LOGP(DLINP, LOGL_ERROR, "Failed to accept. errno: %d/%s\n", errno, strerror(errno)); return -1; } conn = talloc_zero(rf, struct osmo_bsc_rf_conn); if (!conn) { LOGP(DLINP, LOGL_ERROR, "Failed to allocate mem.\n"); close(fd); return -1; } osmo_wqueue_init(&conn->queue, 10); conn->queue.bfd.data = conn; conn->queue.bfd.fd = fd; conn->queue.bfd.when = BSC_FD_READ | BSC_FD_WRITE; conn->queue.read_cb = rf_read_cmd; conn->queue.write_cb = rf_write_cmd; conn->rf = rf; if (osmo_fd_register(&conn->queue.bfd) != 0) { close(fd); talloc_free(conn); return -1; } return 0; } static void rf_auto_off_cb(void *_timer) { struct osmo_bsc_rf *rf = _timer; LOGP(DLINP, LOGL_NOTICE, "Going to switch off RF due lack of a MSC connection.\n"); osmo_bsc_rf_schedule_lock(rf, RF_CMD_D_OFF); } static int msc_signal_handler(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) { struct gsm_network *net; struct msc_signal_data *msc; struct osmo_bsc_rf *rf; /* check if we want to handle this signal */ if (subsys != SS_MSC) return 0; net = handler_data; msc = signal_data; /* check if we have the needed information */ if (!net->bsc_data || !net->bsc_data->rf_ctrl) return 0; if (msc->data->type != MSC_CON_TYPE_NORMAL) return 0; rf = net->bsc_data->rf_ctrl; switch (signal) { case S_MSC_LOST: if (net->bsc_data->auto_off_timeout < 0) return 0; if (osmo_timer_pending(&rf->auto_off_timer)) return 0; osmo_timer_schedule(&rf->auto_off_timer, net->bsc_data->auto_off_timeout, 0); break; case S_MSC_CONNECTED: osmo_timer_del(&rf->auto_off_timer); break; } return 0; } struct osmo_bsc_rf *osmo_bsc_rf_create(const char *path, struct gsm_network *net) { unsigned int namelen; struct sockaddr_un local; struct osmo_fd *bfd; struct osmo_bsc_rf *rf; int rc; rf = talloc_zero(NULL, struct osmo_bsc_rf); if (!rf) { LOGP(DLINP, LOGL_ERROR, "Failed to create osmo_bsc_rf.\n"); return NULL; } bfd = &rf->listen; bfd->fd = socket(AF_UNIX, SOCK_STREAM, 0); if (bfd->fd < 0) { LOGP(DLINP, LOGL_ERROR, "Can not create socket. %d/%s\n", errno, strerror(errno)); return NULL; } 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) { LOGP(DLINP, LOGL_ERROR, "Failed to bind '%s' errno: %d/%s\n", local.sun_path, errno, strerror(errno)); close(bfd->fd); talloc_free(rf); return NULL; } if (listen(bfd->fd, 0) != 0) { LOGP(DLINP, LOGL_ERROR, "Failed to listen: %d/%s\n", errno, strerror(errno)); close(bfd->fd); talloc_free(rf); return NULL; } bfd->when = BSC_FD_READ; bfd->cb = rf_ctrl_accept; bfd->data = rf; if (osmo_fd_register(bfd) != 0) { LOGP(DLINP, LOGL_ERROR, "Failed to register bfd.\n"); close(bfd->fd); talloc_free(rf); return NULL; } rf->gsm_network = net; rf->policy = S_RF_ON; rf->last_state_command = ""; /* check the rf state */ rf->rf_check.data = rf; rf->rf_check.cb = rf_check_cb; /* delay cmd handling */ rf->delay_cmd.data = rf; rf->delay_cmd.cb = rf_delay_cmd_cb; rf->auto_off_timer.data = rf; rf->auto_off_timer.cb = rf_auto_off_cb; /* listen to RF signals */ osmo_signal_register_handler(SS_MSC, msc_signal_handler, net); return rf; } void osmo_bsc_rf_schedule_lock(struct osmo_bsc_rf *rf, char cmd) { rf->last_request = cmd; if (!osmo_timer_pending(&rf->delay_cmd)) osmo_timer_schedule(&rf->delay_cmd, 1, 0); }