Patch to nm-modem-probe



Dan

As a result of keeping trying to get these darned ZTE devices to work reliably, I've come up with some tweaks to nm-modem-probe. Patch file is attached, plus text explaining the reasons for the changes.

This makes a big difference between the ZTE being still flaky and solidly reliable. I don't think these changes should have a negative impact on other modems, but there are new command options to switch the behaviour where it might be modem-specific.

I hope this is useful.

--
Cheers
Rick
--- nm-modem-probe-orig.c	2009-07-06 15:36:36.000000000 +0100
+++ nm-modem-probe.c	2009-07-21 21:02:54.000000000 +0100
@@ -313,7 +313,7 @@
 	return x->tv_sec < y->tv_sec;
 }
 
-static int modem_probe_caps(int fd, glong timeout_ms)
+static int modem_probe_caps(int fd, glong timeout_ms, glong init_sec, const char* initstr)
 {
 	const char *gcap_responses[] = { GCAP_TAG, HUAWEI_EC121_TAG, NULL };
 	const char *terminators[] = { "OK", "ERROR", "ERR", "+CME ERROR", NULL };
@@ -323,11 +323,24 @@
 	GTimeVal start, end;
 	gboolean send_success;
 
-	/* If a delay was specified, start a bit later */
-	if (timeout_ms > 500) {
-		g_usleep (500000);
-		timeout_ms -= 500;
+	/* wait for specified initial delay */
+	if (timeout_ms > 0) {
+		g_usleep (timeout_ms * 1000);
+		timeout_ms = 0;
+	}
+
+	/* turn off echo and (optionally) send +CPMS to stop ZTE +ZUSIMR messages
+		tries at 1-sec intervals to max of init_sec secs. from program start */
+	do {
+		g_usleep (1000000);
+		if (modem_send_command (fd, initstr)) {
+			const char *null_responses[] = { NULL };
+			modem_wait_reply (fd, 2, null_responses, terminators, &term_idx, &reply);
+			--init_sec;
 	}
+		else
+			init_sec = 0;
+	} while (init_sec > 0 && term_idx != 0);
 
 	/* Standard response timeout case */
 	timeout_ms += 3000;
@@ -339,7 +352,7 @@
 		g_get_current_time (&start);
 
 		idx = term_idx = 0;
-		send_success = modem_send_command (fd, "AT+GCAP\r\n");
+		send_success = modem_send_command (fd, "AT+GCAP\r");
 		if (send_success)
 			idx = modem_wait_reply (fd, 2, gcap_responses, terminators, &term_idx, &reply);
 		else
@@ -391,7 +404,7 @@
 		reply = NULL;
 
 		verbose ("GCAP failed, trying ATI...");
-		if (modem_send_command (fd, "ATI\r\n")) {
+		if (modem_send_command (fd, "ATI\r")) {
 			idx = modem_wait_reply (fd, 3, ati_responses, terminators, &term_idx, &reply);
 			if (0 == term_idx && 0 == idx) {
 				verbose ("ATI response: %s", reply);
@@ -410,7 +423,7 @@
 	if ((idx != -2) && !(ret & MODEM_CAP_GSM) && !(ret & MODEM_CAP_IS707_A)) {
 		const char *cgmm_responses[] = { CGMM_TAG, NULL };
 
-		if (modem_send_command (fd, "AT+CGMM\r\n")) {
+		if (modem_send_command (fd, "AT+CGMM\r")) {
 			idx = modem_wait_reply (fd, 5, cgmm_responses, terminators, &term_idx, &reply);
 			if (0 == term_idx && 0 == idx) {
 				verbose ("CGMM response: %s", reply);
@@ -428,7 +441,9 @@
 {
 	printf("Usage: probe-modem [options] <device>\n"
 	    " --export               export key/value pairs\n"
-	    " --delay <ms>           delay before probing (1 to 3000 ms inclusive)\n"
+	    " --delay <ms>           delay before probing (1 to 5000 ms inclusive)\n"
+	    " --initwait             secs to wait for modem to initialise (optional, default 10)\n"
+	    " --nocpms               don't include +CPMS? in init string\n"
 	    " --verbose              print verbose debugging output\n"
 	    " --quiet                suppress logging to stdout (does not affect logfile output)\n"
 	    " --log <file>           log all output\n"
@@ -445,6 +460,8 @@
 	static const struct option options[] = {
 		{ "export", 0, NULL, 'x' },
 		{ "delay", required_argument, NULL, 'a' },
+		{ "initwait", required_argument, NULL, 'w' },
+		{ "nocpms", 0, NULL, 'n' },
 		{ "verbose", 0, NULL, 'v' },
 		{ "quiet", 0, NULL, 'q' },
 		{ "log", required_argument, NULL, 'l' },
@@ -463,8 +480,10 @@
 	struct termios orig, attrs;
 	int fd = -1, caps, ret = 0;
 	guint32 delay_ms = 0;
+	guint32 init_sec = 10;
 	unsigned int vid = 0, pid = 0, usbif = 0, last_err = 0;
 	unsigned long int tmp;
+	const char *initstr = "ATE0+CPMS?\r";
 
 	while (1) {
 		int option;
@@ -479,12 +498,23 @@
 			break;
 		case 'a':
 			tmp = strtoul (optarg, NULL, 10);
-			if (tmp < 1 || tmp > 3000) {
+			if (tmp < 1 || tmp > 5000) {
 				fprintf (stderr, "Invalid delay: %s\n", optarg);
 				return 1;
 			}
 			delay_ms = (guint32) tmp;
 			break;
+		case 'w':
+			tmp = strtoul (optarg, NULL, 10);
+			if (tmp < 1 || tmp > 15) {
+				fprintf (stderr, "Invalid initwait: %s\n", optarg);
+				return 1;
+			}
+			init_sec = (guint32) tmp;
+			break;
+		case 'n':
+			initstr = "ATE0\r";
+			break;
 		case 'v':
 			verbose = TRUE;
 			break;
@@ -526,6 +556,9 @@
 		}
 	}
 
+	/* reduce initialise timeout by initial delay */
+	init_sec -= delay_ms / 1000;
+
 	if (logpath) {
 		time_t t = time (NULL);
 
@@ -590,7 +623,6 @@
 	attrs.c_iflag &= ~(IGNCR | ICRNL | IUCLC | INPCK | IXON | IXANY | IGNPAR);
 	attrs.c_oflag &= ~(OPOST | OLCUC | OCRNL | ONLCR | ONLRET);
 	attrs.c_lflag &= ~(ICANON | XCASE | ECHO | ECHOE | ECHONL);
-	attrs.c_lflag &= ~(ECHO | ECHOE);
 	attrs.c_cc[VMIN] = 1;
 	attrs.c_cc[VTIME] = 0;
 	attrs.c_cc[VEOF] = 1;
@@ -599,8 +631,8 @@
 	attrs.c_cflag |= (B9600 | CS8 | CREAD | PARENB);

 	tcsetattr (fd, TCSANOW, &attrs);
-	caps = modem_probe_caps (fd, delay_ms);
-	tcsetattr (fd, TCSANOW, &orig);
+	caps = modem_probe_caps (fd, delay_ms, init_sec, initstr);
+/*	tcsetattr (fd, TCSANOW, &orig);	don't restore the terminal attributes */
 
 	if (caps < 0) {
 		g_printerr ("%s: couldn't get modem capabilities\n", device);
Description and explanation of changes to nm-modem-probe, Rick Jones, 23/7/09





The attached patch to nm-modem-probe fixes various problems relating to modem

initialisation and reliable detection of port capabilities. These changes were done in

particular because of difficulties using a ZTE MF627 USB modem. However, most

changes are generic improvements, and the features that may be ZTE-specific are

switchable by command line options.





Problems causing unreliable detection.



The ZTE modem takes around 8 seconds to settle and give reliable responses to AT

commands. This is the time from when udev invokes nm-modem-probe. The original

nm-modem-probe has a --delay option which the source comments indicate is a

configurable delay before probing the modem. It was not actually implemented as

such, but was implemented as an overall timeout. The actual timeout was the

command parameter plus an arbitrary 3 secs, with an initial delay fixed at .5 secs.

The maximum value allowed was also only  3 sec (3000 msec).



Some modems may well be quick enough for these timings, but it's much too short a

time for the ZTE.



The ZTE also outputs continuous unsolicited messages (UMs) of +ZUSIMR:2 at 2 sec.

intervals. UMs cause all sorts of problems with detecting responses to commands,

and can also cause responses to get lost. These UMs can be stopped by giving any

form of +CPMS command - e.g. +CPMS? to query the status of the SMS memories.



The modem's initial state is with echo mode on. This results in unnecessary clutter for

the modem_wait_reply function to deal with, and can have other unpleasant side

effects (see below).



To deal with the issues above, the new version sends an initialisation string to the

modem, and re-tries at 1-sec intervals until an OK response is received, or a maximum

time has expired. The string is "ATE0+CPMS?\r". A command line option --nocpms

changes this to "ATE0\r", for modems that don't handle +CPMS. The default timeout is

10 secs. from program start, and can be set by the command line option --initwait= n.



The --delay option has been re-coded to be a true initial delay, and can be given a

value up to 5000 (5 secs). The actual time that the initialisation waits is thus the

--initwait value minus the --delay value.



By initialising the modem and  waiting for it to settle, the responses to the actual

probe commands (AT+GCAP, ATI, etc) are much more reliable and usually produce a

good response first time. The timeout for the probe commands is still hardcoded at 3

secs.





Serial port termio (stty) modes



The original program sets the port modes to the equivalent of "raw" before probing

the modem, and then resets them before exiting. NetworkManager itself does

likewise. Although resetting a port's modes is good general practice, here it leads to

problems, even kernel panics.



The initial modes of the usb-serial port seem to be somewhat random, or if they are

consistent they correspond to the defaults for a terminal. In particular this can

include echo being on. The default state of the modem is also to have echo on. While

idle, a modem can generate UMs (the ZTE being a particularly bad case). While the

serial port is closed this data is buffered somewhere (in the USB sub-system?), and

produces a short input flood when the port is opened. Although NetworkManager

switches off echo as soon as it opens the port, there is a short window where the

buffered data can be echoed back to the modem before this happens. If the modem

is also in echo mode there is the potential for an echo race.



I have definitely experienced occasional kernel panics using standard

NetworkManager, but these do not occur if steps are taken to disable the echo loop.

This is confirmed by other users in the Ubuntu forum.



The simplest solution is simply not to reset the port modes prior to nm-modem-

probe exiting. Given that the default modes are probably to suit a terminal, they will

always be completely inappropriate for a modem anyway, and NetworkManager will

set them itself as well.



Note that initialising the modem also helps address this issue, by turning off modem

echo and stopping the major cause of UMs. However, also disabling echo at the port

completely kills any danger of an echo race.





Udev rules



This modified version of nm-modem-probe works well with the standard

77-nm-probe-modem-capabilities.rules file, when a ZTE modem is used. It has not

been tested with any other modem. I would expect the most likely compatibility

problem would be modems that do not handle +CPMS? well (i.e. don't support SMS).



It is clearly possible to branch the rules to invoke nm-modem-probe with or without

--nocpms depending on the vendor ID, 0x19d2 being ZTE. I haven't attempted to do

this.





Test results



I have posted this version of nm-modem-probe on the Ubuntu forum where some

contributors having been unable to get ZTE modems to work, either reliably or at all.

Those that have tried it have reported that it resolves the problems, especially if used

in conjunction with  the recent update to NetworkManager that identifies ZTE ports

correctly (that update is not sufficient in itself).



It's also of course working well on my own machine, an Asus Eee 901 running Ubuntu

9.04.







[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]