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