summaryrefslogtreecommitdiffstats
path: root/src/modem.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/modem.c')
-rw-r--r--src/modem.c575
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;
+}