/* * This file is part of QMI-RIL. * * Copyright (C) 2010-2011 Joerie de Gram * Copyright (C) 2011-2014 Paul Kocialkowski * Copyright (C) 2017 Wolfgang Wiedmeyer * * QMI-RIL 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 3 of the License, or * (at your option) any later version. * * QMI-RIL 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 QMI-RIL. If not, see . */ #include #include #define LOG_TAG "RIL" #include #include #include #include static GMainLoop *loop; /* * RIL data */ struct ril_data *ril_data = NULL; /* * RIL request */ struct ril_request_handler ril_request_handlers[] = { /* Power */ { .request = RIL_REQUEST_RADIO_POWER, .handler = ril_request_radio_power, }, /* Call */ { .request = RIL_REQUEST_GET_CURRENT_CALLS, .handler = ril_request_get_current_calls, }, /* SIM */ { .request = RIL_REQUEST_GET_SIM_STATUS, .handler = ril_request_get_sim_status, }, { .request = RIL_REQUEST_QUERY_FACILITY_LOCK, .handler = ril_request_query_facility_lock, }, { .request = RIL_REQUEST_SIM_IO, .handler = ril_request_sim_io, }, /* Network */ { .request = RIL_REQUEST_SIGNAL_STRENGTH, .handler = ril_request_signal_strength, }, { .request = RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE, .handler = ril_request_query_network_selection_mode, }, { .request = RIL_REQUEST_OPERATOR, .handler = ril_request_operator, }, { .request = RIL_REQUEST_VOICE_REGISTRATION_STATE, .handler = ril_request_registration_state, }, { .request = RIL_REQUEST_DATA_REGISTRATION_STATE, .handler = ril_request_registration_state, }, { .request = RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE, .handler = ril_request_set_preferred_network_type, }, /* Misc */ { .request = RIL_REQUEST_BASEBAND_VERSION, .handler = ril_request_baseband_version, }, { .request = RIL_REQUEST_GET_IMSI, .handler = ril_request_get_imsi, }, { .request = RIL_REQUEST_GET_IMEI, .handler = ril_request_get_imei, }, { .request = RIL_REQUEST_GET_IMEISV, .handler = ril_request_get_imeisv, }, { .request = RIL_REQUEST_SCREEN_STATE, .handler = ril_request_screen_state, }, /* Data */ { .request = RIL_REQUEST_SETUP_DATA_CALL, .handler = ril_request_setup_data_call, }, }; unsigned int ril_request_handlers_count = sizeof(ril_request_handlers) / sizeof(struct ril_request_handler); static gboolean sigterm_handler(void) { RIL_LOGD("caught sigterm"); if (ctx != NULL && ctx->cancellable) { RIL_LOGD("cancelling the current operation..."); g_cancellable_cancel(ctx->cancellable); } if (loop && g_main_loop_is_running (loop)) { RIL_LOGD("cancelling the main loop..."); g_idle_add((GSourceFunc)g_main_loop_quit, loop); } return G_SOURCE_REMOVE; } int ril_request_stats_log(void) { struct ril_request *request; struct list_head *list; unsigned int pending = 0; unsigned int handled = 0; unsigned int unhandled = 0; unsigned int count = 0; if (ril_data == NULL) return -1; RIL_REQUEST_LOCK(); list = ril_data->requests; while (list != NULL) { if (list->data == NULL) goto list_continue; request = (struct ril_request *) list->data; switch (request->status) { case RIL_REQUEST_PENDING: pending++; break; case RIL_REQUEST_HANDLED: handled++; break; case RIL_REQUEST_UNHANDLED: unhandled++; break; } count++; list_continue: list = list->next; } RIL_LOGD("%d RIL request%s in the queue (%d pending, %d handled, %d unhandled)", count, count > 1 ? "s" : "", pending, handled, unhandled); count = 0; list = ril_data->requests_data; while (list != NULL) { count++; list = list->next; } if (count > 0) RIL_LOGD("%d RIL request%s data in the queue", count, count > 1 ? "s" : ""); RIL_REQUEST_UNLOCK(); return 0; } int ril_request_register(int request, void *data, size_t size, RIL_Token token) { struct ril_request *ril_request; struct list_head *list_end; struct list_head *list; unsigned int i; if (ril_data == NULL) return -1; RIL_REQUEST_LOCK(); ril_request = (struct ril_request *) calloc(1, sizeof(struct ril_request)); ril_request->request = request; ril_request->data = NULL; ril_request->size = size; ril_request->token = token; ril_request->status = RIL_REQUEST_PENDING; if (size > 0) { ril_request->data = calloc(1, size); memcpy(ril_request->data, data, size); } list_end = ril_data->requests; while (list_end != NULL && list_end->next != NULL) list_end = list_end->next; list = list_head_alloc(list_end, NULL, (void *) ril_request); if (ril_data->requests == NULL) ril_data->requests = list; RIL_REQUEST_UNLOCK(); return 0; } int ril_request_unregister(struct ril_request *request) { struct list_head *list; unsigned int i; if (request == NULL || ril_data == NULL) return -1; RIL_REQUEST_LOCK(); list = ril_data->requests; while (list != NULL) { if (list->data == (void *) request) { if (request->data != NULL && request->size > 0) free(request->data); memset(request, 0, sizeof(struct ril_request)); free(request); if (list == ril_data->requests) ril_data->requests = list->next; list_head_free(list); break; } list_continue: list = list->next; } RIL_REQUEST_UNLOCK(); return 0; } int ril_request_flush(void) { struct ril_request *request; struct list_head *list; struct list_head *list_next; if (ril_data == NULL) return -1; RIL_REQUEST_LOCK(); list = ril_data->requests; while (list != NULL) { if (list->data != NULL) { request = (struct ril_request *) list->data; if (request->data != NULL && request->size > 0) free(request->data); memset(request, 0, sizeof(struct ril_request)); free(request); } if (list == ril_data->requests) ril_data->requests = list->next; list_next = list->next; list_head_free(list); list_continue: list = list_next; } RIL_REQUEST_UNLOCK(); return 0; } struct ril_request *ril_request_find(void) { struct ril_request *request; struct list_head *list; if (ril_data == NULL) return NULL; RIL_REQUEST_LOCK(); list = ril_data->requests; while (list != NULL) { if (list->data == NULL) goto list_continue; request = (struct ril_request *) list->data; RIL_REQUEST_UNLOCK(); return request; list_continue: list = list->next; } RIL_REQUEST_UNLOCK(); return NULL; } struct ril_request *ril_request_find_request_status(int request, int status) { struct ril_request *ril_request; struct list_head *list; if (ril_data == NULL) return NULL; RIL_REQUEST_LOCK(); list = ril_data->requests; while (list != NULL) { if (list->data == NULL) goto list_continue; ril_request = (struct ril_request *) list->data; if (ril_request->request == request && ril_request->status == status) { RIL_REQUEST_UNLOCK(); return ril_request; } list_continue: list = list->next; } RIL_REQUEST_UNLOCK(); return NULL; } struct ril_request *ril_request_find_request(int request) { struct ril_request *ril_request; struct list_head *list; if (ril_data == NULL) return NULL; RIL_REQUEST_LOCK(); list = ril_data->requests; while (list != NULL) { if (list->data == NULL) goto list_continue; ril_request = (struct ril_request *) list->data; if (ril_request->request == request) { RIL_REQUEST_UNLOCK(); return ril_request; } list_continue: list = list->next; } RIL_REQUEST_UNLOCK(); return NULL; } struct ril_request *ril_request_find_token(RIL_Token token) { struct ril_request *request; struct list_head *list; if (ril_data == NULL) return NULL; RIL_REQUEST_LOCK(); list = ril_data->requests; while (list != NULL) { if (list->data == NULL) goto list_continue; request = (struct ril_request *) list->data; if (request->token == token) { RIL_REQUEST_UNLOCK(); return request; } list_continue: list = list->next; } RIL_REQUEST_UNLOCK(); return NULL; } struct ril_request *ril_request_find_status(int status) { struct ril_request *request; struct list_head *list; if (ril_data == NULL) return NULL; RIL_REQUEST_LOCK(); list = ril_data->requests; while (list != NULL) { if (list->data == NULL) goto list_continue; request = (struct ril_request *) list->data; if (request->status == status) { RIL_REQUEST_UNLOCK(); return request; } list_continue: list = list->next; } RIL_REQUEST_UNLOCK(); return NULL; } int ril_request_complete(RIL_Token token, RIL_Errno error, void *data, size_t size) { struct ril_request *request; if (ril_data == NULL || ril_data->env == NULL || ril_data->env->OnRequestComplete == NULL) return -1; if (token == NULL) return 0; request = ril_request_find_token(token); if (request == NULL) goto complete; ril_request_unregister(request); ril_request_stats_log(); complete: ril_data->env->OnRequestComplete(token, error, data, size); return 0; } int ril_request_unsolicited(int request, void *data, size_t size) { if (ril_data == NULL || ril_data->env == NULL || ril_data->env->OnUnsolicitedResponse == NULL) return -1; ril_data->env->OnUnsolicitedResponse(request, data, size); return 0; } int ril_request_timed_callback(RIL_TimedCallback callback, void *data, const struct timeval *time) { if (ril_data == NULL || ril_data->env == NULL || ril_data->env->RequestTimedCallback == NULL) return -1; ril_data->env->RequestTimedCallback(callback, data, time); return 0; } int ril_request_dispatch(struct ril_request *request) { unsigned int i; int status; int rc; if (request == NULL || ril_data == NULL) return -1; for (i = 0; i < ril_request_handlers_count; i++) { if (ril_request_handlers[i].handler == NULL) continue; if (ril_request_handlers[i].request == request->request) { status = ril_request_handlers[i].handler(request->data, request->size, request->token); switch (status) { case RIL_REQUEST_PENDING: case RIL_REQUEST_HANDLED: case RIL_REQUEST_UNHANDLED: request->status = status; break; case RIL_REQUEST_COMPLETED: break; default: RIL_LOGE("Handling RIL request %d failed", request->request); return -1; } return 0; } } RIL_LOGD("Unhandled RIL request: %d", request->request); ril_request_complete(request->token, RIL_E_REQUEST_NOT_SUPPORTED, NULL, 0); return 0; } static gboolean ril_request_loop(gpointer data) { struct ril_request *request; int rc; rc = all_clients_running(); if (rc < 0) { RIL_LOGE("error during QMI client allocation"); return FALSE; } else if (rc == 0) { return TRUE; } rc = ril_radio_state_check(RADIO_STATE_OFF); if (rc < 0) return TRUE; do { request = ril_request_find_status(RIL_REQUEST_UNHANDLED); if (request == NULL) break; request->status = RIL_REQUEST_PENDING; } while (request != NULL); do { request = ril_request_find_status(RIL_REQUEST_PENDING); if (request == NULL) break; rc = ril_request_dispatch(request); if (rc < 0) ril_request_unregister(request); } while (request != NULL); return TRUE; } void *ril_request_main_thread(void *data) { int rc; if (ril_data == NULL) return NULL; loop = g_main_loop_new(NULL, FALSE); g_unix_signal_add(SIGTERM, (GSourceFunc) sigterm_handler, NULL); g_timeout_add(100, ril_request_loop, data); RIL_LOGD("RIL running main loop"); g_main_loop_run(loop); RIL_LOGD("exiting main thread"); if (ctx->cancellable) g_object_unref(ctx->cancellable); if (ctx->DmsClient) g_object_unref(ctx->DmsClient); if (ctx->NasClient) g_object_unref(ctx->NasClient); if (ctx->UimClient) g_object_unref(ctx->UimClient); if (ctx->WdsClient) g_object_unref(ctx->WdsClient); if (ctx->device) g_object_unref(ctx->device); g_main_loop_unref(loop); g_object_unref(ctx->file); RIL_LOGD("cleaned up"); exit(EXIT_SUCCESS); return NULL; } /* * RIL request data */ int ril_request_data_register(int request, void *data, size_t size) { struct ril_request_data *request_data; struct list_head *list_end; struct list_head *list; unsigned int i; if (data == NULL || ril_data == NULL) return -1; request_data = (struct ril_request_data *) calloc(1, sizeof(struct ril_request_data)); request_data->request = request; request_data->data = data; request_data->size = size; list_end = ril_data->requests_data; while (list_end != NULL && list_end->next != NULL) list_end = list_end->next; list = list_head_alloc(list_end, NULL, (void *) request_data); if (ril_data->requests_data == NULL) ril_data->requests_data = list; return 0; } int ril_request_data_unregister(struct ril_request_data *request_data) { struct list_head *list; unsigned int i; if (request_data == NULL || ril_data == NULL) return -1; list = ril_data->requests_data; while (list != NULL) { if (list->data == (void *) request_data) { memset(request_data, 0, sizeof(struct ril_request_data)); free(request_data); if (list == ril_data->requests_data) ril_data->requests_data = list->next; list_head_free(list); break; } list_continue: list = list->next; } return 0; } int ril_request_data_flush(void) { struct ril_request_data *request_data; struct list_head *list; struct list_head *list_next; if (ril_data == NULL) return -1; list = ril_data->requests_data; while (list != NULL) { if (list->data != NULL) { request_data = (struct ril_request_data *) list->data; if (request_data->data != NULL && request_data->size > 0) free(request_data->data); memset(request_data, 0, sizeof(struct ril_request_data)); free(request_data); } if (list == ril_data->requests_data) ril_data->requests_data = list->next; list_next = list->next; list_head_free(list); list_continue: list = list_next; } return 0; } struct ril_request_data *ril_request_data_find_request(int request) { struct ril_request_data *request_data; struct list_head *list; if (ril_data == NULL) return NULL; list = ril_data->requests_data; while (list != NULL) { if (list->data == NULL) goto list_continue; request_data = (struct ril_request_data *) list->data; if (request_data->request == request) return request_data; list_continue: list = list->next; } return NULL; } int ril_request_data_free(int request) { struct ril_request_data *request_data; do { request_data = ril_request_data_find_request(request); if (request_data == NULL) break; if (request_data->data != NULL && request_data->size > 0) free(request_data->data); ril_request_data_unregister(request_data); } while (request_data != NULL); return 0; } int ril_request_data_set(int request, void *data, size_t size) { void *buffer; int rc; if (data == NULL || size == 0) return -1; buffer = calloc(1, size); memcpy(buffer, data, size); rc = ril_request_data_register(request, buffer, size); if (rc < 0) return -1; return 0; } int ril_request_data_set_uniq(int request, void *data, size_t size) { int rc; ril_request_data_free(request); rc = ril_request_data_set(request, data, size); if (rc < 0) return -1; return 0; } size_t ril_request_data_size_get(int request) { struct ril_request_data *request_data; request_data = ril_request_data_find_request(request); if (request_data == NULL) return 0; return request_data->size; } void *ril_request_data_get(int request) { struct ril_request_data *request_data; void *buffer; request_data = ril_request_data_find_request(request); if (request_data == NULL) return NULL; buffer = request_data->data; ril_request_data_unregister(request_data); return buffer; } /* * RIL radio state */ int ril_radio_state_update(RIL_RadioState radio_state) { struct ril_request *request; unsigned int i; int rc; if (ril_data == NULL) return -1; if (ril_data->radio_state == radio_state) return 0; RIL_LOGD("Updating RIL radio state to %d", radio_state); ril_data->radio_state = radio_state; ril_request_unsolicited(RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, NULL, 0); switch (ril_data->radio_state) { case RADIO_STATE_UNAVAILABLE: do { request = ril_request_find(); if (request == NULL) break; ril_request_complete(request->token, RIL_E_RADIO_NOT_AVAILABLE, NULL, 0); ril_request_unregister(request); } while (request != NULL); ril_request_flush(); ril_request_data_flush(); ril_request_stats_log(); case RADIO_STATE_OFF: RIL_LOGE("TODO: Implement ril_data_connection_flush() here!"); ril_request_unsolicited(RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, NULL, 0); ril_request_unsolicited(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED, NULL, 0); ril_request_unsolicited(RIL_UNSOL_DATA_CALL_LIST_CHANGED, NULL, 0); ril_request_unsolicited(RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED, NULL, 0); break; default: break; } return 0; } int ril_radio_state_check(RIL_RadioState radio_state) { RIL_RadioState radio_states[] = { RADIO_STATE_UNAVAILABLE, RADIO_STATE_OFF, RADIO_STATE_ON, RADIO_STATE_NV_NOT_READY, RADIO_STATE_NV_READY, RADIO_STATE_SIM_NOT_READY, RADIO_STATE_SIM_LOCKED_OR_ABSENT, RADIO_STATE_SIM_READY, }; unsigned int index; unsigned int count; unsigned int i; if (ril_data == NULL) return -1; count = sizeof(radio_states) / sizeof(RIL_RadioState); for (i = 0; i < count; i++) if (radio_states[i] == radio_state) break; index = i; for (i = 0; i < count; i++) if (radio_states[i] == ril_data->radio_state) break; if (i < index) return -1; return 0; } /* * RIL data */ int ril_data_create(void) { ril_data = (struct ril_data *) calloc(1, sizeof(struct ril_data)); pthread_mutex_init(&ril_data->request_mutex, NULL); RIL_LOGD("initializing RIL data"); ril_data->radio_state = RADIO_STATE_UNAVAILABLE; /* modem is booting in ethernet mode */ ril_data->data_connection.raw_ip_mode = FALSE; return 0; } int ril_data_destroy(void) { RIL_LOGD("destroying RIL data"); if (ril_data == NULL) return -1; pthread_mutex_destroy(&ril_data->request_mutex); free(ril_data); ril_data = NULL; return 0; } /* * RIL interface */ void ril_on_request(int request, void *data, size_t size, RIL_Token token) { struct ril_request *ril_request; void *buffer = NULL; unsigned int strings_count = 0; unsigned int i; char *c; ril_request = ril_request_find_token(token); if (ril_request != NULL) ril_request_unregister(ril_request); switch (request) { case RIL_REQUEST_DIAL: if (data == NULL || size < sizeof(RIL_Dial)) break; buffer = calloc(1, size); memcpy(buffer, data, size); if (((RIL_Dial *) data)->address != NULL) ((RIL_Dial *) buffer)->address = strdup(((RIL_Dial *) data)->address); data = buffer; break; case RIL_REQUEST_WRITE_SMS_TO_SIM: if (data == NULL || size < sizeof(RIL_SMS_WriteArgs)) break; buffer = calloc(1, size); memcpy(buffer, data, size); if (((RIL_SMS_WriteArgs *) data)->pdu != NULL) ((RIL_SMS_WriteArgs *) buffer)->pdu = strdup(((RIL_SMS_WriteArgs *) data)->pdu); if (((RIL_SMS_WriteArgs *) data)->smsc != NULL) ((RIL_SMS_WriteArgs *) buffer)->smsc = strdup(((RIL_SMS_WriteArgs *) data)->smsc); data = buffer; break; case RIL_REQUEST_SEND_SMS: case RIL_REQUEST_SEND_SMS_EXPECT_MORE: case RIL_REQUEST_QUERY_FACILITY_LOCK: case RIL_REQUEST_SET_FACILITY_LOCK: case RIL_REQUEST_ENTER_SIM_PIN: case RIL_REQUEST_ENTER_SIM_PUK: case RIL_REQUEST_ENTER_SIM_PIN2: case RIL_REQUEST_ENTER_SIM_PUK2: case RIL_REQUEST_CHANGE_SIM_PIN: case RIL_REQUEST_CHANGE_SIM_PIN2: strings_count = size / sizeof(char *); break; case RIL_REQUEST_SIM_IO: if (data == NULL || size < sizeof(RIL_SIM_IO_v6)) break; buffer = calloc(1, size); memcpy(buffer, data, size); if (((RIL_SIM_IO_v6 *) data)->path != NULL) ((RIL_SIM_IO_v6 *) buffer)->path = strdup(((RIL_SIM_IO_v6 *) data)->path); if (((RIL_SIM_IO_v6 *) data)->data != NULL) ((RIL_SIM_IO_v6 *) buffer)->data = strdup(((RIL_SIM_IO_v6 *) data)->data); if (((RIL_SIM_IO_v6 *) data)->pin2 != NULL) ((RIL_SIM_IO_v6 *) buffer)->pin2 = strdup(((RIL_SIM_IO_v6 *) data)->pin2); if (((RIL_SIM_IO_v6 *) data)->aidPtr != NULL) ((RIL_SIM_IO_v6 *) buffer)->aidPtr = strdup(((RIL_SIM_IO_v6 *) data)->aidPtr); data = buffer; break; case RIL_REQUEST_SETUP_DATA_CALL: case RIL_REQUEST_DEACTIVATE_DATA_CALL: strings_count = size / sizeof(char *); break; default: if (data == NULL || size != sizeof(char *)) break; c = (char *) data; for (i = 0; isprint(c[i]); i++); if (i > 0 && c[i] == '\0') { size = i + 1; RIL_LOGD("Detected string with a size of %d byte%s", size, size > 0 ? "s" : ""); } break; } if (strings_count > 0 && data != NULL && size >= strings_count * sizeof(char *)) { buffer = calloc(1, size); for (i = 0; i < strings_count; i++) { if (((char **) data)[i] != NULL) { c = strdup(((char **) data)[i]); ((char **) buffer)[i] = c; } } data = buffer; } ril_request_register(request, data, size, token); if (buffer != NULL) free(buffer); ril_request_stats_log(); } RIL_RadioState ril_on_state_request(void) { if (ril_data == NULL) return RADIO_STATE_UNAVAILABLE; return ril_data->radio_state; } int ril_supports(int request) { unsigned int i; for (i = 0; i < ril_request_handlers_count; i++) { if (ril_request_handlers[i].handler == NULL) continue; if (ril_request_handlers[i].request == request) return 1; } return 0; } void ril_on_cancel(RIL_Token token) { struct ril_request *request; request = ril_request_find_token(token); if (request == NULL) return; ril_request_unregister(request); ril_request_stats_log(); } const char *ril_get_version(void) { return RIL_VERSION_STRING; } RIL_RadioFunctions ril_radio_functions = { RIL_VERSION, ril_on_request, ril_on_state_request, ril_supports, ril_on_cancel, ril_get_version }; const RIL_RadioFunctions *RIL_Init(const struct RIL_Env *env, int argc, char **argv) { RIL_RadioFunctions *radio_functions; pthread_attr_t attr; int rc; if (env == NULL) return NULL; rc = ril_data_create(); if (rc < 0) { RIL_LOGE("Creating RIL data failed"); return NULL; } ril_data->env = env; RIL_LOGD("creating RIL clients"); rc = create_qmi_clients(); if (rc < 0) { RIL_LOGE("Creating QMI clients failed"); goto error; } pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); rc = pthread_create(&ril_data->request_thread, &attr, ril_request_main_thread, NULL); if (rc != 0) { RIL_LOGE("Starting request main thread failed"); goto error; } radio_functions = &ril_radio_functions; goto complete; error: radio_functions = NULL; complete: return radio_functions; }