diff options
Diffstat (limited to 'src/modem.c')
-rw-r--r-- | src/modem.c | 575 |
1 files changed, 466 insertions, 109 deletions
diff --git a/src/modem.c b/src/modem.c index 4cf9a49..71a8eb4 100644 --- a/src/modem.c +++ b/src/modem.c @@ -9,6 +9,7 @@ #include <sys/time.h> #include <time.h> #include <stdio.h> +#include <sys/wait.h> #include <oop.h> @@ -24,26 +25,34 @@ /* Baudrate for the TTY. Should be greater the 64000. */ #define BAUD_RATE B115200 -#define RESET_IDLE_TIME 2 +#define RESET_IDLE_TIME 10 #define INIT_TIMEOUT 5 #define ACCEPT_TIMEOUT 5 #define BASIC_TIMEOUT 5 +#define CHILD_TIMEOUT 10 +#define HANGUP_TIMEOUT 10 +#define ISDN_DELAY_USEC 125L +#define ISDN_LATENCY_USEC 25000L -#define INIT_AT_COMMANDS 12 +#define INPUT_WATERMARK_DEFAULT -1 +#define INPUT_WATERMARK_CONNECTION 2 +#define INPUT_WATERMARK_CHILD 1 + +#define INIT_AT_COMMANDS 16 static const char* const init_at_commands[INIT_AT_COMMANDS*2] = { - "\nAT&F\n", + "\nAT&F\n", /* Reset to fabric defaults */ "OK\r\n", - "ATI\n", + "ATI\n", /* Make sure this is an isdn4linux device */ "Linux ISDN\r\nOK\r\n", - "AT&B16\n", + "AT+FCLASS=8\n", /* switch to audio mode */ "OK\r\n", - "AT+FCLASS=8\n", + "AT+VIP\n", /* Reset audio parameters */ "OK\r\n", - - "AT+VSM=6\n", + + "AT+VSM=6\n", /* Set uLaw encoding */ "OK\r\n", "ATS18=1\n", @@ -55,97 +64,75 @@ static const char* const init_at_commands[INIT_AT_COMMANDS*2] = { "ATS13.4=0\n", "OK\r\n", - "ATS13.0=1\n", + "ATS13.0=1\n", /* direct tty send */ + "OK\r\n", + + "AT\n", //S13.2=0\n", /* Don't hangup on DTR low */ "OK\r\n", "ATS23=1\n", "OK\r\n", + + "ATS16=1\n", /* send packet size */ + "OK\r\n", + + "AT\n", //S12.3=0\n", /* DCD always on */ + "OK\r\n", + + "AT\n", //S12.6=0\n", /* DSR always on */ + "OK\r\n", - "ATS0=0\n", + "ATS0=0\n", /* No automatic call answering */ "OK\r\n", - "AT&L%s\n", + "AT&L%s\n", /* Listen on this MSN */ "OK\r\n", }; -static void modem_output_empty_cb(struct buffio *b, void *user); -static void modem_input_ready_cb(struct buffio *b, void *user); +static const char hup_sequence[] = { DLE, DC4, DLE, ETX, 0 }; +static const char ath_sequence[] = "\nATH\n"; + +static int modem_output_empty_cb(struct buffio *b, void *user); +static int modem_input_ready_cb(struct buffio *b, void *user); static void* oop_timeout_cb(oop_source *source, struct timeval tv, void *user); static void modem_init_cycle(struct modem *m); static void modem_timeout(struct modem *m, int t); +static void copy_child_to_modem(struct modem *m); +static void copy_modem_to_child(struct modem *m); +static int modem_reopen(struct modem *m); +void modem_close(struct modem *m); + struct modem *modem_open(const char *dev) { struct modem *m; - int fd = -1, n; - struct termios ts, ts2; assert(dev); if (device_lock(dev) != 0) return NULL; - if ((fd = open(dev, O_RDWR|O_NDELAY)) < 0) { - daemon_log(LOG_ERR, "Failed to open device <%s>: %s", dev, strerror(errno)); - goto fail; - } - - if ((n = fcntl(fd, F_GETFL, 0)) < 0 || fcntl(fd, F_SETFL, n & ~O_NDELAY) < 0) { - daemon_log(LOG_ERR, "Failed to remove O_NDELAY flag from device: %s", strerror(errno)); - goto fail; - } - - memset(&ts, 0, sizeof ts); - ts.c_cflag = CRTSCTS | IGNPAR | HUPCL | CS8; - ts.c_iflag = IGNPAR; - ts.c_oflag = 0; - ts.c_lflag = 0; - - ts.c_cc[VMIN] = 1; - ts.c_cc[VTIME] = 0; - - cfsetospeed(&ts, BAUD_RATE); - cfsetispeed(&ts, BAUD_RATE); - - tcflush(fd, TCIFLUSH); - - if (tcsetattr(fd, TCSANOW, &ts) < 0) { - daemon_log(LOG_ERR, "Failed to set TTY attributes: %s", strerror(errno)); - goto fail; - } - - if (tcgetattr(fd, &ts2) < 0 || memcmp(&ts, &ts2, sizeof(ts)) != 0) { - daemon_log(LOG_ERR, "Failed to set TTY attributes"); - /*goto fail;*/ /* DON'T FORGET TO REMOVE THE COMMENT MARKS */ - } - m = malloc(sizeof(struct modem)); assert(m); memset(m, 0, sizeof(struct modem)); + m->dev = strdup(dev); assert(m->dev); - m->buffio = buffio_new(fd, fd); - assert(m->buffio); - - m->buffio->output_empty_cb = modem_output_empty_cb; - m->buffio->input_ready_cb = modem_input_ready_cb; - m->buffio->user = m; m->child_pid = -1; - + m->child_buffio = NULL; m->local_msn = "46"; m->tabentry = NULL; - - modem_init_cycle(m); + if (modem_reopen(m) < 0) + goto fail; + + modem_init_cycle(m); + return m; fail: - if (fd >= 0) - close(fd); - - device_unlock(dev); - + modem_close(m); return NULL; } @@ -161,14 +148,28 @@ void modem_close(struct modem *m) { if (m->child_pid != -1) child_process_kill(m->child_pid); - buffio_free(m->buffio); + if (m->child_buffio) + buffio_free(m->child_buffio); + + if (m->buffio) + buffio_free(m->buffio); + device_unlock(m->dev); + free(m->dev); free(m->caller_number); free(m->ring_number); free(m); } +static int error_cb(struct buffio *b, void *user) { + struct modem *m = user; + assert(m && (m->buffio == b || m->child_buffio == b)); + + daemon_log(LOG_ERR, "Buffio (%s) failure, exiting...", m->buffio == b ? "modem" : "child"); + return -1; +} + static void modem_timeout(struct modem *m, int t) { assert(m); @@ -192,7 +193,9 @@ static void modem_next_command(struct modem *m) { p = init_at_commands[m->command_index*2]; - if (m->command_index == 11) { + if (m->command_index == 0) + buffio_command(m->buffio, hup_sequence); + else if (m->command_index == 15) { snprintf(tmp, sizeof(tmp), p, m->local_msn); p = tmp; } @@ -210,7 +213,13 @@ static void modem_init_cycle(struct modem *m) { modem_next_command(m); } -void modem_force_hangup(struct modem *m) { +static void modem_sched_reset(struct modem *m) { + daemon_log(LOG_INFO, "Reinitializing in %i seconds.", RESET_IDLE_TIME); + m->state = MODEM_STATE_IDLE; + modem_timeout(m, RESET_IDLE_TIME); +} + +static void modem_force_hangup(struct modem *m) { struct termios pts; assert(m && m->buffio); @@ -225,6 +234,193 @@ void modem_force_hangup(struct modem *m) { tcsetattr(m->buffio->ifd, TCSANOW, &pts); } +static void modem_hangup_seq(struct modem *m) { + assert(m); + + if (m->child_buffio) { + + if (buffio_output_is_empty(m->child_buffio)) + buffio_close_output_fd(m->child_buffio); + + if (m->child_buffio->ifd == -1 && m->child_buffio->ofd == -1) { + buffio_free(m->child_buffio); + m->child_buffio = NULL; + } + } + + if (m->child_buffio && m->child_buffio->ofd != -1) { + /* Draining output is required */ + daemon_log(LOG_INFO, "Waiting for child drain."); + m->state = MODEM_STATE_CHILD_DRAIN; + modem_timeout(m, CHILD_TIMEOUT); + + } else if (m->child_buffio) { + /* Draining input is required */ + daemon_log(LOG_INFO, "Waiting for child EOF."); + m->state = MODEM_STATE_CHILD_WAIT_EOF; + buffio_close_output_fd(m->child_buffio); + buffio_flush_input(m->child_buffio); + modem_timeout(m, CHILD_TIMEOUT); + + } else if (m->child_pid != (pid_t) -1) { + daemon_log(LOG_INFO, "Killing child."); + m->state = MODEM_STATE_CHILD_KILL; + child_process_kill(m->child_pid); + modem_timeout(m, CHILD_TIMEOUT); + + } else + modem_sched_reset(m); +} + +static void modem_hangup(struct modem *m, int b) { + size_t l; + assert(m); + daemon_log(LOG_INFO, "Hanging up."); + + buffio_set_input_watermark(m->buffio, INPUT_WATERMARK_DEFAULT); + buffio_set_delay_usec(m->buffio, 0, 0); + + l = strlen(ath_sequence) + (b ? strlen(hup_sequence) : 0); + + if (buffio_can_write(m->buffio, l)) { + + if (b) + buffio_command(m->buffio, hup_sequence); + + buffio_command(m->buffio, ath_sequence); + m->state = MODEM_STATE_HANGUP; + } else + m->state = b ? MODEM_STATE_PRE_HANGUP_HUPSEQ : MODEM_STATE_PRE_HANGUP_ATHSEQ; + + modem_timeout(m, HANGUP_TIMEOUT); +} + +static void child_exit_cb(pid_t t, int status, void *user) { + struct modem *m = user; + assert(m && m->child_pid == t); + + if (WIFEXITED(status)) + daemon_log(WEXITSTATUS(status) == 0 ? LOG_INFO : LOG_WARNING, "Child exited with return code %i.", WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + daemon_log(LOG_WARNING, "Child exited by signal %i.", WTERMSIG(status)); + else + daemon_log(LOG_ERR, "Child exited due to unknown cause."); + + m->child_pid = (pid_t) -1; + + switch (m->state) { + + case MODEM_STATE_CHILD_KILL: + modem_hangup_seq(m); + break; + + default: + break; + } +} + +static int child_input_ready_cb(struct buffio *b, void *user) { + struct modem *m = (struct modem*) user; + assert(m && m->child_buffio == b); + + if (m->state == MODEM_STATE_CONNECTION) + copy_child_to_modem(m); + else + buffio_flush_input(m->child_buffio); + + return 0; +} + +static int child_eof_cb(struct buffio *b, void *user) { + struct modem *m = user; + assert(m && m->child_buffio == b); + + daemon_log(LOG_INFO, "Got child EOF."); + + switch (m->state) { + + case MODEM_STATE_CONNECTION: + modem_hangup(m, 1); + break; + + case MODEM_STATE_CHILD_WAIT_EOF: + modem_hangup_seq(m); + break; + + default: + break; + } + + return 0; +} + +static int child_epipe_cb(struct buffio *b, void *user) { + struct modem *m = user; + assert(m && m->child_buffio == b); + + daemon_log(LOG_INFO, "Got child EPIPE."); + + assert(m->child_buffio); + buffio_flush_output(m->child_buffio); + + switch (m->state) { + + case MODEM_STATE_CONNECTION: + modem_hangup(m, 1); + break; + + case MODEM_STATE_CHILD_DRAIN: + modem_hangup_seq(m); + break; + + default: + break; + } + + return 0; +} + +static int child_output_empty_cb(struct buffio *b, void *user) { + struct modem *m = user; + assert(m && m->child_buffio == b); + + switch (m->state) { + + case MODEM_STATE_CONNECTION: + copy_modem_to_child(m); + break; + + case MODEM_STATE_CHILD_DRAIN: + modem_hangup_seq(m); + break; + + default: + break; + } + + return 0; +} + +static int modem_start_child(struct modem *m) { + int ifd, ofd; + assert(m && m->child_pid == (pid_t) -1 && m->tabentry && m->state == MODEM_STATE_CONNECTION); + + assert(m->tabentry->args && m->tabentry->args[0]); + if ((m->child_pid = child_process_create(m->tabentry->args[0], m->tabentry->args, &ifd, &ofd, child_exit_cb, m)) < 0) + return -1; + + assert(ifd >= 0 && ofd >= 0 && !m->child_buffio); + m->child_buffio = buffio_new(ifd, ofd); + buffio_set_input_watermark(m->child_buffio, INPUT_WATERMARK_CHILD); + m->child_buffio->user = m; + m->child_buffio->eof_cb = child_eof_cb; + m->child_buffio->epipe_cb = child_epipe_cb; + m->child_buffio->input_ready_cb = child_input_ready_cb; + m->child_buffio->output_empty_cb = child_output_empty_cb; + m->child_buffio->error_cb = error_cb; + + return 0; +} static void* oop_timeout_cb(oop_source *source, struct timeval tv, void *user) { struct modem *m = (struct modem*) user; @@ -236,14 +432,17 @@ static void* oop_timeout_cb(oop_source *source, struct timeval tv, void *user) { case MODEM_STATE_CALLER_NUMBER: case MODEM_STATE_RING_EXPECT: case MODEM_STATE_RING: + case MODEM_STATE_PRE_HANGUP_HUPSEQ: + case MODEM_STATE_PRE_HANGUP_ATHSEQ: + case MODEM_STATE_HANGUP: daemon_log(LOG_ERR, "Timeout reached for device <%s>", m->dev); return OOP_HALT; - case MODEM_STATE_ANSWER: + case MODEM_STATE_ATA: case MODEM_STATE_VTXVRX: - daemon_log(LOG_ERR, "Connection failed for device <%s>", m->dev); + daemon_log(LOG_ERR, "Connection not established for device <%s>", m->dev); m->state = MODEM_STATE_IDLE; modem_timeout(m, RESET_IDLE_TIME); return OOP_CONTINUE; @@ -252,13 +451,36 @@ static void* oop_timeout_cb(oop_source *source, struct timeval tv, void *user) { modem_init_cycle(m); return OOP_CONTINUE; + + case MODEM_STATE_CHILD_DRAIN: + daemon_log(LOG_INFO, "Child output buffer did not drain, flushing."); + assert(m->child_buffio); + buffio_flush_output(m->child_buffio); + + modem_hangup_seq(m); + return OOP_CONTINUE; + case MODEM_STATE_CHILD_WAIT_EOF: + + daemon_log(LOG_INFO, "Child did not EOF, closing child pipes."); + assert(m->child_buffio); + buffio_free(m->child_buffio); + m->child_buffio = NULL; + + modem_hangup_seq(m); + + return OOP_CONTINUE; + + case MODEM_STATE_CHILD_KILL: + daemon_log(LOG_INFO, "Child did not react on killing, exiting."); + return OOP_HALT; + default: assert(0); } } -static void modem_input_ready_cb(struct buffio *b, void *user) { +static int modem_input_ready_cb(struct buffio *b, void *user) { struct modem *m = user; assert(b && m && m->buffio == b); @@ -280,7 +502,8 @@ static void modem_input_ready_cb(struct buffio *b, void *user) { modem_input_ready_cb(b, user); } else modem_next_command(m); - } + } else + buffio_dump_lines(m->buffio); break; } @@ -291,7 +514,8 @@ static void modem_input_ready_cb(struct buffio *b, void *user) { m->state = MODEM_STATE_CALLER_NUMBER; modem_timeout(m, BASIC_TIMEOUT); modem_input_ready_cb(b, user); - } + } else + buffio_dump_lines(m->buffio); break; @@ -318,7 +542,9 @@ static void modem_input_ready_cb(struct buffio *b, void *user) { if (buffio_find_input(m->buffio, "RING/")) { m->state = MODEM_STATE_RING; modem_input_ready_cb(b, user); - } + } else + buffio_dump_lines(m->buffio); + break; case MODEM_STATE_RING: { @@ -342,7 +568,7 @@ static void modem_input_ready_cb(struct buffio *b, void *user) { m->tabentry = t; daemon_log(LOG_INFO, "Accepting call."); - m->state = MODEM_STATE_ANSWER; + m->state = MODEM_STATE_ATA; modem_timeout(m, ACCEPT_TIMEOUT); buffio_command(m->buffio, "ATA\n"); @@ -358,28 +584,27 @@ static void modem_input_ready_cb(struct buffio *b, void *user) { break; } - case MODEM_STATE_ANSWER: + case MODEM_STATE_ATA: if (buffio_find_input(m->buffio, "VCON\r\n")) { assert(m->tabentry); if (m->tabentry->action == CALL_ACTION_ACCEPT) { daemon_log(LOG_INFO, "Call accepted, changing to voice mode."); - buffio_command(m->buffio, "AT+VTX+VRX"); + buffio_command(m->buffio, "AT+VTX+VRX\n"); m->state = MODEM_STATE_VTXVRX; } else if (m->tabentry->action == CALL_ACTION_HANGUP) { modem_timeout(m, 0); - daemon_log(LOG_INFO, "Call accepted for hang up."); - m->state = MODEM_STATE_ATH; - modem_input_ready_cb(b, user); + modem_hangup(m, 0); } else assert(0); - } else if (buffio_find_input(m->buffio, "NO CARRIER\n\n")) { + + } else if (buffio_find_input(m->buffio, "NO CARRIER\r\n")) { daemon_log(LOG_INFO, "Failed to accept call, carrier lost"); - m->state = MODEM_STATE_IDLE; - modem_timeout(m, RESET_IDLE_TIME); - } + modem_sched_reset(m); + } else + buffio_dump_lines(m->buffio); break; @@ -390,58 +615,190 @@ static void modem_input_ready_cb(struct buffio *b, void *user) { m->state = MODEM_STATE_CONNECTION; daemon_log(LOG_INFO, "Voice connection established."); - modem_input_ready_cb(b, user); - } else if (buffio_find_input(m->buffio, "NO CARRIER\n\n")) { + + if (modem_start_child(m) < 0) { + daemon_log(LOG_ERR, "Failed to start child process"); + modem_hangup(m, 1); + } else { + m->dle_flag = 0; + buffio_set_input_watermark(m->buffio, INPUT_WATERMARK_CONNECTION); + buffio_set_delay_usec(m->buffio, ISDN_DELAY_USEC, ISDN_LATENCY_USEC); + modem_input_ready_cb(b, user); + } + + } else if (buffio_find_input(m->buffio, "NO CARRIER\r\n")) { daemon_log(LOG_INFO, "Failed to accept call, carrier lost"); - m->state = MODEM_STATE_IDLE; - modem_timeout(m, RESET_IDLE_TIME); + modem_sched_reset(m); } break; case MODEM_STATE_CONNECTION: - m->state = MODEM_STATE_HANGUP; - modem_input_ready_cb(b, user); + copy_modem_to_child(m); break; - - case MODEM_STATE_HANGUP: { - static const char hup[] = { DLE, DC4, DLE, ETX, 0 }; - buffio_command(m->buffio, hup); - - /* pass over ... */ - } - - case MODEM_STATE_ATH: { - daemon_log(LOG_INFO, "Hanging up."); - buffio_command(m->buffio, "ATH\n"); - m->state = MODEM_STATE_FORCE_HANGUP; - break; - } default: break; } -} - -static void modem_output_empty_cb(struct buffio *b, void *user) { + return 0; +} + +static int modem_output_empty_cb(struct buffio *b, void *user) { struct modem *m = user; assert(b && m && m->buffio == b); switch (m->state) { - case MODEM_STATE_FORCE_HANGUP: + case MODEM_STATE_CONNECTION: + copy_child_to_modem(m); + break; + + case MODEM_STATE_PRE_HANGUP_HUPSEQ: + if (b) + buffio_command(m->buffio, hup_sequence); + /* pass over */ + + case MODEM_STATE_PRE_HANGUP_ATHSEQ: + buffio_command(m->buffio, ath_sequence); + m->state = MODEM_STATE_HANGUP; + + daemon_log(LOG_INFO, "Delayed HANGUP sequence"); + + /* Don't restart timeout here */ + break; + + case MODEM_STATE_HANGUP: modem_force_hangup(m); - daemon_log(LOG_INFO, "Hang up successful, reinitializing in %i seconds.", RESET_IDLE_TIME); - m->state = MODEM_STATE_IDLE; - modem_timeout(m, RESET_IDLE_TIME); + modem_hangup_seq(m); break; default: break; } + + return 0; } +static int modem_dle_cb(uint8_t c, void *user) { + struct modem *m = user; + assert(m); + + switch (c) { + case ETX : + case DC4 : + daemon_log(LOG_INFO, "Recieved hangup sequence from peer."); + + assert(m->state == MODEM_STATE_CONNECTION); + + buffio_flush_output(m->buffio); + modem_hangup(m, 1); + return -1; + + default: + daemon_log(LOG_INFO, "Recieved DTMF character '%c'", c); + } + return 0; +} + +static void copy_modem_to_child(struct modem *m) { + const uint8_t *sp; + uint8_t *dp; + size_t sl, dl; + + assert(m->buffio && m->child_buffio); + sp = buffio_read_ptr(m->buffio, &sl); + dp = buffio_write_ptr(m->child_buffio, &dl); + + if (sp && sl && dp && dl) { + sl = dle_decode(sp, sl, dp, &dl, modem_dle_cb, m, &m->dle_flag); + + /* It may be the case that dle_decode recieved a + hangup sequence! */ + + if (m->state == MODEM_STATE_CONNECTION) + buffio_read_ptr_inc(m->buffio, sl); + buffio_write_ptr_inc(m->child_buffio, dl); + } +} + +static void copy_child_to_modem(struct modem *m) { + size_t sl, dl; + const uint8_t *sp; + uint8_t *dp; + + assert(m->child_buffio && m->buffio); + sp = buffio_read_ptr(m->child_buffio, &sl); + dp = buffio_write_ptr(m->buffio, &dl); + + if (sp && dp && sl && dl) { + sl = dle_encode(sp, sl, dp, &dl); + buffio_read_ptr_inc(m->child_buffio, sl); + buffio_write_ptr_inc(m->buffio, dl); + } +} + +static int modem_reopen(struct modem *m) { + int fd; + struct termios ts; + assert(m && m->dev); + + if (m->buffio) { + daemon_log(LOG_INFO, "Closing TTY device '%s'."); + buffio_close_output_fd(m->buffio); + buffio_close_input_fd(m->buffio); + } + daemon_log(LOG_INFO, "Trying to open TTY device '%s' ...", m->dev); + if ((fd = open(m->dev, O_RDWR|O_NDELAY)) < 0) { + daemon_log(LOG_ERR, "Failed to open device '%s': %s", m->dev, strerror(errno)); + goto fail; + } + + daemon_log(LOG_INFO, "TTY device '%s' successfully opened.", m->dev); + + if (tcgetattr(fd, &ts) < 0) { + daemon_log(LOG_ERR, "Failed to get TTY attributes: %s", strerror(errno)); + goto fail; + } + + cfmakeraw(&ts); + + ts.c_cflag |= HUPCL | CS8 | CLOCAL; + ts.c_iflag |= IGNPAR | IGNBRK; + + ts.c_cc[VMIN] = 1; + ts.c_cc[VTIME] = 0; + + cfsetospeed(&ts, BAUD_RATE); + cfsetispeed(&ts, BAUD_RATE); + + tcflush(fd, TCIOFLUSH); + + if (tcsetattr(fd, TCSANOW, &ts) < 0) { + daemon_log(LOG_ERR, "Failed to set TTY attributes: %s", strerror(errno)); + goto fail; + } + + if (!m->buffio) { + m->buffio = buffio_new(fd, fd); + assert(m->buffio); + + m->buffio->output_empty_cb = modem_output_empty_cb; + m->buffio->input_ready_cb = modem_input_ready_cb; + m->buffio->error_cb = error_cb; + + m->buffio->user = m; + } else + buffio_set_fds(m->buffio, fd, fd); + + return 0; + +fail: + if (fd >= 0) + close(fd); + + return -1; +} |