quectel: support both internal and n_gsm muxes

The in-kernel implementation of gsm0710 causes deadlocks in the
kernel[1], so switch the default back to the user-space implementation
in ofono.

The change also removes the timeout-callback used to defer disabling the
n_gsm line discipline, as that is no longer needed[2]

To enable use of the kernel line discipline, add an udev env entry with
OFONO_QUECTEL_MUX="n_gsm".

[1] https://lore.kernel.org/lkml/4b2455c0-25ba-0187-6df6-c63b4ccc6a6e@geanix.com/
[2] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7030082a7415d18e3befdf1f9ec05b3d5de98de4
This commit is contained in:
Martin Hundebøll 2019-10-07 23:39:59 +02:00 committed by Denis Kenzior
parent ad73e590e2
commit d0398b3965
1 changed files with 161 additions and 42 deletions

View File

@ -36,6 +36,7 @@
#include <ell/ell.h>
#include <gatchat.h>
#include <gattty.h>
#include <gatmux.h>
#define OFONO_API_SUBJECT_TO_CHANGE
#include <ofono.h>
@ -95,7 +96,9 @@ struct quectel_data {
bool sim_ready;
/* used by quectel uart driver */
GIOChannel *device;
GAtChat *uart;
GAtMux *mux;
int mux_ready_count;
int initial_ldisc;
struct l_gpio_writer *gpio;
@ -192,43 +195,48 @@ static void quectel_remove(struct ofono_modem *modem)
g_at_chat_unref(data->aux);
g_at_chat_unref(data->modem);
g_at_chat_unref(data->uart);
g_at_mux_unref(data->mux);
if (data->device)
g_io_channel_unref(data->device);
l_free(data);
}
static void close_mux_cb(struct l_timeout *timeout, void *user_data)
static void close_mux(struct ofono_modem *modem)
{
struct quectel_data *data = ofono_modem_get_data(modem);
DBG("%p", modem);
g_io_channel_unref(data->device);
data->device = NULL;
g_at_mux_unref(data->mux);
data->mux = NULL;
}
static void close_ngsm(struct ofono_modem *modem)
{
struct ofono_modem *modem = user_data;
struct quectel_data *data = ofono_modem_get_data(modem);
GIOChannel *device;
uint32_t gpio_value = 0;
ssize_t write_count;
int fd;
DBG("%p", modem);
device = g_at_chat_get_channel(data->uart);
fd = g_io_channel_unix_get_fd(device);
if (!data->device)
return;
fd = g_io_channel_unix_get_fd(data->device);
/* restore initial tty line discipline */
if (ioctl(fd, TIOCSETD, &data->initial_ldisc) < 0)
ofono_warn("Failed to restore line discipline");
/* terminate gsm 0710 multiplexing on the modem side */
write_count = write(fd, gsm0710_terminate, sizeof(gsm0710_terminate));
if (write_count != sizeof(gsm0710_terminate))
ofono_warn("Failed to terminate gsm multiplexing");
g_at_chat_unref(data->uart);
data->uart = NULL;
l_timeout_remove(timeout);
l_gpio_writer_set(data->gpio, 1, &gpio_value);
ofono_modem_set_powered(modem, FALSE);
}
static void close_serial(struct ofono_modem *modem)
{
struct quectel_data *data = ofono_modem_get_data(modem);
uint32_t gpio_value = 0;
DBG("%p", modem);
@ -241,19 +249,16 @@ static void close_serial(struct ofono_modem *modem)
g_at_chat_unref(data->modem);
data->modem = NULL;
/*
* if gsm0710 multiplexing is used, the aux and modem file descriptors
* must be closed before closing the underlying serial device to avoid
* an old kernel dead-lock:
* https://lists.ofono.org/pipermail/ofono/2011-March/009405.html
*
* setup a timer to iterate the mainloop once to let gatchat close the
* virtual file descriptors unreferenced above
*/
if (data->uart)
l_timeout_create_ms(1, close_mux_cb, modem, NULL);
g_at_chat_unref(data->uart);
data->uart = NULL;
if (data->mux)
close_mux(modem);
else
ofono_modem_set_powered(modem, false);
close_ngsm(modem);
l_gpio_writer_set(data->gpio, 1, &gpio_value);
ofono_modem_set_powered(modem, FALSE);
}
static void dbus_hw_reply_properties(struct dbus_hw *hw)
@ -793,6 +798,19 @@ static void cgmm_cb(int ok, GAtResult *result, void *user_data)
NULL);
}
static void setup_aux(struct ofono_modem *modem)
{
struct quectel_data *data = ofono_modem_get_data(modem);
DBG("%p", modem);
g_at_chat_set_slave(data->modem, data->aux);
g_at_chat_send(data->aux, "ATE0; &C0; +CMEE=1; +QIURC=0", none_prefix,
NULL, NULL, NULL);
g_at_chat_send(data->aux, "AT+CGMM", cgmm_prefix, cgmm_cb, modem,
NULL);
}
static int open_ttys(struct ofono_modem *modem)
{
struct quectel_data *data = ofono_modem_get_data(modem);
@ -812,16 +830,73 @@ static int open_ttys(struct ofono_modem *modem)
return -EIO;
}
g_at_chat_set_slave(data->modem, data->aux);
g_at_chat_send(data->aux, "ATE0; &C0; +CMEE=1; +QIURC=0", none_prefix,
NULL, NULL, NULL);
g_at_chat_send(data->aux, "AT+CGMM", cgmm_prefix, cgmm_cb, modem,
NULL);
setup_aux(modem);
return -EINPROGRESS;
}
static GAtChat *create_chat(struct ofono_modem *modem, char *debug)
{
struct quectel_data *data = ofono_modem_get_data(modem);
GIOChannel *channel;
GAtSyntax *syntax;
GAtChat *chat;
DBG("%p", modem);
channel = g_at_mux_create_channel(data->mux);
if (channel == NULL)
return NULL;
syntax = g_at_syntax_new_gsmv1();
chat = g_at_chat_new(channel, syntax);
g_at_syntax_unref(syntax);
g_io_channel_unref(channel);
if (chat == NULL)
return NULL;
if (getenv("OFONO_AT_DEBUG"))
g_at_chat_set_debug(chat, quectel_debug, debug);
return chat;
}
static void cmux_gatmux(struct ofono_modem *modem)
{
struct quectel_data *data = ofono_modem_get_data(modem);
DBG("%p", modem);
data->mux = g_at_mux_new_gsm0710_basic(data->device, 127);
if (data->mux == NULL) {
ofono_error("failed to create gsm0710 mux");
close_serial(modem);
return;
}
if (getenv("OFONO_MUX_DEBUG"))
g_at_mux_set_debug(data->mux, quectel_debug, "Mux: ");
g_at_mux_start(data->mux);
data->modem = create_chat(modem, "Modem: ");
if (!data->modem) {
ofono_error("failed to create modem channel");
close_serial(modem);
return;
}
data->aux = create_chat(modem, "Aux: ");
if (!data->aux) {
ofono_error("failed to create aux channel");
close_serial(modem);
return;
}
setup_aux(modem);
}
static void mux_ready_cb(struct l_timeout *timeout, void *user_data)
{
struct ofono_modem *modem = user_data;
@ -854,19 +929,16 @@ static void mux_ready_cb(struct l_timeout *timeout, void *user_data)
g_at_chat_set_slave(data->uart, data->modem);
}
static void cmux_cb(gboolean ok, GAtResult *result, gpointer user_data)
static void cmux_ngsm(struct ofono_modem *modem)
{
struct ofono_modem *modem = user_data;
struct quectel_data *data = ofono_modem_get_data(modem);
struct gsm_config gsm_config;
GIOChannel *device;
int ldisc = N_GSM0710;
int fd;
DBG("%p", modem);
device = g_at_chat_get_channel(data->uart);
fd = g_io_channel_unix_get_fd(device);
fd = g_io_channel_unix_get_fd(data->device);
/* get initial line discipline to restore after use */
if (ioctl(fd, TIOCGETD, &data->initial_ldisc) < 0) {
@ -922,6 +994,39 @@ static void cmux_cb(gboolean ok, GAtResult *result, gpointer user_data)
}
}
static void cmux_cb(gboolean ok, GAtResult *result, gpointer user_data)
{
struct ofono_modem *modem = user_data;
struct quectel_data *data = ofono_modem_get_data(modem);
const char *mux = ofono_modem_get_string(modem, "Mux");
DBG("%p", modem);
g_at_chat_unref(data->uart);
data->uart = NULL;
if (!ok) {
close_serial(modem);
return;
}
if (!mux)
mux = "internal";
if (strcmp(mux, "n_gsm") == 0) {
cmux_ngsm(modem);
return;
}
if (strcmp(mux, "internal") == 0) {
cmux_gatmux(modem);
return;
}
ofono_error("unsupported mux setting: '%s'", mux);
close_serial(modem);
}
static void ate_cb(int ok, GAtResult *result, void *user_data)
{
struct ofono_modem *modem = user_data;
@ -979,6 +1084,8 @@ static int open_serial(struct ofono_modem *modem)
struct quectel_data *data = ofono_modem_get_data(modem);
const uint32_t gpio_value = 1;
const char *rts_cts;
ssize_t written;
int fd;
DBG("%p", modem);
@ -998,6 +1105,18 @@ static int open_serial(struct ofono_modem *modem)
if (data->uart == NULL)
return -EINVAL;
data->device = g_at_chat_get_channel(data->uart);
g_io_channel_ref(data->device);
/*
* terminate gsm 0710 multiplexing on the modem side to make sure it
* responds to plain AT commands
* */
fd = g_io_channel_unix_get_fd(data->device);
written = write(fd, gsm0710_terminate, sizeof(gsm0710_terminate));
if (written != sizeof(gsm0710_terminate))
ofono_warn("Failed to terminate gsm multiplexing");
if (data->gpio && !l_gpio_writer_set(data->gpio, 1, &gpio_value)) {
close_serial(modem);
return -EIO;