/*
* mf626-helper - ZTE MF626 USB GSM modem tool.
*
* This tool works around the ZTE MF626 "lock": modem requires driver to spit
* magic string to the device every two minutes, and disconnects from the
* network otherwise.
*
* Copyright (C) 2008 Mikhail Gusarov <dottedmag@dottedmag.net>
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#define _GNU_SOURCE
#include <errno.h>
#include <getopt.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <usb.h>
#define PACKAGE_NAME "mf626-helper"
#define PACKAGE_VERSION "0.1"
#define ZTE_VENDOR 0x19d2
#define ZTE_PRODUCT_MF626 0x0031
#define ZTE_MF626_CONTROL_INTERFACE 0x01
#define ZTE_MF626_CONTROL_INTERFACE_SETTING 0x01
#define ZTE_MF626_CONTROL_ENDPOINT 0x02
#define MAGIC_STRING "AT+ZOPERTE=\"beeline\"\r\n"
#define MAGIC_STRING_INTERVAL 50
#define USB_TIMEOUT 200
int force;
int use_syslog;
int verbose;
int gotsignal;
void sighandler(int signal)
{
gotsignal = 1;
}
void perr(const char* format, ...)
{
va_list ap;
va_start(ap, format);
if(use_syslog)
{
char* msg;
if(-1 == vasprintf(&msg, format, ap))
abort();
syslog(LOG_ERR, "%s: %s", msg, strerror(errno));
free(msg);
}
else
{
fputs("E: ", stderr);
vfprintf(stderr, format, ap);
fprintf(stderr, ": %s\n", strerror(errno));
}
va_end(ap);
exit(1);
}
void err(const char* format, ...)
{
va_list ap;
va_start(ap, format);
if(use_syslog)
vsyslog(LOG_ERR, format, ap);
else
{
fputs("E: ", stderr);
vfprintf(stderr, format, ap);
fputc('\n', stderr);
}
va_end(ap);
exit(1);
}
void info(const char* format, ...)
{
if(!verbose)
return;
va_list ap;
va_start(ap, format);
if(use_syslog)
vsyslog(LOG_INFO, format, ap);
else
{
fputs("I: ", stderr);
vfprintf(stderr, format, ap);
fputc('\n', stderr);
}
va_end(ap);
}
int usb_bulk_pwrite(usb_dev_handle* dev, int ep, char* bytes, int size, int timeout)
{
int towrite = size;
while(towrite)
{
int written = usb_bulk_write(dev, ep, bytes, towrite, timeout);
if(written < 0)
return written;
bytes += written;
towrite -= written;
}
return size;
}
static struct option longopts[] = {
{ "force", 0, NULL, 0 },
{ "syslog", 0, NULL, 0 },
{ "verbose", 0, NULL, 0 },
{ "version", 0, NULL, 0 },
{ "help", 0, NULL, 0 },
{ 0, 0, 0, 0 }
};
void version()
{
printf(PACKAGE_NAME " " PACKAGE_VERSION "\n");
exit(0);
}
void usage()
{
printf("Usage: " PACKAGE_NAME " [OPTIONS] <USB bus number> <USB device number>\n");
printf("Connect to MF626 device on specified USB address and send magic string to it.\n");
printf("\n");
printf(" -f, --force Proceed even if USB device is not recognised as a modem\n");
printf(" or claimed by unknown driver\n");
printf(" -s, --syslog Use syslog instead of stderr for logging\n");
printf(" -v, --verbose Be verbose\n");
printf(" -V, --version Print version and exit\n");
printf(" -h, --help Print this help message and exit\n");
printf("\n");
printf("Bus number and device number must be an unsigned integers.\n");
printf("Both numbers must be in range [0..255].\n");
}
usb_dev_handle* open_modem(struct usb_device* d)
{
if(d->descriptor.idVendor == ZTE_VENDOR
&& d->descriptor.idProduct == ZTE_PRODUCT_MF626)
info("Found ZTE626 modem");
else
{
if(!force)
err("USB address is used by unknown device: %04x:%04x.",
d->descriptor.idVendor, d->descriptor.idProduct);
info("USB address is used by unknown device: %04x:%04x. "
"Proceeding as requested.",
d->descriptor.idVendor, d->descriptor.idProduct);
}
usb_dev_handle* dev = usb_open(d);
if(!dev)
perr("Unable to open USB device: usb_open");
return dev;
}
usb_dev_handle* find_modem(const char* busstr, const char* devstr)
{
struct usb_bus* b;
struct usb_device* d;
usb_init();
usb_find_busses();
usb_find_devices();
for(b = usb_busses; b; b = b->next)
if(!strcmp(b->dirname, busstr))
for(d = b->devices; d; d = d->next)
if(!strcmp(d->filename, devstr))
return open_modem(d);
return NULL;
}
void setup_modem(usb_dev_handle* dev)
{
if(usb_claim_interface(dev, ZTE_MF626_CONTROL_INTERFACE) < 0)
{
if(errno == EBUSY)
{
char driver[256];
if(usb_get_driver_np(dev, ZTE_MF626_CONTROL_INTERFACE,
driver, sizeof(driver)) < 0)
perr("usb_get_driver_np");
if(strcmp(driver, "onda")
&& strcmp(driver, "option")
&& strcmp(driver, "usb-serial"))
{
if(force)
info("Device is claimed by unknown driver: %s. "
"Proceeding as requested.", driver);
else
err("Device is claimed by unknown driver: %s.", driver);
}
if(usb_detach_kernel_driver_np(dev, ZTE_MF626_CONTROL_INTERFACE) < 0)
perr("usb_detach_kernel_driver_np");
if(usb_claim_interface(dev, ZTE_MF626_CONTROL_INTERFACE) < 0)
perr("usb_claim_interface");
}
else
perr("usb_claim_interface");
}
if(usb_set_altinterface(dev, ZTE_MF626_CONTROL_INTERFACE_SETTING) < 0)
perr("usb_set_altinterface");
if(usb_clear_halt(dev, ZTE_MF626_CONTROL_ENDPOINT) < 0)
perr("usb_clear_halt");
}
int ping_modem(usb_dev_handle* dev)
{
if(usb_bulk_pwrite(dev, ZTE_MF626_CONTROL_ENDPOINT,
MAGIC_STRING, strlen(MAGIC_STRING),
USB_TIMEOUT) < 0)
{
if(errno == ENODEV)
{
usb_close(dev);
return 0;
}
else if(errno == EAGAIN)
return 1;
else
{
usb_close(dev);
perr("usb_bulk_write");
}
}
return 1;
}
int main(int argc, char** argv)
{
openlog("mf626-helper", LOG_PID, LOG_DAEMON);
for(;;)
{
int option_index = 0;
int c = getopt_long(argc, argv, "fsvVh", longopts, &option_index);
if(c == -1)
break;
switch(c)
{
case 0:
switch(option_index)
{
case 0:
force = 1;
break;
case 1:
use_syslog = 1;
break;
case 2:
verbose = 1;
break;
case 3:
version();
case 4:
usage();
exit(0);
default:
err("Error in getopt: unexpected long option %d", option_index);
exit(1);
}
case 'f':
force = 1;
break;
case 's':
use_syslog = 1;
break;
case 'v':
verbose = 1;
break;
case 'V':
version();
case 'h':
usage();
exit(0);
case '?':
usage();
exit(1);
default:
err("Error in getopt: unexpected return code 0%o", c);
}
}
if(optind != argc - 2)
{
err("Exactly two non-option arguments is required, got %d. Try --help.",
argc - optind);
}
unsigned int busnum;
if(!sscanf(argv[optind], "%u", &busnum))
err("Unable to parse USB bus number: %s", argv[optind]);
if(busnum > 255)
err("USB bus number is out of range [0,255]: %s", argv[optind]);
char busstr[4];
sprintf(busstr, "%03u", busnum);
unsigned int devnum;
if(!sscanf(argv[optind+1], "%u", &devnum))
err("Unable to parse USB device number: %s", argv[optind+1]);
if(devnum > 255)
err("USB device number is out of range [0,255]: %s", argv[optind+1]);
char devstr[4];
sprintf(devstr, "%03u", devnum);
info("Trying to open %s:%s", busstr, devstr);
usb_dev_handle* dev = find_modem(busstr, devstr);
if(!dev)
err("Unable to find modem.");
setup_modem(dev);
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = &sighandler;
sigaction(SIGTERM, &act, NULL);
sigaction(SIGINT, &act, NULL);
while(ping_modem(dev))
{
if(gotsignal)
{
info("Got signal. Terminating");
exit(0);
}
sleep(MAGIC_STRING_INTERVAL);
}
info("Modem %s:%s has been disconnected. Terminating.", busstr, devstr);
exit(0);
}