3 * AT chat library with GLib integration
5 * Copyright (C) 2008-2009 Intel Corporation. All rights reserved.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
32 #include "ringbuffer.h"
33 #include "gatresult.h"
36 /* #define WRITE_SCHEDULER_DEBUG 1 */
38 static void g_at_chat_wakeup_writer(GAtChat *chat);
41 PARSER_STATE_IDLE = 0,
42 PARSER_STATE_INITIAL_CR,
43 PARSER_STATE_INITIAL_LF,
44 PARSER_STATE_RESPONSE,
45 PARSER_STATE_TERMINATOR_CR,
46 PARSER_STATE_RESPONSE_COMPLETE,
47 PARSER_STATE_GUESS_MULTILINE_RESPONSE,
48 PARSER_STATE_MULTILINE_RESPONSE,
49 PARSER_STATE_MULTILINE_TERMINATOR_CR,
50 PARSER_STATE_MULTILINE_COMPLETE,
53 PARSER_STATE_PDU_COMPLETE,
55 PARSER_STATE_PROMPT_COMPLETE
62 GAtResultFunc callback;
64 GDestroyNotify notify;
67 struct at_notify_node {
69 GAtNotifyFunc callback;
71 GDestroyNotify notify;
80 gint ref_count; /* Ref count */
81 guint next_cmd_id; /* Next command id */
82 guint next_notify_id; /* Next notify id */
83 guint read_watch; /* GSource read id, 0 if none */
84 guint write_watch; /* GSource write id, 0 if none */
85 GIOChannel *channel; /* channel */
86 GQueue *command_queue; /* Command queue */
87 guint cmd_bytes_written; /* bytes written from cmd */
88 GHashTable *notify_list; /* List of notification reg */
89 GAtDisconnectFunc user_disconnect; /* user disconnect func */
90 gpointer user_disconnect_data; /* user disconnect data */
91 struct ring_buffer *buf; /* Current read buffer */
92 guint read_so_far; /* Number of bytes processed */
93 gboolean disconnecting; /* Whether we're disconnecting */
94 enum chat_state state; /* Current chat state */
96 char *pdu_notify; /* Unsolicited Resp w/ PDU */
97 GSList *response_lines; /* char * lines of the response */
98 char *wakeup; /* command sent to wakeup modem */
99 gdouble inactivity_time; /* Period of inactivity */
100 guint wakeup_timeout; /* How long to wait for resp */
101 GTimer *wakeup_timer; /* Keep track of elapsed time */
104 static gint at_notify_node_compare_by_id(gconstpointer a, gconstpointer b)
106 const struct at_notify_node *node = a;
107 guint id = GPOINTER_TO_UINT(b);
118 static void at_notify_node_destroy(struct at_notify_node *node)
121 node->notify(node->user_data);
126 static void at_notify_destroy(struct at_notify *notify)
128 g_slist_foreach(notify->nodes, (GFunc) at_notify_node_destroy, NULL);
132 static gint at_command_compare_by_id(gconstpointer a, gconstpointer b)
134 const struct at_command *command = a;
135 guint id = GPOINTER_TO_UINT(b);
137 if (command->id < id)
140 if (command->id > id)
146 static struct at_command *at_command_create(const char *cmd,
147 const char **prefix_list,
150 GDestroyNotify notify)
152 struct at_command *c;
154 char **prefixes = NULL;
157 int num_prefixes = 0;
160 while (prefix_list[num_prefixes])
163 prefixes = g_new(char *, num_prefixes + 1);
165 for (i = 0; i < num_prefixes; i++)
166 prefixes[i] = strdup(prefix_list[i]);
168 prefixes[num_prefixes] = NULL;
171 c = g_try_new0(struct at_command, 1);
177 c->cmd = g_try_new(char, len + 2);
184 memcpy(c->cmd, cmd, len);
186 /* If we have embedded '\r' then this is a command expecting a prompt
187 * from the modem. Embed Ctrl-Z at the very end automatically
189 if (strchr(cmd, '\r'))
194 c->cmd[len+1] = '\0';
196 c->prefixes = prefixes;
198 c->user_data = user_data;
204 static void at_command_destroy(struct at_command *cmd)
207 cmd->notify(cmd->user_data);
209 g_strfreev(cmd->prefixes);
214 static void g_at_chat_cleanup(GAtChat *chat)
216 struct at_command *c;
218 ring_buffer_free(chat->buf);
221 /* Cleanup pending commands */
222 while ((c = g_queue_pop_head(chat->command_queue)))
223 at_command_destroy(c);
225 g_queue_free(chat->command_queue);
226 chat->command_queue = NULL;
228 /* Cleanup any response lines we have pending */
229 g_slist_foreach(chat->response_lines, (GFunc)g_free, NULL);
230 g_slist_free(chat->response_lines);
231 chat->response_lines = NULL;
233 /* Cleanup registered notifications */
234 g_hash_table_destroy(chat->notify_list);
235 chat->notify_list = NULL;
237 if (chat->pdu_notify) {
238 g_free(chat->pdu_notify);
239 chat->pdu_notify = NULL;
243 g_free(chat->wakeup);
247 if (chat->wakeup_timer) {
248 g_timer_destroy(chat->wakeup_timer);
249 chat->wakeup_timer = 0;
253 static void read_watcher_destroy_notify(GAtChat *chat)
255 chat->read_watch = 0;
257 if (chat->disconnecting)
260 chat->channel = NULL;
262 g_at_chat_cleanup(chat);
264 if (chat->user_disconnect)
265 chat->user_disconnect(chat->user_disconnect_data);
268 static void write_watcher_destroy_notify(GAtChat *chat)
270 chat->write_watch = 0;
273 static void at_notify_call_callback(gpointer data, gpointer user_data)
275 struct at_notify_node *node = data;
276 GAtResult *result = user_data;
278 node->callback(result, node->user_data);
281 static gboolean g_at_chat_match_notify(GAtChat *chat, char *line)
284 struct at_notify *notify;
287 gboolean ret = FALSE;
290 g_hash_table_iter_init(&iter, chat->notify_list);
292 result.final_or_pdu = 0;
294 while (g_hash_table_iter_next(&iter, &key, &value)) {
298 if (!g_str_has_prefix(line, key))
302 chat->pdu_notify = line;
303 chat->state = PARSER_STATE_PDU;
308 result.lines = g_slist_prepend(NULL, line);
310 g_slist_foreach(notify->nodes, at_notify_call_callback,
316 g_slist_free(result.lines);
318 chat->state = PARSER_STATE_IDLE;
324 static void g_at_chat_finish_command(GAtChat *p, gboolean ok,
327 struct at_command *cmd = g_queue_pop_head(p->command_queue);
329 /* Cannot happen, but lets be paranoid */
336 p->response_lines = g_slist_reverse(p->response_lines);
338 result.final_or_pdu = final;
339 result.lines = p->response_lines;
341 cmd->callback(ok, &result, cmd->user_data);
344 g_slist_foreach(p->response_lines, (GFunc)g_free, NULL);
345 g_slist_free(p->response_lines);
346 p->response_lines = NULL;
350 at_command_destroy(cmd);
352 p->cmd_bytes_written = 0;
354 if (g_queue_peek_head(p->command_queue))
355 g_at_chat_wakeup_writer(p);
358 struct terminator_info {
359 const char *terminator;
364 static struct terminator_info terminator_table[] = {
366 { "ERROR", -1, FALSE },
367 { "NO DIALTONE", -1, FALSE },
368 { "BUSY", -1, FALSE },
369 { "NO CARRIER", -1, FALSE },
370 { "CONNECT", -1, TRUE },
371 { "NO ANSWER", -1, FALSE },
372 { "+CMS ERROR:", 11, FALSE },
373 { "+CME ERROR:", 11, FALSE },
374 { "+EXT ERROR:", 11, FALSE }
377 static gboolean g_at_chat_handle_command_response(GAtChat *p,
378 struct at_command *cmd,
382 int size = sizeof(terminator_table) / sizeof(struct terminator_info);
384 p->state = PARSER_STATE_IDLE;
386 for (i = 0; i < size; i++) {
387 struct terminator_info *info = &terminator_table[i];
389 if (info->len == -1 && !strcmp(line, info->terminator)) {
390 g_at_chat_finish_command(p, info->success, line);
395 !strncmp(line, info->terminator, info->len)) {
396 g_at_chat_finish_command(p, info->success, line);
404 for (i = 0; cmd->prefixes[i]; i++)
405 if (g_str_has_prefix(line, cmd->prefixes[i]))
412 if (!(p->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF))
413 p->state = PARSER_STATE_GUESS_MULTILINE_RESPONSE;
415 p->response_lines = g_slist_prepend(p->response_lines,
421 static void have_line(GAtChat *p, gboolean strip_preceding)
423 /* We're not going to copy terminal <CR><LF> */
424 unsigned int len = p->read_so_far - 2;
426 struct at_command *cmd;
428 /* If we have preceding <CR><LF> modify the len */
432 /* Make sure we have terminal null */
433 str = g_try_new(char, len + 1);
436 ring_buffer_drain(p->buf, p->read_so_far);
441 ring_buffer_drain(p->buf, 2);
442 ring_buffer_read(p->buf, str, len);
443 ring_buffer_drain(p->buf, 2);
447 /* Check for echo, this should not happen, but lets be paranoid */
448 if (!strncmp(str, "AT", 2) == TRUE)
451 cmd = g_queue_peek_head(p->command_queue);
453 if (cmd && p->cmd_bytes_written == strlen(cmd->cmd) &&
454 g_at_chat_handle_command_response(p, cmd, str))
457 if (g_at_chat_match_notify(p, str) == TRUE)
461 /* No matches & no commands active, ignore line */
463 p->state = PARSER_STATE_IDLE;
466 static void have_pdu(GAtChat *p)
468 unsigned int len = p->read_so_far - 2;
471 struct at_notify *notify;
476 pdu = g_try_new(char, len + 1);
479 ring_buffer_drain(p->buf, p->read_so_far);
483 ring_buffer_read(p->buf, pdu, len);
484 ring_buffer_drain(p->buf, 2);
488 result.lines = g_slist_prepend(NULL, p->pdu_notify);
489 result.final_or_pdu = pdu;
491 g_hash_table_iter_init(&iter, p->notify_list);
493 while (g_hash_table_iter_next(&iter, &key, &value)) {
497 if (!g_str_has_prefix(p->pdu_notify, prefix))
503 g_slist_foreach(notify->nodes, at_notify_call_callback,
507 g_slist_free(result.lines);
510 g_free(p->pdu_notify);
511 p->pdu_notify = NULL;
516 p->state = PARSER_STATE_IDLE;
519 static inline void parse_char(GAtChat *chat, char byte)
521 switch (chat->state) {
522 case PARSER_STATE_IDLE:
524 chat->state = PARSER_STATE_INITIAL_CR;
525 else if (chat->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF) {
527 chat->state = PARSER_STATE_PROMPT;
529 chat->state = PARSER_STATE_RESPONSE;
533 case PARSER_STATE_INITIAL_CR:
535 chat->state = PARSER_STATE_INITIAL_LF;
536 else if (byte != '\r' && /* Echo & no <CR><LF>?! */
537 (chat->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF))
538 chat->state = PARSER_STATE_RESPONSE;
539 else if (byte != '\r')
540 chat->state = PARSER_STATE_IDLE;
543 case PARSER_STATE_INITIAL_LF:
545 chat->state = PARSER_STATE_TERMINATOR_CR;
546 else if (byte == '>')
547 chat->state = PARSER_STATE_PROMPT;
549 chat->state = PARSER_STATE_RESPONSE;
552 case PARSER_STATE_RESPONSE:
554 chat->state = PARSER_STATE_TERMINATOR_CR;
557 case PARSER_STATE_TERMINATOR_CR:
559 chat->state = PARSER_STATE_RESPONSE_COMPLETE;
561 chat->state = PARSER_STATE_IDLE;
564 case PARSER_STATE_GUESS_MULTILINE_RESPONSE:
566 chat->state = PARSER_STATE_INITIAL_CR;
568 chat->state = PARSER_STATE_MULTILINE_RESPONSE;
571 case PARSER_STATE_MULTILINE_RESPONSE:
573 chat->state = PARSER_STATE_MULTILINE_TERMINATOR_CR;
576 case PARSER_STATE_MULTILINE_TERMINATOR_CR:
578 chat->state = PARSER_STATE_MULTILINE_COMPLETE;
581 case PARSER_STATE_PDU:
583 chat->state = PARSER_STATE_PDU_CR;
586 case PARSER_STATE_PDU_CR:
588 chat->state = PARSER_STATE_PDU_COMPLETE;
591 case PARSER_STATE_PROMPT:
593 chat->state = PARSER_STATE_PROMPT_COMPLETE;
595 chat->state = PARSER_STATE_RESPONSE;
597 case PARSER_STATE_RESPONSE_COMPLETE:
598 case PARSER_STATE_PDU_COMPLETE:
599 case PARSER_STATE_MULTILINE_COMPLETE:
601 /* This really shouldn't happen */
607 static void new_bytes(GAtChat *p)
609 unsigned int len = ring_buffer_len(p->buf);
610 unsigned int wrap = ring_buffer_len_no_wrap(p->buf);
611 unsigned char *buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
613 while (p->read_so_far < len) {
619 if (p->read_so_far == wrap) {
620 buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
624 if (p->state == PARSER_STATE_RESPONSE_COMPLETE) {
625 gboolean strip_preceding;
627 if (p->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF)
628 strip_preceding = FALSE;
630 strip_preceding = TRUE;
632 len -= p->read_so_far;
633 wrap -= p->read_so_far;
635 have_line(p, strip_preceding);
638 } else if (p->state == PARSER_STATE_MULTILINE_COMPLETE) {
639 len -= p->read_so_far;
640 wrap -= p->read_so_far;
645 } else if (p->state == PARSER_STATE_PDU_COMPLETE) {
646 len -= p->read_so_far;
647 wrap -= p->read_so_far;
652 } else if (p->state == PARSER_STATE_INITIAL_CR) {
653 len -= p->read_so_far - 1;
654 wrap -= p->read_so_far - 1;
656 ring_buffer_drain(p->buf, p->read_so_far - 1);
659 } else if (p->state == PARSER_STATE_PROMPT_COMPLETE) {
660 len -= p->read_so_far;
661 wrap -= p->read_so_far;
663 g_at_chat_wakeup_writer(p);
665 ring_buffer_drain(p->buf, p->read_so_far);
671 if (p->state == PARSER_STATE_IDLE && p->read_so_far > 0) {
672 ring_buffer_drain(p->buf, p->read_so_far);
677 static gboolean received_data(GIOChannel *channel, GIOCondition cond,
681 GAtChat *chat = data;
685 gsize total_read = 0;
687 if (cond & G_IO_NVAL)
690 /* Regardless of condition, try to read all the data available */
694 toread = ring_buffer_avail_no_wrap(chat->buf);
696 /* We're going to start overflowing the buffer
697 * this cannot happen under normal circumstances, so probably
698 * the channel is getting garbage, drop off
701 if (chat->state == PARSER_STATE_RESPONSE)
704 err = G_IO_ERROR_AGAIN;
708 buf = ring_buffer_write_ptr(chat->buf);
710 err = g_io_channel_read(channel, (char *) buf, toread, &rbytes);
712 total_read += rbytes;
715 ring_buffer_write_advance(chat->buf, rbytes);
717 } while (err == G_IO_ERROR_NONE && rbytes > 0);
722 if (cond & (G_IO_HUP | G_IO_ERR))
725 if (err == G_IO_ERROR_NONE && rbytes == 0)
728 if (err != G_IO_ERROR_NONE && err != G_IO_ERROR_AGAIN)
734 static gboolean wakeup_no_response(gpointer user)
736 GAtChat *chat = user;
738 g_at_chat_finish_command(chat, FALSE, NULL);
743 static gboolean can_write_data(GIOChannel *channel, GIOCondition cond,
746 GAtChat *chat = data;
747 struct at_command *cmd;
753 gboolean wakeup_first = FALSE;
754 #ifdef WRITE_SCHEDULER_DEBUG
758 if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
761 /* Grab the first command off the queue and write as
762 * much of it as we can
764 cmd = g_queue_peek_head(chat->command_queue);
766 /* For some reason command queue is empty, cancel write watcher */
770 len = strlen(cmd->cmd);
772 /* For some reason write watcher fired, but we've already
773 * written the entire command out to the io channel,
774 * cancel write watcher
776 if (chat->cmd_bytes_written >= len)
780 if (!chat->wakeup_timer) {
782 chat->wakeup_timer = g_timer_new();
784 } else if (g_timer_elapsed(chat->wakeup_timer, NULL) >
785 chat->inactivity_time)
789 if (chat->cmd_bytes_written == 0 && wakeup_first == TRUE) {
790 cmd = at_command_create(chat->wakeup, NULL, NULL, NULL, NULL);
795 g_queue_push_head(chat->command_queue, cmd);
797 len = strlen(chat->wakeup);
799 g_timeout_add(chat->wakeup_timeout, wakeup_no_response,
803 towrite = len - chat->cmd_bytes_written;
805 cr = strchr(cmd->cmd + chat->cmd_bytes_written, '\r');
808 towrite = cr - (cmd->cmd + chat->cmd_bytes_written) + 1;
810 #ifdef WRITE_SCHEDULER_DEBUG
817 err = g_io_channel_write(chat->channel,
818 cmd->cmd + chat->cmd_bytes_written,
819 #ifdef WRITE_SCHEDULER_DEBUG
826 if (err != G_IO_ERROR_NONE) {
827 g_at_chat_shutdown(chat);
831 chat->cmd_bytes_written += bytes_written;
833 if (bytes_written < towrite)
836 /* Full command submitted, update timer */
837 if (chat->wakeup_timer)
838 g_timer_start(chat->wakeup_timer);
843 static void g_at_chat_wakeup_writer(GAtChat *chat)
845 if (chat->write_watch != 0)
848 chat->write_watch = g_io_add_watch_full(chat->channel,
850 G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
851 can_write_data, chat,
852 (GDestroyNotify)write_watcher_destroy_notify);
855 GAtChat *g_at_chat_new(GIOChannel *channel, int flags)
863 chat = g_try_new0(GAtChat, 1);
868 chat->next_cmd_id = 1;
869 chat->next_notify_id = 1;
872 chat->buf = ring_buffer_new(4096);
877 chat->command_queue = g_queue_new();
879 if (!chat->command_queue)
882 chat->notify_list = g_hash_table_new_full(g_str_hash, g_str_equal,
883 g_free, (GDestroyNotify)at_notify_destroy);
885 if (g_io_channel_set_encoding(channel, NULL, NULL) !=
889 io_flags = g_io_channel_get_flags(channel);
891 io_flags |= G_IO_FLAG_NONBLOCK;
893 if (g_io_channel_set_flags(channel, io_flags, NULL) !=
897 g_io_channel_set_close_on_unref(channel, TRUE);
899 chat->channel = channel;
900 chat->read_watch = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT,
901 G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
903 (GDestroyNotify)read_watcher_destroy_notify);
909 ring_buffer_free(chat->buf);
911 if (chat->command_queue)
912 g_queue_free(chat->command_queue);
914 if (chat->notify_list)
915 g_hash_table_destroy(chat->notify_list);
921 GAtChat *g_at_chat_ref(GAtChat *chat)
926 g_atomic_int_inc(&chat->ref_count);
931 void g_at_chat_unref(GAtChat *chat)
938 is_zero = g_atomic_int_dec_and_test(&chat->ref_count);
941 g_at_chat_shutdown(chat);
943 g_at_chat_cleanup(chat);
948 gboolean g_at_chat_shutdown(GAtChat *chat)
950 if (chat->channel == NULL)
953 chat->disconnecting = TRUE;
955 if (chat->read_watch)
956 g_source_remove(chat->read_watch);
958 if (chat->write_watch)
959 g_source_remove(chat->write_watch);
964 gboolean g_at_chat_set_disconnect_function(GAtChat *chat,
965 GAtDisconnectFunc disconnect, gpointer user_data)
970 chat->user_disconnect = disconnect;
971 chat->user_disconnect_data = user_data;
976 guint g_at_chat_send(GAtChat *chat, const char *cmd,
977 const char **prefix_list, GAtResultFunc func,
978 gpointer user_data, GDestroyNotify notify)
980 struct at_command *c;
982 if (chat == NULL || chat->command_queue == NULL)
985 c = at_command_create(cmd, prefix_list, func, user_data, notify);
990 c->id = chat->next_cmd_id++;
992 g_queue_push_tail(chat->command_queue, c);
994 if (g_queue_get_length(chat->command_queue) == 1)
995 g_at_chat_wakeup_writer(chat);
1000 gboolean g_at_chat_cancel(GAtChat *chat, guint id)
1004 if (chat == NULL || chat->command_queue == NULL)
1007 l = g_queue_find_custom(chat->command_queue, GUINT_TO_POINTER(id),
1008 at_command_compare_by_id);
1013 if (l == g_queue_peek_head(chat->command_queue)) {
1014 struct at_command *c = l->data;
1016 /* We can't actually remove it since it is most likely
1017 * already in progress, just null out the callback
1018 * so it won't be called
1022 at_command_destroy(l->data);
1023 g_queue_remove(chat->command_queue, l->data);
1029 static struct at_notify *at_notify_create(GAtChat *chat, const char *prefix,
1032 struct at_notify *notify;
1035 key = g_strdup(prefix);
1040 notify = g_try_new0(struct at_notify, 1);
1049 g_hash_table_insert(chat->notify_list, key, notify);
1054 guint g_at_chat_register(GAtChat *chat, const char *prefix,
1055 GAtNotifyFunc func, gboolean expect_pdu,
1057 GDestroyNotify destroy_notify)
1059 struct at_notify *notify;
1060 struct at_notify_node *node;
1062 if (chat == NULL || chat->notify_list == NULL)
1068 if (prefix == NULL || strlen(prefix) == 0)
1071 notify = g_hash_table_lookup(chat->notify_list, prefix);
1074 notify = at_notify_create(chat, prefix, expect_pdu);
1076 if (!notify || notify->pdu != expect_pdu)
1079 node = g_try_new0(struct at_notify_node, 1);
1084 node->id = chat->next_notify_id++;
1085 node->callback = func;
1086 node->user_data = user_data;
1087 node->notify = destroy_notify;
1089 notify->nodes = g_slist_prepend(notify->nodes, node);
1094 gboolean g_at_chat_unregister(GAtChat *chat, guint id)
1096 GHashTableIter iter;
1097 struct at_notify *notify;
1099 gpointer key, value;
1102 if (chat == NULL || chat->notify_list == NULL)
1105 g_hash_table_iter_init(&iter, chat->notify_list);
1107 while (g_hash_table_iter_next(&iter, &key, &value)) {
1111 l = g_slist_find_custom(notify->nodes, GUINT_TO_POINTER(id),
1112 at_notify_node_compare_by_id);
1117 at_notify_node_destroy(l->data);
1118 notify->nodes = g_slist_remove(notify->nodes, l->data);
1120 if (notify->nodes == NULL)
1121 g_hash_table_iter_remove(&iter);
1129 gboolean g_at_chat_set_wakeup_command(GAtChat *chat, const char *cmd,
1130 unsigned int timeout, unsigned int msec)
1136 g_free(chat->wakeup);
1138 chat->wakeup = g_strdup(cmd);
1139 chat->inactivity_time = (gdouble)msec / 1000;
1140 chat->wakeup_timeout = timeout;