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