Merged revisions 11610-11649 via svnmerge from
[wvapps.git] / wvdial / wvmodemscan.cc
blobbf599246c3d00241d9f174825677431948e25bff
1 /*
2 * Worldvisions Weaver Software:
3 * Copyright (C) 1997-2003 Net Integration Technologies, Inc.
5 * Intelligent serial port scanner: try to find a port (or ports)
6 * with a working modem, guess a basic startup init string, and find
7 * the maximum baud rate.
8 */
9 #include "wvmodemscan.h"
10 #include "wvmodem.h"
11 #include "strutils.h"
12 #include <time.h>
13 #include <assert.h>
14 #include <dirent.h>
15 #include <ctype.h>
16 #include <sys/stat.h>
18 WvString isdn_init;
19 bool default_asyncmap = false;
21 // startup at atz atq0 atv1 ate1 ats0 carrier dtr fastdial
22 // baudstep reinit done
23 static char *commands[WvModemScan::NUM_STAGES] = {
24 NULL, "Q0 V1 E1", "Z", "S0=0",
25 "&C1", "&D2", "+FCLASS=0", NULL,
26 NULL, "", NULL
29 static int baudcheck[6] = {
30 2400,
31 9600,
32 115200,
36 static int default_baud = baudcheck[0];
37 static int isdn_speed = 115200;
39 WvModemScan::WvModemScan(WvStringParm devname, bool is_modem_link)
40 : debug(devname, WvLog::Debug)
42 stage = Startup;
43 memset(status, 0, sizeof(status));
45 if (devname[0] == '/')
46 file = devname;
47 else
48 file = WvString("/dev/%s", devname);
50 use_modem_link = is_modem_link;
51 baud = default_baud;
52 modem = NULL;
53 tries = 0;
54 broken = false;
58 WvModemScan::~WvModemScan()
60 if (isok() && isdone())
61 debug(WvLog::Info, "Speed %s; init \"%s\"\n", maxbaud(), initstr());
63 WVRELEASE(modem);
67 bool WvModemScan::use_default_asyncmap() const
69 return default_asyncmap;
72 bool WvModemScan::isok() const
74 return !broken;
79 WvString WvModemScan::initstr() const
81 char s[200];
83 if (isdn_init)
84 return (isdn_init);
86 strcpy(s, "AT");
88 for (int i = 0; i < NUM_STAGES; i++)
90 if (status[i] != Worked && status[i] != Test)
91 continue;
92 if (!commands[i] || !commands[i][0])
93 continue;
94 if ((commands[i][0]=='Z' || commands[i][0]=='I') && status[i] != Test)
95 continue;
97 strcat(s, commands[i]);
98 strcat(s, " ");
101 return WvString(trim_string(s));
105 void WvModemScan::execute()
107 if (isdone() || !isok()) return;
109 switch ((Stage)stage)
111 case Startup:
112 assert(!modem);
113 modem = new WvModem(file, baud);
114 modem->die_fast = true;
115 if (!modem->isok())
117 if (modem->geterr()
118 && modem->geterr() != EIO
119 && modem->geterr() != ENOENT
120 && modem->geterr() != ENODEV)
122 debug(WvLog::Info, "%s\n", modem->errstr());
124 broken = true;
126 else
127 stage++;
128 break;
130 case AT:
131 case ATZ:
132 case ATS0:
133 case Carrier:
134 case DTR:
135 case FCLASS:
136 case Reinit:
137 assert(modem);
138 status[stage] = Test;
139 if (!strncmp(file, "/dev/ircomm", 11))
141 while (baudcheck[tries+1] <= 9600 && baudcheck[tries+1] != 0)
142 tries++;
144 if (baudcheck[tries] > 19200 || baudcheck[tries] == 0)
146 broken = true;
147 debug("failed at 9600 and 19200 baud.\n");
148 return;
150 baud = modem->speed(baudcheck[tries]);
152 if (!doresult(WvString("%s\r", initstr()), stage==ATZ ? 3000 : 500)
153 || ((stage <= AT || stage == Reinit) && status[stage]==Fail))
155 int old_baud = baud;
156 tries++;
157 //modem->drain();
158 //modem->speed(baud*2);
159 //baud = modem->speed(baud);
160 if (baudcheck[tries] == 0)
162 broken = true;
163 debug("and failed too at %s, giving up.\n",
164 WvString(isdn_speed));
165 // Go back to default_baud:
166 modem->speed(default_baud);
167 baud = modem->getspeed();
169 else if (strncmp(file, "/dev/ircomm", 11))
170 debug("failed with %s baud, next try: %s baud\n",
171 old_baud,
172 baud = modem->speed(baudcheck[tries]));
173 //baud = modem->speed(baud*2));
174 #if 0
175 if (tries >= 4)
177 if (baud == default_baud)
179 debug("nothing at %s baud,\n", WvString(default_baud));
180 // Ok, then let's try ISDN speed for ISDN TAs:
181 modem->speed(isdn_speed);
182 baud = modem->getspeed();
183 tries = 0;
185 else
187 // Ok, we tried default_baud and ISDN speed, give up:
188 broken = true;
189 debug("nor at %s.\n", WvString(isdn_speed));
190 // Go back to default_baud:
191 modem->speed(default_baud);
192 baud = modem->getspeed();
195 #endif
197 // else try again shortly
199 else
201 tries = 0;
202 stage++;
204 break;
206 case GetIdent:
207 assert(modem);
208 status[stage] = Test;
209 debug("Modem Identifier: ");
210 if (!doresult(WvString("ATI\r"), 500) || (status[stage]==Fail))
212 tries++;
214 if (tries >= 3)
215 debug("nothing.\n");
217 // else try again shortly
219 else
221 if (is_isdn())
222 debug("Looks like an ISDN modem.\n");
224 if (!strncmp(identifier, "Hagenuk", 7))
226 status[stage] = Test;
227 if (doresult(WvString("ATI1\r"), 500))
228 if (!strncmp(identifier, "Speed Dragon", 12)
229 || !strncmp(identifier, "Power Dragon", 12))
231 isdn_init = "ATB8";
232 modem_name = WvString("Hagenuk %s", identifier);
234 status[stage] = Worked;
236 else if (!strncmp(identifier, "346900", 6))
238 status[stage] = Test;
239 if (doresult(WvString("ATI3\r"), 500))
240 if (!strncmp(identifier, "3Com U.S. Robotics ISDN",23))
242 isdn_init = "AT*PPP=1";
243 modem_name = identifier;
245 status[stage] = Worked;
247 else if (!strncmp(identifier, "SP ISDN", 7))
249 status[stage] = Test;
250 if (doresult(WvString("ATI4\r"), 500))
251 if (!strncmp(identifier, "Sportster ISDN TA", 17))
253 isdn_init = "ATB3";
254 modem_name = identifier;
256 status[stage] = Worked;
258 else if (!strncmp(identifier, "\"Version", 8))
260 status[stage] = Test;
261 if (doresult(WvString("ATI6\r"), 500))
262 modem_name = identifier;
263 status[stage] = Worked;
265 else if (!strncmp(identifier, "644", 3))
267 status[stage] = Test;
268 if (doresult(WvString("ATI6\r"), 500))
269 if (!strncmp(identifier, "ELSA MicroLink ISDN", 19))
271 isdn_init = "AT$IBP=HDLCP";
272 modem_name = identifier;
273 default_asyncmap = true;
275 status[stage] = Worked;
277 else if (!strncmp(identifier, "643", 3))
279 status[stage] = Test;
280 if (doresult(WvString("ATI6\r"), 500))
281 if (!strncmp(identifier, "MicroLink ISDN/TLV.34", 21))
283 isdn_init = "AT\\N10%P1";
284 modem_name = identifier;
286 status[stage] = Worked;
288 else if (!strncmp(identifier, "ISDN TA", 6))
290 status[stage] = Test;
291 if (doresult(WvString("ATI5\r"), 500))
292 if (strstr(identifier, ";ASU"))
294 isdn_init = "ATB40";
295 modem_name = "ASUSCOM ISDNLink TA";
297 status[stage] = Worked;
299 else if (!strncmp(identifier, "128000", 6))
301 status[stage] = Test;
302 if (doresult(WvString("ATI3\r"), 500))
303 if (!strncmp(identifier, "Lasat Speed", 11))
305 isdn_init = "AT\\P1&B2X3";
306 modem_name = identifier;
308 status[stage] = Worked;
310 else if (!strncmp(identifier, "28642", 5) // Elite 2864I
311 || !strncmp(identifier, "1281", 4) // Omni TA128 USA
312 || !strncmp(identifier, "1282", 4) // Omni TA128 DSS1
313 || !strncmp(identifier, "1283", 4) // Omni TA128 1TR6
314 || !strncmp(identifier, "1291", 4) // Omni.Net USA
315 || !strncmp(identifier, "1292", 4) // Omni.Net DSS1
316 || !strncmp(identifier, "1293", 4) // Omni.Net 1TR6
319 status[stage] = Test;
320 if (doresult(WvString("ATI1\r"), 500))
321 if (!strncmp(identifier, "Elite 2864I", 11)
322 || !strncmp(identifier, "ZyXEL omni", 10))
324 isdn_init = "AT&O2B40";
325 if (strncmp(identifier, "ZyXEL", 5))
326 modem_name = WvString("ZyXEL %s", identifier);
327 else
328 modem_name = identifier;
330 status[stage] = Worked;
333 tries = 0;
334 stage++;
337 case BaudStep:
338 assert(modem);
339 modem->drain();
340 modem->speed(baud*2);
342 // if we try 2*baud three times without success, or setting 2*baud
343 // results in a lower setting than 1*baud, we have reached the
344 // top speed of the modem or the serial port, respectively.
345 if (tries >= 3 || modem->getspeed() <= baud)
347 // using the absolute maximum baud rate confuses many slower modems
348 // in obscure ways; step down one.
349 baud = modem->speed(baud);
350 debug("Max speed is %s; that should be safe.\n", baud);
352 stage++;
353 status[stage] = Worked;
354 break;
357 debug("Speed %s: ", modem->getspeed());
359 if (!doresult("AT\r", 500) || status[stage] == Fail)
361 tries++;
363 else // got a response
365 baud = modem->getspeed();
366 tries = 0;
367 // next time through we try a faster speed
369 break;
371 case Done:
372 case NUM_STAGES:
373 assert(0);
374 break; // should never happen
377 if (stage == Done) // we just incremented stage number to Done
379 WVRELEASE(modem);
384 bool WvModemScan::doresult(WvStringParm _s, int msec)
386 char buf[1024], *cptr;
387 size_t len;
388 WvString s(_s);
390 modem->drain();
391 usleep(50 * 1000); // delay a bit after emptying the buffer
392 modem->write(s);
394 debug("%s -- ", trim_string(s.edit()));
396 len = coagulate(buf, sizeof(buf), msec);
398 if (!len)
400 // debug("(no answer yet)\n");
401 return false;
404 buf[len] = 0;
406 cptr = trim_string(buf);
407 while (strchr(cptr, '\r'))
409 cptr = trim_string(strchr(cptr, '\r'));
410 if (stage == GetIdent && status[stage] == Test)
412 char *p = strpbrk(cptr, "\n\r");
413 if (p) *p=0;
414 identifier = cptr;
415 status[stage] = Worked;
416 debug("%s\n", identifier);
417 return true;
420 while (strchr(cptr, '\n'))
421 cptr = trim_string(strchr(cptr, '\n'));
423 debug("%s\n", cptr);
425 if (!strncmp(cptr, "OK", 2))
426 status[stage] = Worked;
427 else
428 status[stage] = Fail;
430 return true;
434 size_t WvModemScan::coagulate(char *buf, size_t size, int msec)
436 size_t len = 0, amt;
437 char *cptr = buf;
439 assert(modem);
441 if (!modem->isok())
443 broken = true;
444 return 0;
447 while (modem->select(msec, true, false))
449 amt = modem->read(cptr, size-1);
450 cptr[amt] = 0;
452 len += amt;
453 size -= amt;
454 cptr += amt;
456 if (strstr(buf, "OK") || strstr(buf, "ERROR"))
457 break;
460 return len;
464 const char *WvModemScan::is_isdn() const
466 if (isdn_init)
467 return isdn_init;
469 if (!identifier)
470 return NULL;
472 if (identifier == "3C882") // 3Com Impact IQ
473 return identifier;
474 if (identifier == "346800") // USR ISDN TA
475 return identifier;
477 #if 0 // this isn't nearly unique enough...
478 if (identifier == "960") // Motorola BitSurfr
479 return identifier;
480 #endif
482 return NULL;
486 static int fileselect(const struct dirent *e)
488 return !strncmp(e->d_name, "ttyS", 4) // serial
489 || !strncmp(e->d_name, "ttyLT", 5) // Lucent WinModem
490 || !strncmp(e->d_name, "ttyACM", 6) // USB acm Modems
491 || !strncmp(e->d_name, "ttyUSB", 6) // Modems on USB RS232
492 || !strncmp(e->d_name, "ircomm", 6) // Handys over IrDA
493 || !strncmp(e->d_name, "ttySL", 5); // SmartLink WinModem
495 // (no internal ISDN support) || !strncmp(e->d_name, "ttyI", 4);
499 static int filesort(const void *_e1, const void *_e2)
501 dirent const * const *e1 = (dirent const * const *)_e1;
502 dirent const * const *e2 = (dirent const * const *)_e2;
503 const char *p1, *p2;
504 int diff;
506 for (p1=(*e1)->d_name, p2=(*e2)->d_name; *p1 || *p2; p1++, p2++)
508 if (!isdigit(*p1) || !isdigit(*p2))
510 // Scan i (ircomm*) after t (tty*):
511 if (*p1 == 'i' && *p2 == 't')
512 return(1);
513 // Scan A (ttyACM*) after S (ttyS*):
514 if (*p1 == 'A' && *p2 == 'S')
515 return(1);
516 if (*p1 == 'S' && *p2 == 'A')
517 return(-1);
518 diff = *p1 - *p2;
519 if (diff) return diff;
521 else // both are digits
523 return atoi(p1) - atoi(p2);
527 return 0;
531 WvModemScanList::WvModemScanList(WvStringParm _exception)
532 : log("Modem Port Scan", WvLog::Debug)
534 struct dirent **namelist;
535 struct stat mouse, modem;
536 int num, count, mousestat, modemstat;
537 WvString exception;
539 thisline = -1;
540 printed = false;
542 mousestat = stat("/dev/mouse", &mouse);
543 modemstat = stat("/dev/modem", &modem);
544 num = scandir("/dev", &namelist, fileselect, filesort);
546 if (num < 0)
547 return;
549 // there shouldn't be a /dev/
550 if (!!_exception)
551 exception = strrchr(_exception, '/') + 1;
553 for (count = 0; count < num; count++)
555 // never search the device assigned to /dev/mouse; most mouse-using
556 // programs neglect to lock the device, so we could mess up the
557 // mouse response! (We are careful to put things back when done,
558 // but X seems to still get confused.) Anyway the mouse is seldom
559 // a modem.
560 if (mousestat==0 && mouse.st_ino == (ino_t)namelist[count]->d_ino)
562 log("\nIgnoring %s because /dev/mouse is a link to it.\n",
563 namelist[count]->d_name);
564 continue;
567 if (!!exception && !strcmp(exception, namelist[count]->d_name))
569 log("\nIgnoring %s because I've been told to ignore it.\n",
570 namelist[count]->d_name);
571 continue;
574 // bump /dev/modem to the top of the list, if it exists
575 // and also use /dev/modem as the device name which will be used later
576 // so PCMCIA can change it where it has detected a serial port and
577 // wvdial will follow without the need for another wvdialconf call.
578 if (modemstat==0 && modem.st_ino == (ino_t)namelist[count]->d_ino)
580 log("\nScanning %s first, /dev/modem is a link to it.\n",
581 namelist[count]->d_name);
582 prepend(new WvModemScan(WvString("%s", namelist[count]->d_name), true),
583 true);
585 else
586 append(new WvModemScan(WvString("%s", namelist[count]->d_name), false),
587 true);
590 while (--num >= 0)
591 free(namelist[num]);
592 free(namelist);
596 // we used to try to scan all ports simultaneously; unfortunately, this
597 // caused problems when people had "noncritical" IRQ conflicts (ie. two
598 // serial ports with the same IRQ, but they work as long as only one port
599 // is used at a time). Also, the log messages looked really confused.
601 // So now we do the scanning sequentially, which is slower. The port
602 // being scanned is at the head of the list. If the probe fails, we
603 // unlink the element. If it succeeds, we have found a modem -- so,
604 // isdone() knows we are done when the _first_ element is done.
606 void WvModemScanList::execute()
608 assert (!isdone());
610 WvModemScanList::Iter i(*this);
612 for (i.rewind(); i.next(); )
613 if (!i().isdone()) break;
615 if (!i.cur()) return;
617 WvModemScan &s(*i);
619 if (!s.isok())
621 if (!printed)
623 WvStringParm f = s.filename();
624 const char *cptr = strrchr(f, '/');
625 if (cptr)
626 cptr++;
627 else
628 cptr = f;
630 if (!strncmp(cptr, "tty", 3))
631 cptr += 3;
633 ++thisline %= 8;
634 if (!thisline)
635 log("\n");
637 log("%-4s ", cptr);
640 i.unlink();
641 printed = false;
643 else
645 s.execute();
647 if (s.isok())
648 printed = true;
651 if (isdone())
652 log("\n");
656 bool WvModemScanList::isdone()
658 WvModemScanList::Iter i(*this);
660 for (i.rewind(); i.next(); )
661 if (!i().isdone()) return false;
663 return true;