diff options
Diffstat (limited to 'tools/common/modem.c')
-rw-r--r-- | tools/common/modem.c | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/tools/common/modem.c b/tools/common/modem.c new file mode 100644 index 0000000..fac9797 --- /dev/null +++ b/tools/common/modem.c @@ -0,0 +1,413 @@ +/* + * This file is part of libsamsung-ipc. + * + * Copyright (C) 2014 Paul Kocialkowsk <contact@paulk.fr> + * Copyright (C) 2021 Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org> + * + * libsamsung-ipc 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. + * + * libsamsung-ipc 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 libsamsung-ipc. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> /* system("dmesg") */ +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include <sys/stat.h> +#include <sys/types.h> + +#include "modem.h" + +int seq; + +static enum modem_state current_state = MODEM_STATE_LPM; + +static enum modem_callback_state callback_state = MODEM_CALLBACK_STATE_UTILS; + +/* Taken from tools/ipc-modem.c */ +int seq_get(void) +{ + if (seq == 0xff) + seq = 0x00; + + seq++; + + return seq; +} + +static int modem_response_sec(struct ipc_client *client, + struct ipc_message *resp, + enum modem_state new_state) +{ + struct ipc_sec_pin_status_response_data *sim_status; + unsigned char type; + + if (!client) + return 0; + + + switch (resp->command) { + case IPC_SEC_PIN_STATUS: + sim_status = + (struct ipc_sec_pin_status_response_data *)resp->data; + + switch (sim_status->status) { + case IPC_SEC_PIN_STATUS_CARD_NOT_PRESENT: + ipc_client_log(client, "[I] SIM card not found\n"); + + if (new_state == MODEM_STATE_SIM_OK) + return -ENODEV; /* SIM not found but required */ + else + return 0; /* Modem init done */ + case IPC_SEC_PIN_STATUS_LOCK_SC: + /* TODO */ + switch (sim_status->facility_lock) { + case IPC_SEC_FACILITY_LOCK_TYPE_SC_PIN1_REQ: + ipc_client_log(client, + "[I] " + "The card is locked with the PIN1\n" + "TODO: implement unlocking the card" + ); + break; + case IPC_SEC_FACILITY_LOCK_TYPE_SC_PUK_REQ: + ipc_client_log(client, + "[I] " + "The card is locked with the PUK\n" + "TODO: implement unlocking the card" + ); + break; + case IPC_SEC_FACILITY_LOCK_TYPE_SC_CARD_BLOCKED: + ipc_client_log(client, "[I] " + "The SIM Card is blocked:\n" + "Unless you have a " + "programable card with the " + "ADM1 pin you might need to " + "contact your operator.\n"); + break; + } + + /* Only fail if the SIM card is required */ + if (new_state == MODEM_STATE_SIM_OK) + return -EACCES; + + break; + case IPC_SEC_PIN_STATUS_INIT_COMPLETE: + ipc_client_log(client, "[3] SIM init complete\n"); + if (current_state == MODEM_STATE_NORMAL) { + current_state = MODEM_STATE_SIM_OK; + /* In any case we're done when the SIM is + * ready + */ + return 0; + } + break; + case IPC_SEC_PIN_STATUS_PB_INIT_COMPLETE: + ipc_client_log(client, + "[I] SIM Phone Book init complete\n"); + break; + } + break; + case IPC_SEC_SIM_ICC_TYPE: + type = *((char *) resp->data); + switch (type) { + case IPC_SEC_SIM_CARD_TYPE_UNKNOWN: + ipc_client_log(client, + "[I] No SIM card type: unknown (absent?)" + "\n"); + + /* Only fail if the SIM card is required */ + if (new_state == MODEM_STATE_SIM_OK) + return -ENODEV; + break; + case IPC_SEC_SIM_CARD_TYPE_SIM: + case IPC_SEC_SIM_CARD_TYPE_USIM: + ipc_client_log(client, "[I] SIM card found\n"); + break; + } + break; + } + + return -EAGAIN; +} + +int modem_stop(struct ipc_client *client) +{ + int rc; + + ipc_client_log(client, "%s: ipc_client_close", __FUNCTION__); + rc = ipc_client_close(client); + if (rc < 0) { + ipc_client_log(client, "Closing failed: error %d\n", rc); + return rc; + } + + ipc_client_log(client, "%s: ipc_client_power_off", __FUNCTION__); + rc = ipc_client_power_off(client); + if (rc < 0) { + ipc_client_log(client, "Powering on failed: error %d\n", rc); + return rc; + } + + ipc_client_log(client, "%s: Modem stopped", __FUNCTION__); + + return 0; +} + +/* TODO: share the code */ +static int modem_response_pwr(struct ipc_client *client, + struct ipc_message *resp, + __attribute__((unused)) enum modem_state new_state) +{ + int state_n; + + if (!client) + return 0; + + switch (resp->command) { + case IPC_PWR_PHONE_PWR_UP: + ipc_client_log(client, "[2] Phone is powered up (LPM)!\n"); + current_state = MODEM_STATE_LPM; + break; + + case IPC_PWR_PHONE_STATE: + state_n = *((int *)resp->data); +#if 0 + switch (state_n) { + /* FIXME: Broken */ + case IPC_PWR_PHONE_STATE_NORMAL: + ipc_client_log(client, "Power state is now: NORMAL\n"); + break; + case IPC_PWR_PHONE_STATE_LPM: + ipc_client_log(client, "Power state is now: " + "LPM (Low Power Mode)?\n"); + break; + } +#else + ipc_client_log(client, "Power state is now: 0x%x\n", + current_state); +#endif + current_state = state_n; + break; + + } + + return 0; +} + +/* + * -EAGAIN is returned when modem_response_handle handled a + * response and another value < 0 when there were errors. + * It returns 0 when it's done initializing the modem, so + * application writers can more easily run the code they + * whish at this point and handle the responses the way they + * whish. + */ +static int modem_start_response_handle(struct ipc_client *client, + struct ipc_message *resp, + enum modem_state new_state) +{ + int rc; + + if (!client) + return 0; + + switch (IPC_GROUP(resp->command)) { + case IPC_GROUP_PWR: + rc = modem_response_pwr(client, resp, new_state); + if (rc == 0) + return -EAGAIN; + return rc; + case IPC_GROUP_SEC: + return modem_response_sec(client, resp, new_state); + default: + ipc_client_log(client, "Unhandled %s command", + ipc_group_string(IPC_GROUP(resp->command))); + return -EAGAIN; + } +} + +int register_app_modem_response_handler( + struct ipc_client *client, + struct app_modem_response_handler *handler, + int (*handler_func)(struct ipc_client *client, struct ipc_message *resp, void *handler_data), + void *handler_func_data) +{ + if (!client) + return 0; + + if (!handler) + return 0; + + handler->handler = handler_func; + handler->data = handler_func_data; + + return 0; +} + +static int modem_response_handle(struct ipc_client *client, + struct ipc_message *resp, + enum modem_state new_state, + struct app_modem_response_handler *handler) +{ + int rc; + + if (!client) + return 0; + + + if (callback_state == MODEM_CALLBACK_STATE_UTILS) + rc = modem_start_response_handle(client, resp, new_state); + else + rc = handler->handler(client, resp, handler->data); + + return rc; +} + +/* TODO: share modem_read_loop and fix it elsewhere too */ +/* TODO: new_state is not needed for the application */ +int modem_read_loop(struct ipc_client *client, + enum modem_state new_state, + struct app_modem_response_handler *handler) +{ + struct ipc_message resp; + int rc; + + if (!client) + return 0; + + memset(&resp, 0, sizeof(resp)); + + while (1) { + usleep(3000); + + rc = ipc_client_poll(client, NULL, NULL); + if (rc < 0) + continue; + + rc = ipc_client_recv(client, &resp); + if (rc < 0) { + if (resp.data != NULL) + free(resp.data); + return rc; + } + + rc = modem_response_handle(client, &resp, new_state, handler); + + ipc_client_log(client, "modem_response_handle: rc=%d", rc); + + if (resp.data != NULL) + free(resp.data); + + if (rc == -EAGAIN) { + continue; + } else if (rc < 0) { + return rc; + } else if (rc == 0) { + /* The callback exited normally because it reached + * new_state. + * It's now to the app callback to take over + */ + if (callback_state == MODEM_CALLBACK_STATE_UTILS) + callback_state = MODEM_CALLBACK_STATE_APP; + return 0; + } + } +} + +static int _modem_start(struct ipc_client *client) +{ + int rc; + + if (!client) + return 0; + + rc = ipc_client_data_create(client); + if (rc < 0) { + ipc_client_log(client, "Creating data failed: error %d\n", rc); + return rc; + } + + rc = ipc_client_boot(client); + if (rc < 0) { + ipc_client_log(client, "Booting failed: error %d\n", rc); + return rc; + } + + rc = ipc_client_power_on(client); + if (rc < 0) { + ipc_client_log(client, "Powering on failed: error %d\n", rc); + return rc; + } + + rc = ipc_client_open(client); + if (rc < 0) { + ipc_client_log(client, "ipc_client_open failed: error %d\n", + rc); + return rc; + } + + rc = ipc_client_power_on(client); + if (rc < 0) { + ipc_client_log(client, + "ipc_client_power_on failed: error %d\n", rc); + return rc; + } + + return rc; +} + +const char *modem_state_to_string(enum modem_state state) +{ + static char group_string[5] = { 0 }; + + switch (state) { + case MODEM_STATE_LPM: + return "MODEM_STATE_LPM"; + case MODEM_STATE_NORMAL: + return "MODEM_STATE_NORMAL"; + case MODEM_STATE_SIM_OK: + return "MODEM_STATE_SIM_OK"; + default: + snprintf((char *) &group_string, sizeof(group_string), "0x%02x", + (unsigned int)group_string); + return group_string; + } +}; + +int modem_start(struct ipc_client *client, enum modem_state new_state, + struct app_modem_response_handler *handler) +{ + int rc; + + if (!client) + return 0; + + ipc_client_log(client, "%s: requested state %s: %d\n", + __FUNCTION__, + modem_state_to_string(new_state), + new_state); + + rc = _modem_start(client); + if (rc < 0) { + ipc_client_destroy(client); + return 1; + } + + rc = modem_read_loop(client, new_state, handler); + if (rc < 0) + ipc_client_log(client, + "modem_read_loop failed: error %d\n", rc); + return 0; +} |