zephyr/drivers/modem/gsm_ppp.c
Göran Weinholt 1ccc65a9c9 drivers: modem: Get the IMSI and ICCID from the SIM
This adds an option to query the modem for the SIM's IMSI and ICCID
numbers, just like the modem's IMEI is queried today. This requires
the SIM to be present, which might not be the case for all
applications, so it can be disabled.

Signed-off-by: Göran Weinholt <goran.weinholt@endian.se>
Signed-off-by: Benjamin Lindqvist <benjamin.lindqvist@endian.se>
2020-08-18 09:39:49 +02:00

686 lines
16 KiB
C

/*
* Copyright (c) 2020 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_REGISTER(modem_gsm, CONFIG_MODEM_LOG_LEVEL);
#include <kernel.h>
#include <device.h>
#include <sys/ring_buffer.h>
#include <sys/util.h>
#include <net/ppp.h>
#include <drivers/uart.h>
#include <drivers/console/uart_mux.h>
#include "modem_context.h"
#include "modem_iface_uart.h"
#include "modem_cmd_handler.h"
#include "../console/gsm_mux.h"
#define GSM_CMD_READ_BUF 128
#define GSM_CMD_AT_TIMEOUT K_SECONDS(2)
#define GSM_CMD_SETUP_TIMEOUT K_SECONDS(6)
#define GSM_RX_STACK_SIZE CONFIG_MODEM_GSM_RX_STACK_SIZE
#define GSM_RECV_MAX_BUF 30
#define GSM_RECV_BUF_SIZE 128
#define GSM_BUF_ALLOC_TIMEOUT K_SECONDS(1)
/* During the modem setup, we first create DLCI control channel and then
* PPP and AT channels. Currently the modem does not create possible GNSS
* channel.
*/
enum setup_state {
STATE_INIT = 0,
STATE_CONTROL_CHANNEL = 0,
STATE_PPP_CHANNEL,
STATE_AT_CHANNEL,
STATE_DONE
};
static struct gsm_modem {
struct modem_context context;
struct modem_cmd_handler_data cmd_handler_data;
uint8_t cmd_read_buf[GSM_CMD_READ_BUF];
uint8_t cmd_match_buf[GSM_CMD_READ_BUF];
struct k_sem sem_response;
struct modem_iface_uart_data gsm_data;
struct k_delayed_work gsm_configure_work;
char gsm_isr_buf[PPP_MRU];
char gsm_rx_rb_buf[PPP_MRU * 3];
uint8_t *ppp_recv_buf;
size_t ppp_recv_buf_len;
enum setup_state state;
struct device *ppp_dev;
struct device *at_dev;
struct device *control_dev;
bool mux_enabled : 1;
bool mux_setup_done : 1;
bool setup_done : 1;
} gsm;
NET_BUF_POOL_DEFINE(gsm_recv_pool, GSM_RECV_MAX_BUF, GSM_RECV_BUF_SIZE,
0, NULL);
K_KERNEL_STACK_DEFINE(gsm_rx_stack, GSM_RX_STACK_SIZE);
struct k_thread gsm_rx_thread;
static void gsm_rx(struct gsm_modem *gsm)
{
LOG_DBG("starting");
while (true) {
k_sem_take(&gsm->gsm_data.rx_sem, K_FOREVER);
/* The handler will listen AT channel */
gsm->context.cmd_handler.process(&gsm->context.cmd_handler,
&gsm->context.iface);
}
}
MODEM_CMD_DEFINE(gsm_cmd_ok)
{
modem_cmd_handler_set_error(data, 0);
LOG_DBG("ok");
k_sem_give(&gsm.sem_response);
return 0;
}
MODEM_CMD_DEFINE(gsm_cmd_error)
{
modem_cmd_handler_set_error(data, -EINVAL);
LOG_DBG("error");
k_sem_give(&gsm.sem_response);
return 0;
}
static struct modem_cmd response_cmds[] = {
MODEM_CMD("OK", gsm_cmd_ok, 0U, ""),
MODEM_CMD("ERROR", gsm_cmd_error, 0U, ""),
MODEM_CMD("CONNECT", gsm_cmd_ok, 0U, ""),
};
#if defined(CONFIG_MODEM_SHELL)
#define MDM_MANUFACTURER_LENGTH 10
#define MDM_MODEL_LENGTH 16
#define MDM_REVISION_LENGTH 64
#define MDM_IMEI_LENGTH 16
#define MDM_IMSI_LENGTH 16
#define MDM_ICCID_LENGTH 32
struct modem_info {
char mdm_manufacturer[MDM_MANUFACTURER_LENGTH];
char mdm_model[MDM_MODEL_LENGTH];
char mdm_revision[MDM_REVISION_LENGTH];
char mdm_imei[MDM_IMEI_LENGTH];
#if defined(CONFIG_MODEM_SIM_NUMBERS)
char mdm_imsi[MDM_IMSI_LENGTH];
char mdm_iccid[MDM_ICCID_LENGTH];
#endif
};
static struct modem_info minfo;
/*
* Provide modem info if modem shell is enabled. This can be shown with
* "modem list" shell command.
*/
/* Handler: <manufacturer> */
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_manufacturer)
{
size_t out_len;
out_len = net_buf_linearize(minfo.mdm_manufacturer,
sizeof(minfo.mdm_manufacturer) - 1,
data->rx_buf, 0, len);
minfo.mdm_manufacturer[out_len] = '\0';
LOG_INF("Manufacturer: %s", log_strdup(minfo.mdm_manufacturer));
return 0;
}
/* Handler: <model> */
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_model)
{
size_t out_len;
out_len = net_buf_linearize(minfo.mdm_model,
sizeof(minfo.mdm_model) - 1,
data->rx_buf, 0, len);
minfo.mdm_model[out_len] = '\0';
LOG_INF("Model: %s", log_strdup(minfo.mdm_model));
return 0;
}
/* Handler: <rev> */
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_revision)
{
size_t out_len;
out_len = net_buf_linearize(minfo.mdm_revision,
sizeof(minfo.mdm_revision) - 1,
data->rx_buf, 0, len);
minfo.mdm_revision[out_len] = '\0';
LOG_INF("Revision: %s", log_strdup(minfo.mdm_revision));
return 0;
}
/* Handler: <IMEI> */
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_imei)
{
size_t out_len;
out_len = net_buf_linearize(minfo.mdm_imei, sizeof(minfo.mdm_imei) - 1,
data->rx_buf, 0, len);
minfo.mdm_imei[out_len] = '\0';
LOG_INF("IMEI: %s", log_strdup(minfo.mdm_imei));
return 0;
}
#if defined(CONFIG_MODEM_SIM_NUMBERS)
/* Handler: <IMSI> */
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_imsi)
{
size_t out_len;
out_len = net_buf_linearize(minfo.mdm_imsi, sizeof(minfo.mdm_imsi) - 1,
data->rx_buf, 0, len);
minfo.mdm_imsi[out_len] = '\0';
LOG_INF("IMSI: %s", log_strdup(minfo.mdm_imsi));
return 0;
}
/* Handler: <ICCID> */
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_iccid)
{
size_t out_len;
out_len = net_buf_linearize(minfo.mdm_iccid, sizeof(minfo.mdm_iccid) - 1,
data->rx_buf, 0, len);
minfo.mdm_iccid[out_len] = '\0';
if (minfo.mdm_iccid[0] == '+') {
/* Seen on U-blox SARA: "+CCID: nnnnnnnnnnnnnnnnnnnn".
* Skip over the +CCID bit, which other modems omit.
*/
char *p = strchr(minfo.mdm_iccid, ' ');
if (p) {
size_t len = strlen(p+1);
memmove(minfo.mdm_iccid, p+1, len+1);
}
}
LOG_INF("ICCID: %s", log_strdup(minfo.mdm_iccid));
return 0;
}
#endif /* CONFIG_MODEM_SIM_NUMBERS */
#endif /* CONFIG_MODEM_SHELL */
static struct setup_cmd setup_cmds[] = {
/* no echo */
SETUP_CMD_NOHANDLE("ATE0"),
/* hang up */
SETUP_CMD_NOHANDLE("ATH"),
/* extender errors in numeric form */
SETUP_CMD_NOHANDLE("AT+CMEE=1"),
#if defined(CONFIG_MODEM_SHELL)
/* query modem info */
SETUP_CMD("AT+CGMI", "", on_cmd_atcmdinfo_manufacturer, 0U, ""),
SETUP_CMD("AT+CGMM", "", on_cmd_atcmdinfo_model, 0U, ""),
SETUP_CMD("AT+CGMR", "", on_cmd_atcmdinfo_revision, 0U, ""),
# if defined(CONFIG_MODEM_SIM_NUMBERS)
SETUP_CMD("AT+CIMI", "", on_cmd_atcmdinfo_imsi, 0U, ""),
SETUP_CMD("AT+CCID", "", on_cmd_atcmdinfo_iccid, 0U, ""),
# endif
SETUP_CMD("AT+CGSN", "", on_cmd_atcmdinfo_imei, 0U, ""),
#endif
/* disable unsolicited network registration codes */
SETUP_CMD_NOHANDLE("AT+CREG=0"),
/* create PDP context */
SETUP_CMD_NOHANDLE("AT+CGDCONT=1,\"IP\",\"" CONFIG_MODEM_GSM_APN "\""),
};
static struct setup_cmd connect_cmds[] = {
/* connect to network */
SETUP_CMD_NOHANDLE("ATD*99#"),
};
static int gsm_setup_mccmno(struct gsm_modem *gsm)
{
int ret;
if (CONFIG_MODEM_GSM_MANUAL_MCCMNO[0]) {
/* use manual MCC/MNO entry */
ret = modem_cmd_send(&gsm->context.iface,
&gsm->context.cmd_handler,
NULL, 0,
"AT+COPS=1,2,\""
CONFIG_MODEM_GSM_MANUAL_MCCMNO
"\"",
&gsm->sem_response,
GSM_CMD_AT_TIMEOUT);
} else {
/* register operator automatically */
ret = modem_cmd_send(&gsm->context.iface,
&gsm->context.cmd_handler,
NULL, 0, "AT+COPS=0,0",
&gsm->sem_response,
GSM_CMD_AT_TIMEOUT);
}
if (ret < 0) {
LOG_ERR("AT+COPS ret:%d", ret);
}
return ret;
}
static void set_ppp_carrier_on(struct gsm_modem *gsm)
{
struct device *ppp_dev = device_get_binding(CONFIG_NET_PPP_DRV_NAME);
const struct ppp_api *api;
if (!ppp_dev) {
LOG_ERR("Cannot find PPP %s!", "device");
return;
}
api = (const struct ppp_api *)ppp_dev->api;
api->start(ppp_dev);
}
static void gsm_finalize_connection(struct gsm_modem *gsm)
{
int ret;
if (IS_ENABLED(CONFIG_GSM_MUX) && gsm->mux_enabled) {
ret = modem_cmd_send(&gsm->context.iface,
&gsm->context.cmd_handler,
&response_cmds[0],
ARRAY_SIZE(response_cmds),
"AT", &gsm->sem_response,
GSM_CMD_AT_TIMEOUT);
if (ret < 0) {
LOG_DBG("modem setup returned %d, %s",
ret, "retrying...");
(void)k_delayed_work_submit(&gsm->gsm_configure_work,
K_SECONDS(1));
return;
}
}
(void)gsm_setup_mccmno(gsm);
ret = modem_cmd_handler_setup_cmds(&gsm->context.iface,
&gsm->context.cmd_handler,
setup_cmds,
ARRAY_SIZE(setup_cmds),
&gsm->sem_response,
GSM_CMD_SETUP_TIMEOUT);
if (ret < 0) {
LOG_DBG("modem setup returned %d, %s",
ret, "retrying...");
(void)k_delayed_work_submit(&gsm->gsm_configure_work,
K_SECONDS(1));
return;
}
LOG_DBG("modem setup returned %d, %s", ret, "enable PPP");
ret = modem_cmd_handler_setup_cmds(&gsm->context.iface,
&gsm->context.cmd_handler,
connect_cmds,
ARRAY_SIZE(connect_cmds),
&gsm->sem_response,
GSM_CMD_SETUP_TIMEOUT);
if (ret < 0) {
LOG_DBG("modem setup returned %d, %s",
ret, "retrying...");
(void)k_delayed_work_submit(&gsm->gsm_configure_work,
K_SECONDS(1));
return;
}
gsm->setup_done = true;
/* If we are not muxing, the modem interface and gsm_rx() thread is not
* needed as PPP will handle the incoming traffic internally.
*/
if (!IS_ENABLED(CONFIG_GSM_MUX)) {
k_thread_abort(&gsm_rx_thread);
}
set_ppp_carrier_on(gsm);
if (IS_ENABLED(CONFIG_GSM_MUX) && gsm->mux_enabled) {
/* Re-use the original iface for AT channel */
ret = modem_iface_uart_init_dev(&gsm->context.iface,
gsm->at_dev->name);
if (ret < 0) {
LOG_DBG("iface %suart error %d", "AT ", ret);
} else {
/* Do a test and try to send AT command to modem */
ret = modem_cmd_send(&gsm->context.iface,
&gsm->context.cmd_handler,
&response_cmds[0],
ARRAY_SIZE(response_cmds),
"AT", &gsm->sem_response,
GSM_CMD_AT_TIMEOUT);
if (ret < 0) {
LOG_DBG("modem setup returned %d, %s",
ret, "AT cmds failed");
} else {
LOG_INF("AT channel %d connected to %s",
DLCI_AT, gsm->at_dev->name);
}
}
}
}
static int mux_enable(struct gsm_modem *gsm)
{
int ret;
/* Turn on muxing */
if (IS_ENABLED(CONFIG_MODEM_GSM_SIMCOM)) {
ret = modem_cmd_send(
&gsm->context.iface,
&gsm->context.cmd_handler,
&response_cmds[0],
ARRAY_SIZE(response_cmds),
#if defined(SIMCOM_LTE)
/* FIXME */
/* Some SIMCOM modems can set the channels */
/* Control channel always at DLCI 0 */
"AT+CMUXSRVPORT=0,0;"
/* PPP should be at DLCI 1 */
"+CMUXSRVPORT=" STRINGIFY(DLCI_PPP) ",1;"
/* AT should be at DLCI 2 */
"+CMUXSRVPORT=" STRINGIFY(DLCI_AT) ",1;"
#else
"AT"
#endif
"+CMUX=0,0,5,"
STRINGIFY(CONFIG_GSM_MUX_MRU_DEFAULT_LEN),
&gsm->sem_response,
GSM_CMD_AT_TIMEOUT);
} else {
/* Generic GSM modem */
ret = modem_cmd_send(&gsm->context.iface,
&gsm->context.cmd_handler,
&response_cmds[0],
ARRAY_SIZE(response_cmds),
"AT+CMUX=0", &gsm->sem_response,
GSM_CMD_AT_TIMEOUT);
}
if (ret < 0) {
LOG_ERR("AT+CMUX ret:%d", ret);
}
return ret;
}
static void mux_setup_next(struct gsm_modem *gsm)
{
(void)k_delayed_work_submit(&gsm->gsm_configure_work, K_MSEC(1));
}
static void mux_attach_cb(struct device *mux, int dlci_address,
bool connected, void *user_data)
{
LOG_DBG("DLCI %d to %s %s", dlci_address, mux->name,
connected ? "connected" : "disconnected");
if (connected) {
uart_irq_rx_enable(mux);
uart_irq_tx_enable(mux);
}
mux_setup_next(user_data);
}
static int mux_attach(struct device *mux, struct device *uart,
int dlci_address, void *user_data)
{
int ret = uart_mux_attach(mux, uart, dlci_address, mux_attach_cb,
user_data);
if (ret < 0) {
LOG_ERR("Cannot attach DLCI %d (%s) to %s (%d)", dlci_address,
mux->name, uart->name, ret);
return ret;
}
return 0;
}
static void mux_setup(struct k_work *work)
{
struct gsm_modem *gsm = CONTAINER_OF(work, struct gsm_modem,
gsm_configure_work);
struct device *uart = device_get_binding(CONFIG_MODEM_GSM_UART_NAME);
int ret;
switch (gsm->state) {
case STATE_CONTROL_CHANNEL:
/* Get UART device. There is one dev / DLCI */
gsm->control_dev = uart_mux_alloc();
if (gsm->control_dev == NULL) {
LOG_DBG("Cannot get UART mux for %s channel",
"control");
goto fail;
}
gsm->state = STATE_PPP_CHANNEL;
ret = mux_attach(gsm->control_dev, uart, DLCI_CONTROL, gsm);
if (ret < 0) {
goto fail;
}
break;
case STATE_PPP_CHANNEL:
gsm->ppp_dev = uart_mux_alloc();
if (gsm->ppp_dev == NULL) {
LOG_DBG("Cannot get UART mux for %s channel", "PPP");
goto fail;
}
gsm->state = STATE_AT_CHANNEL;
ret = mux_attach(gsm->ppp_dev, uart, DLCI_PPP, gsm);
if (ret < 0) {
goto fail;
}
break;
case STATE_AT_CHANNEL:
gsm->at_dev = uart_mux_alloc();
if (gsm->at_dev == NULL) {
LOG_DBG("Cannot get UART mux for %s channel", "AT");
goto fail;
}
gsm->state = STATE_DONE;
ret = mux_attach(gsm->at_dev, uart, DLCI_AT, gsm);
if (ret < 0) {
goto fail;
}
break;
case STATE_DONE:
/* At least the SIMCOM modem expects that the Internet
* connection is created in PPP channel. We will need
* to attach the AT channel to context iface after the
* PPP connection is established in order to give AT commands
* to the modem.
*/
ret = modem_iface_uart_init_dev(&gsm->context.iface,
gsm->ppp_dev->name);
if (ret < 0) {
LOG_DBG("iface %suart error %d", "PPP ", ret);
gsm->mux_enabled = false;
goto fail;
}
LOG_INF("PPP channel %d connected to %s",
DLCI_PPP, gsm->ppp_dev->name);
gsm_finalize_connection(gsm);
break;
}
return;
fail:
gsm->state = STATE_INIT;
gsm->mux_enabled = false;
}
static void gsm_configure(struct k_work *work)
{
struct gsm_modem *gsm = CONTAINER_OF(work, struct gsm_modem,
gsm_configure_work);
int ret = -1;
LOG_DBG("Starting modem %p configuration", gsm);
ret = modem_cmd_send(&gsm->context.iface,
&gsm->context.cmd_handler,
&response_cmds[0],
ARRAY_SIZE(response_cmds),
"AT", &gsm->sem_response,
GSM_CMD_AT_TIMEOUT);
if (ret < 0) {
LOG_DBG("modem not ready %d", ret);
(void)k_delayed_work_submit(&gsm->gsm_configure_work,
K_NO_WAIT);
return;
}
if (IS_ENABLED(CONFIG_GSM_MUX) && ret == 0 &&
gsm->mux_enabled == false) {
gsm->mux_setup_done = false;
ret = mux_enable(gsm);
if (ret == 0) {
gsm->mux_enabled = true;
} else {
gsm->mux_enabled = false;
}
LOG_DBG("GSM muxing %s", gsm->mux_enabled ? "enabled" :
"disabled");
if (gsm->mux_enabled) {
gsm->state = STATE_INIT;
k_delayed_work_init(&gsm->gsm_configure_work,
mux_setup);
(void)k_delayed_work_submit(&gsm->gsm_configure_work,
K_NO_WAIT);
return;
}
}
gsm_finalize_connection(gsm);
}
static int gsm_init(struct device *device)
{
struct gsm_modem *gsm = device->data;
int r;
LOG_DBG("Generic GSM modem (%p)", gsm);
gsm->cmd_handler_data.cmds[CMD_RESP] = response_cmds;
gsm->cmd_handler_data.cmds_len[CMD_RESP] = ARRAY_SIZE(response_cmds);
gsm->cmd_handler_data.read_buf = &gsm->cmd_read_buf[0];
gsm->cmd_handler_data.read_buf_len = sizeof(gsm->cmd_read_buf);
gsm->cmd_handler_data.match_buf = &gsm->cmd_match_buf[0];
gsm->cmd_handler_data.match_buf_len = sizeof(gsm->cmd_match_buf);
gsm->cmd_handler_data.buf_pool = &gsm_recv_pool;
gsm->cmd_handler_data.alloc_timeout = GSM_BUF_ALLOC_TIMEOUT;
gsm->cmd_handler_data.eol = "\r";
k_sem_init(&gsm->sem_response, 0, 1);
r = modem_cmd_handler_init(&gsm->context.cmd_handler,
&gsm->cmd_handler_data);
if (r < 0) {
LOG_DBG("cmd handler error %d", r);
return r;
}
#if defined(CONFIG_MODEM_SHELL)
/* modem information storage */
gsm->context.data_manufacturer = minfo.mdm_manufacturer;
gsm->context.data_model = minfo.mdm_model;
gsm->context.data_revision = minfo.mdm_revision;
gsm->context.data_imei = minfo.mdm_imei;
#if defined(CONFIG_MODEM_SIM_NUMBERS)
gsm->context.data_imsi = minfo.mdm_imsi;
gsm->context.data_iccid = minfo.mdm_iccid;
#endif /* CONFIG_MODEM_SIM_NUMBERS */
#endif /* CONFIG_MODEM_SHELL */
gsm->gsm_data.isr_buf = &gsm->gsm_isr_buf[0];
gsm->gsm_data.isr_buf_len = sizeof(gsm->gsm_isr_buf);
gsm->gsm_data.rx_rb_buf = &gsm->gsm_rx_rb_buf[0];
gsm->gsm_data.rx_rb_buf_len = sizeof(gsm->gsm_rx_rb_buf);
r = modem_iface_uart_init(&gsm->context.iface, &gsm->gsm_data,
CONFIG_MODEM_GSM_UART_NAME);
if (r < 0) {
LOG_DBG("iface uart error %d", r);
return r;
}
r = modem_context_register(&gsm->context);
if (r < 0) {
LOG_DBG("context error %d", r);
return r;
}
LOG_DBG("iface->read %p iface->write %p",
gsm->context.iface.read, gsm->context.iface.write);
k_thread_create(&gsm_rx_thread, gsm_rx_stack,
K_KERNEL_STACK_SIZEOF(gsm_rx_stack),
(k_thread_entry_t) gsm_rx,
gsm, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT);
k_thread_name_set(&gsm_rx_thread, "gsm_rx");
k_delayed_work_init(&gsm->gsm_configure_work, gsm_configure);
(void)k_delayed_work_submit(&gsm->gsm_configure_work, K_NO_WAIT);
return 0;
}
DEVICE_INIT(gsm_ppp, "modem_gsm", gsm_init, &gsm, NULL, POST_KERNEL,
CONFIG_MODEM_GSM_INIT_PRIORITY);