6b6d61aac108b38a3f23308c136f5426682ba040
[gnokii.git] / common / data / virtmodem.c
1 /*
2   $Id$
3
4   G N O K I I
5
6   A Linux/Unix toolset and driver for Nokia mobile phones.
7
8   Released under the terms of the GNU GPL, see file COPYING for more details.
9
10   This file provides a virtual modem interface to the GSM phone by calling
11   code in gsm-api.c, at-emulator.c and datapump.c. The code here provides
12   the overall framework and coordinates switching between command mode
13   (AT-emulator) and "online" mode where the data pump code translates data
14   from/to the GSM handset and the modem data/fax stream.
15
16 */
17
18 #define         __virtmodem_c
19
20 #include <config.h>
21
22 /* This is the right way to include stdlib with __USE_XOPEN defined */
23 #ifdef USE_UNIX98PTYS
24 # define _XOPEN_SOURCE 500
25 # include <features.h>
26 #endif
27
28 #include <stdio.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <signal.h>
32 #include <termios.h>
33 #include <grp.h>
34 #include <string.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <sys/time.h>
38 #ifndef UCLINUX
39 #include <sys/poll.h>
40 #include <pthread.h>
41 #endif /* UCLINUX */
42 #include <unistd.h>
43
44 #include "misc.h"
45 #include "gsm-api.h"
46 #include "gsm-common.h"
47 #include "data/at-emulator.h"
48 #include "data/datapump.h"
49 #include "data/virtmodem.h"
50 #include "data/rlp-common.h"
51
52         /* Global variables */
53
54
55 #define GNOKII_DEV "/var/gnokii-dev"
56
57 //extern bool TerminateThread;
58
59         /* Local variables */
60
61 int             PtyRDFD;        /* File descriptor for reading and writing to/from */
62 int             PtyWRFD;        /* pty interface - only different in debug mode. */ 
63
64 static bool     UseSTDIO;       /* Use STDIO for debugging purposes instead of pty */
65 bool    CommandMode;
66
67 static bool     VM_ThreadLoop(void);
68 static GSM_Error        VM_GSMInitialise(char *model, char *port, char *initlength, GSM_ConnectionType connection, char *synchronizetime);
69 static int              VM_PtySetup(char *bindir);
70
71         /* If initialised in debug mode, stdin/out is used instead
72            of ptys for interface. */
73 bool VM_Initialise(char *model,char *port, char *initlength, GSM_ConnectionType connection, char *bindir, bool debug_mode, bool GSMInit,char *synchronizetime)
74 {
75         CommandMode = true;
76
77         if (debug_mode == true) {
78                 UseSTDIO = true;
79         }
80         else {
81                 UseSTDIO = false;
82         }
83
84         if (GSMInit) {
85 #ifdef DEBUG
86           fprintf (stderr , "Initialising GSM\n");
87 #endif /* DEBUG */
88           if ((VM_GSMInitialise(model, port, initlength, connection, synchronizetime) != GE_NONE)) {
89                 fprintf (stderr, _("VM_Initialise - VM_GSMInitialise failed!\n"));
90                 return (false);
91                 
92           }
93         }
94         GSMInit=false;
95
96         if (VM_PtySetup(bindir) < 0) {
97                 fprintf (stderr, _("VM_Initialise - VM_PtySetup failed!\n"));
98                 return (false);
99         }
100     
101         if (ATEM_Initialise(PtyRDFD, PtyWRFD, model, port) != true) {
102                 fprintf (stderr, _("VM_Initialise - ATEM_Initialise failed!\n"));
103                 return (false);
104         }
105
106         if (DP_Initialise(PtyRDFD, PtyWRFD) != true) {
107                 fprintf (stderr, _("VM_Initialise - DP_Initialise failed!\n"));
108                 return (false);
109         }
110
111         /* Create and start thread, */
112         return VM_ThreadLoop();
113 }
114
115 static void    VM_CharHandler(void);
116
117 static bool VM_ThreadLoop(void)
118 {
119         int res;
120         fd_set readfds;
121         struct timeval timeout;
122
123                 /* Note we can't use signals here as they are already used
124                    in the FBUS code.  This may warrant changing the FBUS
125                    code around sometime to use select instead to free up
126                    the SIGIO handler for mainline code. */
127
128         for (;;) {
129           if (!CommandMode) {
130             sleep(1);
131           } else {  /* If we are in data mode, leave it to datapump to get the data */
132
133                 FD_ZERO(&readfds);
134                 FD_SET(PtyRDFD,&readfds);
135                 timeout.tv_sec=0;
136                 timeout.tv_usec=500*1000;
137
138                 res = select(PtyRDFD+1,&readfds,NULL/*writefds*/,NULL/*exceptfds*/,&timeout);
139
140                 switch (res) {
141                         case 0: /* Timeout */
142                                 break;
143
144                         case -1:
145                                 if (errno==EINTR)
146                                         continue;
147                                 perror("VM_ThreadLoop - select");
148                                 return (false);
149
150                         default:
151                           if (FD_ISSET(PtyRDFD,&readfds)) {
152                             VM_CharHandler();
153                           } else usleep(500); /* Probably the file has been closed */
154                           break;
155                 }
156           }
157         }
158         
159 }
160
161         /* Application should call VM_Terminate to shut down
162            the virtual modem thread */
163 void            VM_Terminate(void)
164 {
165         if (!UseSTDIO) {
166                 close (PtyRDFD);
167                 close (PtyWRFD);
168         }
169 }
170
171 static int      VM_GetMasterPty(char **name);
172
173         /* Open pseudo tty interface and (in due course create a symlink
174            to be /dev/gnokii etc. ) */
175
176 static int              VM_PtySetup(char *bindir)
177 {
178         int                     err;
179 #ifndef UCLINUX
180         char            mgnokiidev[200];
181 #endif /* UCLINUX */
182         char            *slave_name;
183 #ifndef UCLINUX
184         char            cmdline[200];
185 #endif /* UCLINUX */
186
187 #ifndef UCLINUX
188         if (bindir) {
189                 strncpy(mgnokiidev, bindir, 200);
190                 strcat(mgnokiidev, "/");
191         }
192         strncat(mgnokiidev, "mgnokiidev", 200 - strlen(bindir));
193 #endif /* UCLINUX */
194
195         if (UseSTDIO) {
196                 PtyRDFD = STDIN_FILENO;
197                 PtyWRFD = STDOUT_FILENO;
198                 return (0);
199         }
200         
201         PtyRDFD = VM_GetMasterPty(&slave_name);
202         if (PtyRDFD < 0) {
203                 fprintf (stderr, _("Couldn't open pty!\n"));
204                 return(-1);
205         }
206         PtyWRFD = PtyRDFD;
207
208 #ifndef UCLINUX
209                 /* Check we haven't been installed setuid root for some reason
210                    if so, don't create /dev/gnokii */
211         if (getuid() != geteuid()) {
212                 fprintf(stderr, _("gnokiid should not be installed setuid root!\n"));
213                 return (0);
214         }
215 #endif
216
217 #ifdef DEBUG
218         fprintf (stderr, _("Slave pty is %s, calling %s to create \"%s\".\n"), slave_name,
219 #ifndef UCLINUX
220                         mgnokiidev
221 #else /* UCLINUX */
222                         "<uClinux>"
223 #endif /* UCLINUX */
224                         ,GNOKII_DEV);
225 #endif /* DEBUG */
226
227 #ifndef UCLINUX
228                 /* Create command line, something line ./mkgnokiidev ttyp0 */
229         sprintf(cmdline, "%s %s", mgnokiidev, slave_name);
230
231                 /* And use system to call it. */        
232         err = system (cmdline);
233 #else /* UCLINUX */
234
235         /* Remove symlink in case it already exists. Don't care if it fails.  */
236         unlink (GNOKII_DEV);
237
238         /* Create symlink */
239         err = symlink(slave_name, GNOKII_DEV);
240
241 #endif /* UCLINUX */
242         
243         return (err);
244
245 }
246
247     /* Handler called when characters received from serial port.
248        calls state machine code to process it. */
249 static void    VM_CharHandler(void)
250 {
251     unsigned char   buffer[255];
252     int             res;
253
254
255     /* If we are in command mode, get the character, otherwise leave it */
256
257     if (CommandMode && ATEM_Initialised) {
258       
259       res = read(PtyRDFD, buffer, 255);
260       
261       /* A returned value of -1 means something serious has gone wrong - so quit!! */
262       /* Note that file closure etc. should have been dealt with in ThreadLoop */
263       
264       if (res < 0) {    
265 //          TerminateThread=true;
266             return;
267       }
268         
269       ATEM_HandleIncomingData(buffer, res);
270     }   
271 }     
272
273         /* Initialise GSM interface, returning GSM_Error as appropriate  */
274 static GSM_Error        VM_GSMInitialise(char *model, char *port, char *initlength, GSM_ConnectionType connection, char *synchronizetime)
275 {
276         int             count=0;
277         GSM_Error       error;
278
279                 /* Initialise the code for the GSM interface. */     
280
281         error = GSM_Initialise(model,port, initlength, connection, RLP_DisplayF96Frame, synchronizetime);
282
283         if (error != GE_NONE) {
284                 fprintf(stderr, _("GSM/FBUS init failed! (Unknown model ?). Quitting.\n"));
285         return (error);
286         }
287
288                 /* First (and important!) wait for GSM link to be active. We allow 10
289                    seconds... */
290
291         while (count++ < 200 && *GSM_LinkOK == false) {
292         usleep(50000);
293         }
294
295         if (*GSM_LinkOK == false) {
296                 fprintf (stderr, _("Hmmm... GSM_LinkOK never went true. Quitting. \n"));
297                 return (GE_NOLINK); 
298         }
299
300         return (GE_NONE);
301 }
302
303 /* VM_GetMasterPty()
304    Takes a double-indirect character pointer in which to put a slave
305    name, and returns an integer file descriptor.  If it returns < 0, an
306    error has occurred.  Otherwise, it has returned the master pty
307    file descriptor, and fills in *name with the name of the
308    corresponding slave pty.  Once the slave pty has been opened,
309    you are responsible to free *name.  Code is from Developling Linux
310    Applications by Troan and Johnson */
311
312
313 static int      VM_GetMasterPty(char **name) { 
314
315 #ifdef USE_UNIX98PTYS
316         int master, err;
317
318         master = open("/dev/ptmx", O_RDWR | O_NOCTTY | O_NONBLOCK);
319         if (master >= 0) {
320                 err = grantpt(master);
321                 err = err || unlockpt(master);
322                 if (!err) {
323                         *name = ptsname(master);
324                 } else {
325                         return(-1);
326                 }
327         }
328 #else /* USE_UNIX98PTYS */
329    int i = 0 , j = 0;
330    /* default to returning error */
331    int master = -1;
332
333    /* create a dummy name to fill in */
334    *name = strdup("/dev/ptyXX");
335
336    /* search for an unused pty */
337    for (i=0; i<16 && master <= 0; i++) {
338       for (j=0; j<16 && master <= 0; j++) {
339          (*name)[8] = "pqrstuvwxyzPQRST"[i];
340          (*name)[9] = "0123456789abcdef"[j];
341          /* open the master pty */
342          if ((master = open(*name, O_RDWR | O_NOCTTY | O_NONBLOCK )) < 0) {
343             if (errno == ENOENT) {
344                /* we are out of pty devices */
345                free (*name);
346                return (master);
347             }
348          }
349       }
350    }
351    if ((master < 0) && (i == 16) && (j == 16)) {
352       /* must have tried every pty unsuccessfully */
353       free (*name);
354       return (master);
355    }
356
357    /* By substituting a letter, we change the master pty
358     * name into the slave pty name.
359     */
360    (*name)[5] = 't';
361
362 #endif /* USE_UNIX98PTYS */
363
364    return (master);
365 }
366