WARNING! dependency on: lace_cfgreader (branchpoint), lace_utils
[gnokii.git] / common / devices / unixserial.c
1 /*
2   
3   $Id$
4
5   G N O K I I
6
7   A Linux/Unix toolset and driver for Nokia mobile phones.
8
9   Copyright (C) 1999, 2000 Hugh Blemings & Pavel Janík ml.
10
11   Released under the terms of the GNU GPL, see file COPYING for more details.
12
13   $Log$
14   Revision 1.1.1.1.12.1  2001/11/25 23:31:56  short
15   WARNING! dependency on: lace_cfgreader (branchpoint), lace_utils
16
17   * new gnokiirc/global settings:
18     * connect_script/disconnect_script - needed for BIP but can be general
19       * entries in such sections passed as %ENV - ...
20       * provided chat script ("connect-et" now) w/o bugs of "ppp-6210-modem"
21     * serial_baudrate               - used when not overriden by phone
22     * handshake = software/hardware - used when not overriden by phone
23     * require_dcd - kills Gnokii when modem drops connection - needed for BIP
24     * serial_write_usleep - waits between each character sent - for Siemens M20
25   * All open device fds are now closed and DTR/RTS-cleared on exit
26     * SIGINT abort not properly handled - clashing with pthreads
27   * Fixed hang-ons by fcntl(FASYNC) forgotting to specify also FNONBLOCK
28
29   Revision 1.1.1.1  2001/11/25 21:59:09  short
30   :pserver:cvs@pserver.samba.org:/cvsroot - gnokii - Sun Nov 25 22:56 CET 2001
31
32   Revision 1.10  2001/11/14 10:46:12  pkot
33   Small cleanup with __unices__
34
35   Revision 1.9  2001/09/14 12:15:28  pkot
36   Cleanups from 0.3.3 (part1)
37
38   Revision 1.8  2001/08/20 23:27:37  pkot
39   Add hardware shakehand to the link layer (Manfred Jonsson)
40
41   Revision 1.7  2001/07/03 00:03:36  pkot
42   Small fixes to let gnokii compile and work under solaris (thanks to Artur Kubiak)
43
44   Revision 1.6  2001/03/21 23:36:04  chris
45   Added the statemachine
46   This will break gnokii --identify and --monitor except for 6210/7110
47
48   Revision 1.5  2001/03/19 23:43:46  pkot
49   Solaris/ *BSD '#if defined' cleanup
50
51   Revision 1.4  2001/03/13 01:21:38  pkot
52   *BSD updates (Bert Driehuis)
53
54   Revision 1.3  2001/03/06 22:27:46  pkot
55   Misc docs and Makefiles updates and cleanups
56
57   Revision 1.2  2001/02/21 19:57:05  chris
58   More fiddling with the directory layout
59
60 */
61
62 /* [global] option "serial_write_usleep" default: */
63 #define SERIAL_WRITE_USLEEP_DEFAULT (-1)
64
65 #include "misc.h"
66 #include "cfgreader.h"
67
68 /* Do not compile this file under Win32 systems. */
69
70 #ifndef WIN32
71
72 #include <stdio.h>
73 #include <fcntl.h>
74 #include <sys/ioctl.h>
75 #include <string.h>
76 #include <limits.h>
77 #include <stdlib.h>
78 #include <errno.h>
79 #include <sys/types.h>
80 #include <sys/wait.h>
81 #include <sys/ioctl.h>
82
83 #include <termios.h>
84 #include "devices/unixserial.h"
85
86 #ifdef HAVE_SYS_IOCTL_COMPAT_H
87   #include <sys/ioctl_compat.h>
88 #endif
89
90 #ifdef HAVE_SYS_SELECT_H
91 #include <sys/select.h>
92 #endif
93
94 /* If the target operating system does not have cfsetspeed, we can emulate
95    it. */
96
97 #ifndef HAVE_CFSETSPEED
98   #if defined(HAVE_CFSETISPEED) && defined(HAVE_CFSETOSPEED)
99      #define cfsetspeed(t, speed) \
100      (cfsetispeed(t, speed) || cfsetospeed(t, speed))
101   #else
102     static int cfsetspeed(struct termios *t, int speed) {
103     #ifdef HAVE_TERMIOS_CSPEED
104       t->c_ispeed = speed;
105       t->c_ospeed = speed;
106     #else
107       t->c_cflag |= speed;
108     #endif
109       return 0;
110     }
111   #endif
112 #endif
113
114 #ifndef O_NONBLOCK
115   #define O_NONBLOCK  0
116 #endif
117
118 /* Structure to backup the setting of the terminal. */
119
120 struct termios serial_termios;
121
122 /* Script handling: */
123
124 static void device_script_cfgfunc(const char *section,const char *key,const char *value)
125 {
126   setenv(key,value,1/*overwrite*/);     /* errors ignored */
127 }
128
129 static int device_script(int fd, const char *section)
130 {
131 pid_t pid;
132 const char *scriptname = CFG_Get(CFG_Info, "global", section);
133
134   if (!scriptname)
135     return(0);
136
137   errno=0;
138   switch ((pid=fork())) {
139     case -1:
140       fprintf(stderr,_("device_script(\"%s\"): fork() failure: %s!\n"),scriptname,strerror(errno));
141       return(-1);
142
143     default: {  /* parent */
144 int status;
145       if (pid==waitpid(pid,&status,0/*options*/) && WIFEXITED(status) && !WEXITSTATUS(status))
146         return(0);
147       fprintf(stderr,_("device_script(\"%s\"): child script failure: %s, exit code=%d\n"),scriptname,
148           (WIFEXITED(status) ? _("normal exit") : _("abnormal exit")),
149           (WIFEXITED(status) ? WEXITSTATUS(status) : -1));
150       errno=EIO;
151       return(-1);
152       }
153
154     case 0: {   /* child */
155       CFG_GetForeach(CFG_Info,section,device_script_cfgfunc);
156       errno=0;
157       if (0!=dup2(fd,0) || 1!=dup2(fd,1) || close(fd)) {
158         fprintf(stderr,_("device_script(\"%s\"): file descriptor prepare: %s\n"),scriptname,strerror(errno));
159         _exit(-1);
160         }
161       /* FIXME: close all open descriptors - how to track them?
162        */
163       execl("/bin/sh","sh","-c",scriptname,NULL);
164       fprintf(stderr,_("device_script(\"%s\"): execute script: %s\n"),scriptname,strerror(errno));
165       _exit(-1);
166       /* NOTREACHED */
167       }
168     }
169   /* NOTREACHED */
170 }
171
172 int serial_close_all_openfds[0x10];     /* -1 when entry not used, fd otherwise */
173 int serial_close(int __fd);
174
175 static void serial_close_all(void)
176 {
177   int i;
178
179   dprintf("serial_close_all() executed\n");
180   for (i=0;i<ARRAY_LEN(serial_close_all_openfds);i++)
181     if (serial_close_all_openfds[i]!=-1)
182       serial_close(serial_close_all_openfds[i]);
183 }
184
185 static void unixserial_interrupted(int signo)
186 {
187         exit(EXIT_FAILURE);
188         /* NOTREACHED */
189 }
190
191 /* Open the serial port and store the settings. */
192
193 int serial_open(__const char *__file, int __oflag) {
194
195   int __fd;
196   int retcode,i;
197   static bool atexit_registered=false;
198
199   if (!atexit_registered) {
200     memset(serial_close_all_openfds,-1,sizeof(serial_close_all_openfds));
201 #if 0   /* Disabled for now as atexit() functions are then called multiple times for pthreads! */
202     signal(SIGINT,unixserial_interrupted);
203 #endif
204     atexit(serial_close_all);
205     atexit_registered=true;
206     }
207
208   __fd = open(__file, __oflag);
209   if (__fd == -1) {
210     perror("Gnokii serial_open: open");
211     return (-1);
212   }
213
214   for (i=0;i<ARRAY_LEN(serial_close_all_openfds);i++)
215     if (serial_close_all_openfds[i]==-1 || serial_close_all_openfds[i]==__fd) {
216       serial_close_all_openfds[i]=__fd;
217       break;
218       }
219
220   retcode=tcgetattr(__fd, &serial_termios);
221   if(retcode==-1) {
222     perror("Gnokii serial_open:tcgetattr");
223     /* Don't call serial_close since serial_termios is not valid */
224     close(__fd);
225     return(-1);
226   }
227   
228   return __fd;
229 }
230
231 /* Close the serial port and restore old settings. */
232
233 int serial_close(int __fd) {
234   int i;
235
236   for (i=0;i<ARRAY_LEN(serial_close_all_openfds);i++)
237     if (serial_close_all_openfds[i]==__fd)
238       serial_close_all_openfds[i]=-1;           /* fd closed */
239
240   /* handle config file disconnect_script:
241    */
242   if (-1 == device_script(__fd,"disconnect_script"))
243     fprintf(stderr,"Gnokii serial_close: disconnect_script\n");
244
245   if (__fd >= 0) {
246 #if 1 /* HACK */
247     serial_termios.c_cflag |= HUPCL;    /* production == 1 */
248 #else
249     serial_termios.c_cflag &= ~HUPCL;   /* debugging  == 0 */
250 #endif
251
252     tcsetattr(__fd, TCSANOW, &serial_termios);
253     }
254
255   return (close(__fd));
256 }
257
258 /* Open a device with standard options.
259  * Use value (-1) for "__with_hw_handshake" if its specification is required from the user
260  */
261 int serial_opendevice(__const char *__file, int __with_odd_parity, int __with_async, int __with_hw_handshake) {
262
263   int fd;
264   int retcode;
265   struct termios tp;
266
267   /* handle config file handshake override: */
268   {
269 char *s=CFG_Get(CFG_Info, "global", "handshake");
270
271          if (s && (!strcasecmp(s,"software") || !strcasecmp(s,"rtscts")))
272       __with_hw_handshake=false;
273     else if (s && (!strcasecmp(s,"hardware") || !strcasecmp(s,"xonxoff")))
274       __with_hw_handshake=true;
275     else if (s)
276       fprintf(stderr,_("Unrecognized [%s] option \"%s\", use \"%s\" or \"%s\" value, ignoring!"),
277           "global","handshake","software","hardware");
278
279     if (__with_hw_handshake==-1) {
280       fprintf(stderr,_("[%s] option \"%s\" not found, trying to use \"%s\" value!"),
281           "global","handshake","software");
282       __with_hw_handshake=false;
283       }
284   }
285
286   /* Open device */
287
288   /* O_NONBLOCK MUST be used here as the CLOCAL may be currently off
289    * and if DCD is down the "open" syscall would be stuck wating for DCD.
290    */
291   fd = serial_open(__file, O_RDWR | O_NOCTTY | O_NONBLOCK);
292
293   if (fd < 0) 
294     return fd;
295
296   /* Initialise the port settings */
297
298   memcpy(&tp, &serial_termios, sizeof(struct termios));
299
300   /* Set port settings for canonical input processing */
301
302   tp.c_cflag = B0 | CS8 | CLOCAL | CREAD | HUPCL;
303   if (__with_odd_parity) {
304     tp.c_cflag |= (PARENB | PARODD);
305     tp.c_iflag = 0;
306   }
307   else
308     tp.c_iflag = IGNPAR;
309   if (__with_hw_handshake)
310     tp.c_cflag |= CRTSCTS;
311   else
312     tp.c_cflag &= ~CRTSCTS;
313
314   tp.c_oflag = 0;
315   tp.c_lflag = 0;
316   tp.c_cc[VMIN] = 1;
317   tp.c_cc[VTIME] = 0;
318
319   retcode=tcflush(fd, TCIFLUSH);
320   if (retcode == -1) {
321     perror("Gnokii serial_opendevice: tcflush");
322     serial_close(fd);
323     return(-1);
324   }
325
326   retcode=tcsetattr(fd, TCSANOW, &tp);
327   if (retcode == -1){
328     perror("Gnokii serial_opendevice: tcsetattr");
329     serial_close(fd);
330     return(-1);
331   }
332
333   /* Set speed */
334   { 
335 char *baudratestring=CFG_Get(CFG_Info, "global", "serial_baudrate");
336 int baudrate=0;
337
338   if (baudratestring)
339     baudrate=atoi(baudratestring);
340   if (baudrate && GE_NONE!=serial_changespeed(fd,baudrate))
341     baudrate=0;
342   if (!baudrate)
343     serial_changespeed(fd,19200/*default value*/);
344   }
345
346   /* We need to turn off O_NONBLOCK now (we have CLOCAL set so it is safe).
347    * When we run some device script it really doesn't expect NONBLOCK!
348    */
349
350   retcode=fcntl(fd, F_SETFL, 0);
351   if (retcode == -1){
352     perror("Gnokii serial_opendevice: fnctl(F_SETFL)");
353     serial_close(fd);
354     return(-1);
355   }
356
357   /* handle config file connect_script:
358    */
359   if (-1 == device_script(fd,"connect_script")) {
360     fprintf(stderr,"Gnokii serial_opendevice: connect_script\n");
361     serial_close(fd);
362     return(-1);
363   }
364
365   /* Allow process/thread to receive SIGIO */
366
367 #if !(__unices__)
368   retcode = fcntl(fd, F_SETOWN, getpid());
369   if (retcode == -1){
370     perror("Gnokii serial_opendevice: fnctl(F_SETOWN)");
371     serial_close(fd);
372     return(-1);
373   }
374 #endif
375
376   /* Make filedescriptor asynchronous. */
377
378   /* We need to supply FNONBLOCK (or O_NONBLOCK) again as it would get reset
379    * by F_SETFL as a side-effect!
380    */
381   retcode=fcntl(fd, F_SETFL, (__with_async ? FASYNC : 0) | FNONBLOCK);
382   if (retcode == -1){
383     perror("Gnokii serial_opendevice: fnctl(F_SETFL)");
384     serial_close(fd);
385     return(-1);
386   }
387   
388   return fd;
389 }
390
391 /* Set the DTR and RTS bit of the serial device. */
392
393 void serial_setdtrrts(int __fd, int __dtr, int __rts) {
394
395   unsigned int flags;
396
397   flags = TIOCM_DTR;
398
399   if (__dtr)
400     ioctl(__fd, TIOCMBIS, &flags);
401   else
402     ioctl(__fd, TIOCMBIC, &flags);
403
404   flags = TIOCM_RTS;
405
406   if (__rts)
407     ioctl(__fd, TIOCMBIS, &flags);
408   else
409     ioctl(__fd, TIOCMBIC, &flags);
410 }
411
412
413 int serial_select(int fd, struct timeval *timeout) {
414
415   fd_set readfds;
416
417   FD_ZERO(&readfds);
418   FD_SET(fd, &readfds);
419
420   return (select(fd + 1, &readfds, NULL, NULL, timeout));
421
422 }
423
424
425 /* Change the speed of the serial device.
426  * RETURNS: Success
427  */
428
429 GSM_Error serial_changespeed(int __fd, int __speed) {
430   GSM_Error retcode = true;
431
432 #ifndef SGTTY
433   struct termios t;
434 #else
435   struct sgttyb t;
436 #endif
437
438   int speed=B9600;
439
440   switch (__speed) {
441     case 9600:   speed = B9600;   break;
442     case 19200:  speed = B19200;  break;
443     case 38400:  speed = B38400;  break;
444     case 57600:  speed = B57600;  break;
445     case 115200: speed = B115200; break;
446     default:
447       fprintf(stderr,_("Serial port speed %d not supported!\n"),__speed);
448       return(GE_NOTSUPPORTED);
449   }
450
451 #ifndef SGTTY
452   if (tcgetattr(__fd, &t))
453     retcode = GE_INTERNALERROR;
454
455   // This is not needed! We set up the speed via cfsetspeed
456   //  t.c_cflag &= ~CBAUD;
457   //  t.c_cflag |= speed;
458   if (cfsetspeed(&t, speed) == -1) {
459     dprintf(_("Serial port speed setting failed\n"));
460     retcode = GE_INTERNALERROR;
461     }
462
463   tcsetattr(__fd, TCSADRAIN, &t);
464 #else
465   if (ioctl(__fd, TIOCGETP, &t))
466     retcode = GE_INTERNALERROR;
467
468   t.sg_ispeed = speed;
469   t.sg_ospeed = speed;
470
471   if (ioctl(__fd, TIOCSETN, &t))
472     retcode = GE_INTERNALERROR;
473 #endif
474
475   return(retcode);
476 }
477
478 /* Read from serial device. */
479
480 size_t serial_read(int __fd, __ptr_t __buf, size_t __nbytes) {
481
482   return (read(__fd, __buf, __nbytes));
483 }
484
485 #if !defined(TIOCMGET) && defined(TIOCMODG)
486 #define TIOCMGET TIOCMODG
487 #endif
488
489 static void check_dcd(int __fd)
490 {
491 #ifdef TIOCMGET
492 int mcs;
493
494         if (ioctl(__fd, TIOCMGET, &mcs) || !(mcs & TIOCM_CAR)) {
495                 fprintf(stderr,_("ERROR: Modem DCD is down and global/require_dcd parameter is set!\n"));
496                 exit(EXIT_FAILURE);             /* Hard quit of all threads */
497                 }
498 #else
499         /* Impossible!! (eg. Coherent) */
500 #endif
501 }
502
503 /* Write to serial device. */
504
505 size_t serial_write(int __fd, __const __ptr_t __buf, size_t __n) {
506
507 size_t r=0;
508 ssize_t got;
509 static long serial_write_usleep=LONG_MIN;
510 static int require_dcd=-1;
511
512         if (serial_write_usleep==LONG_MIN) {
513 char *s=CFG_Get(CFG_Info, "global", "serial_write_usleep");
514
515                 serial_write_usleep=(!s ?
516                         SERIAL_WRITE_USLEEP_DEFAULT : atol(CFG_Get(CFG_Info, "global", "serial_write_usleep")));
517                 }
518
519         if (require_dcd==-1) {
520                 require_dcd=(!!CFG_Get(CFG_Info, "global", "require_dcd"));
521 #ifndef TIOCMGET
522                 if (require_dcd)
523                         fprintf(stderr,_("WARNING: global/require_dcd argument was set but it is not supported on this system!\n"));
524 #endif
525                 }
526
527         if (require_dcd)
528                 check_dcd(__fd);
529
530         if (serial_write_usleep<0)
531                 return(write(__fd, __buf, __n));
532
533         while (__n>0) {
534                 got=write(__fd, __buf, 1);
535                 if (got<=0)
536                         return((!r ? -1 : r));
537                 __buf++;
538                 __n--;
539                 r++;
540                 if (serial_write_usleep)
541                         usleep(serial_write_usleep);
542                 }
543         return(r);
544 }
545
546 #endif  /* WIN32 */