/*
 * IPWireless USB Modem Driver
 *
 * Based on USB Serial Converter driver (linux 2.4.18) copyrighted as follows:
 *   Copyright (C) 1999 - 2001 Greg Kroah-Hartman (greg@kroah.com)
 *   Copyright (c) 2000 Peter Berger (pberger@brimson.com)
 *   Copyright (c) 2000 Al Borchers (borchers@steinerpoint.com)
 *
 * IPWireless modifications made by Stephen Blackheath <stephen@blacksapphire.com>,
 *                                  Ben Martel (benm@symmetric.co.nz>
 * copyrighted as follows:
 *   Copyright (C) 2004 by Symmetric Systems Ltd (NZ)
 *
 * Released under the GNU General Public Licence
 */

/*
 * ChangeLog:
 * 
 * 1.0.4  19 Feb 2004 - Added the ability to set and clear DTR and RTS.
 * 1.0.5  20 Feb 2004 - Driver closes tty socket if USB plug is pulled.
 * 1.0.6  25 Feb 2004 - Add proc filesystem file /proc/ipwireless_usb
 */

#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/smp_lock.h>
#include <asm/uaccess.h>
#include <linux/usb.h>
#include <linux/version.h>
#include <linux/proc_fs.h>

#ifdef CONFIG_DEVFS_FS
#include <linux/devfs_fs_kernel.h>
#endif

static int debug;

/*
 * Version Information
 */
#define DRIVER_VERSION "v1.0.6"
#define DRIVER_AUTHOR "Stephen Blackheath <stephen@blacksapphire.com>, Ben Martel <benm@symmetric.co.nz>"
#define DRIVER_DESC "IPWireless USB Modem Driver"


#define _DUMMY_FUNCTION_ "!"

/* ------ */

#define IPWIRELESS_USB_MINORS	16

#define MAX_NUM_PORTS		8	/* The maximum number of ports one device can grab at once */

#define USB_SERIAL_MAGIC	0x6702	/* magic number for usb_serial struct */
#define USB_SERIAL_PORT_MAGIC	0x7301	/* magic number for usb_serial_port struct */

/* parity check flag */
#define RELEVANT_IFLAG(iflag)	(iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))

/* Kernel version-specific defines. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
#define IPW_FILL_BULK_URB usb_fill_bulk_urb
#define IPW_FILL_INT_URB usb_fill_int_urb
#else
#define IPW_FILL_BULK_URB FILL_BULK_URB
#define IPW_FILL_INT_URB FILL_INT_URB
#endif

#define URB_TIMEOUT (HZ * 4) /* default urb timeout */


struct usb_serial_port {
	int			magic;
	struct usb_serial	*serial;	/* pointer back to the owner of this port */
	struct tty_struct *	tty;		/* the coresponding tty for this port */
	unsigned char		number;
	char			active;		/* someone has this device open */

	unsigned char *		interrupt_in_buffer;
	struct urb *		interrupt_in_urb;
	__u8			interrupt_in_endpointAddress;

	unsigned char *		bulk_in_buffer;
	struct urb *		read_urb;
	__u8			bulk_in_endpointAddress;

	unsigned char *		bulk_out_buffer;
	int			bulk_out_size;
	struct urb *		write_urb;
	__u8			bulk_out_endpointAddress;

	wait_queue_head_t	write_wait;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
	struct work_struct	work;
#else
	struct tq_struct	tqueue;		/* task queue for line discipline waking up */
#endif
	int			open_count;	/* number of times this port has been opened */
	struct semaphore	sem;		/* locks this structure */
	
	void *			private;	/* data private to the specific port */

        unsigned int            control_lines;
};

struct usb_serial {
	int				magic;
	struct usb_device *		dev;
        unsigned int                    ifnum;
	struct usb_serial_device_type *	type;			/* the type of usb serial device this is */
	struct usb_interface *		interface;		/* the interface for this device */
	struct tty_driver *		tty_driver;		/* the tty_driver for this device */
	unsigned char			minor;			/* the starting minor number for this device */
	unsigned char			num_ports;		/* the number of ports this device has */
	char				num_interrupt_in;	/* number of interrupt in endpoints we have */
	char				num_bulk_in;		/* number of bulk in endpoints we have */
	char				num_bulk_out;		/* number of bulk out endpoints we have */
	__u16				vendor;			/* vendor id of this device */
	__u16				product;		/* product id of this device */
	struct usb_serial_port		port[MAX_NUM_PORTS];

	void *			private;		/* data private to the specific driver */
};


#define MUST_HAVE_NOT	0x01
#define MUST_HAVE	0x02
#define DONT_CARE	0x03

#define	HAS		0x02
#define HAS_NOT		0x01

#define NUM_DONT_CARE	(-1)


/* This structure defines the individual serial converter. */
struct usb_serial_device_type {
	char	*name;
	const struct usb_device_id *id_table;
	char	needs_interrupt_in;
	char	needs_bulk_in;
	char	needs_bulk_out;
	char	num_interrupt_in;
	char	num_bulk_in;
	char	num_bulk_out;
	char	num_ports;		/* number of serial ports this device has */

	struct list_head	driver_list;	
};

extern int  usb_serial_register(struct usb_serial_device_type *new_device);
extern void usb_serial_deregister(struct usb_serial_device_type *device);


/* Inline functions to check the sanity of a pointer that is passed to us */
static inline int serial_paranoia_check (struct usb_serial *serial, const char *function)
{
	if (!serial) {
		dbg("%s - serial == NULL", function);
		return -1;
	}
	if (serial->magic != USB_SERIAL_MAGIC) {
		dbg("%s - bad magic number for serial", function);
		return -1;
	}
	if (!serial->type) {
		dbg("%s - serial->type == NULL!", function);
		return -1;
	}

	return 0;
}


static inline int port_paranoia_check (struct usb_serial_port *port, const char *function)
{
	if (!port) {
		dbg("%s - port == NULL", function);
		return -1;
	}
	if (port->magic != USB_SERIAL_PORT_MAGIC) {
		dbg("%s - bad magic number for port", function);
		return -1;
	}
	if (!port->serial) {
		dbg("%s - port->serial == NULL", function);
		return -1;
	}
	if (!port->tty) {
		dbg("%s - port->tty == NULL", function);
		return -1;
	}

	return 0;
}


static inline struct usb_serial* get_usb_serial (struct usb_serial_port *port, const char *function) 
{ 
	/* if no port was specified, or it fails a paranoia check */
	if (!port || 
		port_paranoia_check (port, function) ||
		serial_paranoia_check (port->serial, function)) {
		/* then say that we dont have a valid usb_serial thing, which will
		 * end up genrating -ENODEV return values */ 
		return NULL;
	}

	return port->serial;
}


static inline void usb_serial_debug_data (const char *file, const char *function, int size, const unsigned char *data)
{
	int i;

	if (!debug)
		return;
	
	printk (KERN_DEBUG "%s: %s - length = %d, data = ", file, function, size);
	for (i = 0; i < size; ++i) {
		printk ("%.2x ", data[i]);
	}
	printk ("\n");
}


/* Use our own dbg macro */
#undef dbg
#define dbg(format, arg...) do { if (debug) printk(KERN_DEBUG __FILE__ ": " format "\n" , ## arg); } while (0)

/* ------ */


/* These are modifications of the generic methods from the original usb-serial driver. */
static int  generic_open		(struct usb_serial_port *port, struct file *filp);
static void generic_close		(struct usb_serial_port *port, struct file *filp);
static int  generic_write		(struct usb_serial_port *port, int from_user, const unsigned char *buf, int count);
static int  generic_write_room		(struct usb_serial_port *port);
static int  generic_chars_in_buffer	(struct usb_serial_port *port);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
static void generic_read_bulk_callback  (struct urb *urb, struct pt_regs *regs);
static void generic_write_bulk_callback	(struct urb *urb, struct pt_regs *regs);
#else
static void generic_read_bulk_callback  (struct urb *urb);
static void generic_write_bulk_callback	(struct urb *urb);
#endif
static void generic_shutdown		(struct usb_serial *serial);

static void usb_serial_port_softint(void *private);

#define IPWIRELESS_USB_VENDOR_ID   0x0BC3
#define IPWIRELESS_USB_PRODUCT_ID1 0x0001  /* Single port configuration */
#define IPWIRELESS_USB_PRODUCT_ID2 0x0002  /* Two-port configuration with added console interface */

static struct usb_device_id generic_device_ids[3]; /* Initially all zeroes. */

/* All of the device info needed for the Generic Serial Converter */
static struct usb_serial_device_type generic_device = {
	name:			"ipwireless_usb",
	id_table:		generic_device_ids,
	needs_interrupt_in:	DONT_CARE,		/* don't have to have an interrupt in endpoint */
	needs_bulk_in:		MUST_HAVE,
	needs_bulk_out:		MUST_HAVE,
	num_interrupt_in:	NUM_DONT_CARE,
	num_bulk_in:		1,
	num_bulk_out:		1,
	num_ports:		1,
};


/* local function prototypes */
static int  serial_open (struct tty_struct *tty, struct file * filp);
static void serial_close (struct tty_struct *tty, struct file * filp);
static int  serial_write (struct tty_struct * tty, int from_user, const unsigned char *buf, int count);
static int  serial_write_room (struct tty_struct *tty);
static int  serial_chars_in_buffer (struct tty_struct *tty);
static void serial_throttle (struct tty_struct * tty);
static void serial_unthrottle (struct tty_struct * tty);
static int  serial_ioctl (struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg);
static void serial_set_termios (struct tty_struct *tty, struct termios * old);
static void serial_shutdown (struct usb_serial *serial);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
static int usb_serial_probe(struct usb_interface *interface,
			       const struct usb_device_id *id);
static void usb_serial_disconnect(struct usb_interface *interface);
#else
static void * usb_serial_probe(struct usb_device *dev, unsigned int ifnum,
			       const struct usb_device_id *id);
static void usb_serial_disconnect(struct usb_device *dev, void *ptr);
#endif

static struct usb_driver ipwireless_usb_driver = {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
        owner:          THIS_MODULE,
#endif
	name:		"ipwireless_usb",
	probe:		usb_serial_probe,
	disconnect:	usb_serial_disconnect,
	id_table:	generic_device_ids
};

/* There is no MODULE_DEVICE_TABLE for usbserial.c.  Instead
   the MODULE_DEVICE_TABLE declarations in each serial driver
   cause the "hotplug" program to pull in whatever module is necessary
   via modprobe, and modprobe will load usbserial because the serial
   drivers depend on it.
*/
   

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
static int			serial_refcount;
static struct tty_struct *	serial_tty[IPWIRELESS_USB_MINORS];
static struct termios *		serial_termios[IPWIRELESS_USB_MINORS];
static struct termios *		serial_termios_locked[IPWIRELESS_USB_MINORS];
#endif
static struct tty_driver	serial_tty_driver;
static struct usb_serial	*serial_table[IPWIRELESS_USB_MINORS];	/* initially all NULL */


static LIST_HEAD(ipwireless_usb_driver_list);


static struct usb_serial *get_serial_by_minor (int minor)
{
	return serial_table[minor];
}


static struct usb_serial *get_free_serial (int *minor, struct usb_device *dev, unsigned int ifnum)
{
	struct usb_serial *serial = NULL;
	int i;

	*minor = 0;

          /* First priority: Look for a place where the other interface of this device is already registered. */
	for (i = 0; i < IPWIRELESS_USB_MINORS; i += 2)
            if (serial_table[i+ifnum] == NULL &&
                serial_table[i+1-ifnum] != NULL &&
                serial_table[i+1-ifnum]->dev == dev)
                break;

          /* Second priority: Look for an unused pair of minor numbers. */
        if (i >= IPWIRELESS_USB_MINORS)
            for (i = 0; i < IPWIRELESS_USB_MINORS; i += 2)
		if (serial_table[i] == NULL &&
                    serial_table[i+1] == NULL)
	            break;

          /* If no more empty slots, fail. */
        if (i >= IPWIRELESS_USB_MINORS)
            return NULL;

        i += ifnum;

        if (!(serial = kmalloc(sizeof(struct usb_serial), GFP_KERNEL))) {
                err(_DUMMY_FUNCTION_ " - Out of memory");
                return NULL;
        }
        memset(serial, 0, sizeof(struct usb_serial));
        serial->magic = USB_SERIAL_MAGIC;
        serial_table[i] = serial;
        *minor = i;
        dbg(_DUMMY_FUNCTION_ " - minor base = %d", *minor);
        return serial;
}


static void return_serial (struct usb_serial *serial)
{
	int i;

	dbg(_DUMMY_FUNCTION_);

	if (serial == NULL)
		return;

	for (i = 0; i < serial->num_ports; ++i) {
		serial_table[serial->minor + i] = NULL;
	}

	return;
}



/*****************************************************************************
 * Driver tty interface functions
 *****************************************************************************/
static int serial_open (struct tty_struct *tty, struct file * filp)
{
	struct usb_serial *serial;
	struct usb_serial_port *port;
	int portNumber;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
        int minor = tty->index;
#else
        int minor = MINOR(tty->device);
#endif

	dbg(_DUMMY_FUNCTION_);

	/* initialize the pointer incase something fails */
	tty->driver_data = NULL;

	/* get the serial object associated with this tty pointer */

	serial = get_serial_by_minor(minor);

	if (serial_paranoia_check (serial, _DUMMY_FUNCTION_)) {
		return -ENODEV;
	}

	/* set up our port structure making the tty driver remember our port object, and us it */
	portNumber = minor - serial->minor;
	port = &serial->port[portNumber];
	tty->driver_data = port;
	port->tty = tty;
	 
	/* pass on to the driver specific version of this function if it is available */
        return (generic_open(port, filp));
}


static void serial_close(struct tty_struct *tty, struct file * filp)
{
	struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data;
	struct usb_serial *serial = get_usb_serial (port, _DUMMY_FUNCTION_);

	if (!serial) {
		return;
	}

	dbg(_DUMMY_FUNCTION_ " - port %d", port->number);
	
	if (!port->active) {
		dbg (_DUMMY_FUNCTION_ " - port not opened");
		return;
	}

	/* pass on to the driver specific version of this function if it is available */
        generic_close(port, filp);
}	


static int serial_write (struct tty_struct * tty, int from_user, const unsigned char *buf, int count)
{
	struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data;
	struct usb_serial *serial = get_usb_serial (port, _DUMMY_FUNCTION_);
	
	if (!serial) {
		return -ENODEV;
	}
	
	dbg(_DUMMY_FUNCTION_ " - port %d, %d byte(s)", port->number, count);

	if (!port->active) {
		dbg (_DUMMY_FUNCTION_ " - port not opened");
		return -EINVAL;
	}
	
	/* pass on to the driver specific version of this function if it is available */
        return (generic_write(port, from_user, buf, count));
}


static int serial_write_room (struct tty_struct *tty) 
{
	struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data;
	struct usb_serial *serial = get_usb_serial (port, _DUMMY_FUNCTION_);

	if (!serial) {
		return -ENODEV;
	}

	dbg(_DUMMY_FUNCTION_ " - port %d", port->number);
	
	if (!port->active) {
		dbg (_DUMMY_FUNCTION_ " - port not open");
		return -EINVAL;
	}

	/* pass on to the driver specific version of this function if it is available */
        return (generic_write_room(port));
}


static int serial_chars_in_buffer (struct tty_struct *tty) 
{
	struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data;
	struct usb_serial *serial = get_usb_serial (port, _DUMMY_FUNCTION_);

	if (!serial) {
		return -ENODEV;
	}

	if (!port->active) {
		dbg (_DUMMY_FUNCTION_ " - port not open");
		return -EINVAL;
	}

	/* pass on to the driver specific version of this function if it is available */
        return (generic_chars_in_buffer(port));
}


static void serial_throttle (struct tty_struct * tty)
{
	struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data;
	struct usb_serial *serial = get_usb_serial (port, _DUMMY_FUNCTION_);

	if (!serial) {
		return;
	}

	dbg(_DUMMY_FUNCTION_ " - port %d", port->number);

	if (!port->active) {
		dbg (_DUMMY_FUNCTION_ " - port not open");
		return;
	}

	/* pass on to the driver specific version of this function */
/*
	if (serial->type->throttle) {
		serial->type->throttle(port);
	}
        */

	return;
}


static void serial_unthrottle (struct tty_struct * tty)
{
	struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data;
	struct usb_serial *serial = get_usb_serial (port, _DUMMY_FUNCTION_);

	if (!serial) {
		return;
	}

	dbg(_DUMMY_FUNCTION_ " - port %d", port->number);

	if (!port->active) {
		dbg (_DUMMY_FUNCTION_ " - port not open");
		return;
	}

	/* pass on to the driver specific version of this function */
/*
	if (serial->type->unthrottle) {
		serial->type->unthrottle(port);
	}
        */

	return;
}

static int get_control_lines(struct usb_serial_port* port)
{
    unsigned int control = port->control_lines & (TIOCM_RTS|TIOCM_DTR);
      // Lie, saying that CTS, DSR and CD are set.
    control |= TIOCM_CTS | TIOCM_DSR | TIOCM_CD;
    return control;
}

static int set_control_lines(struct usb_serial_port* port, unsigned int set, unsigned int clear, int verbose)
{
    struct usb_serial *serial = get_usb_serial (port, _DUMMY_FUNCTION_);
    unsigned int control = 0;
    int response;
    unsigned int interface = serial->ifnum;

// The Request 0x07 holds the modem control signals.
//
//  wValue: B0 DTR State
//          B1 RTS State
//          B2..B7 Reserved
//          B8 DTR Mask (if clear do not act on DTR state)
//          B9 RTS Mask (if clear do not act on RTS state)
//          B10..B15 Reserved

    port->control_lines &= ~clear;
    port->control_lines |= set;

    if (set & TIOCM_RTS) {
        control |= 0x202;
        if (verbose)
            info("port %d set RTS", port->number);
    }
    if (set & TIOCM_DTR) {
        control |= 0x101;
        if (verbose)
            info("port %d set DTR", port->number);
    }
    if (clear & TIOCM_RTS) {
        control |= 0x200;
        if (verbose)
            info("port %d clear RTS", port->number);
    }
    if (clear & TIOCM_DTR) {
        control |= 0x100;
        if (verbose)
            info("port %d clear DTR", port->number);
    }

    if (control == 0)
        response = 0;
    else
        response = usb_control_msg(
            serial->dev,
            usb_sndctrlpipe(serial->dev, 0),
            0x07,
            USB_TYPE_VENDOR|USB_DIR_OUT|USB_RECIP_INTERFACE,  // Request type
            control,
            interface,
            NULL, 0, URB_TIMEOUT);
    if (response < 0)
        err("set_termios failed: response to 0x07 = %d", response);
    return response;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) 
static int serial_tiocmget (struct tty_struct *tty, struct file *file)
{
	struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data;
	struct usb_serial *serial = get_usb_serial (port, __FUNCTION__);

	if (!serial)
		goto exit;

	dbg("%s - port %d", __FUNCTION__, port->number);

	if (!port->open_count) {
		dbg("%s - port not open", __FUNCTION__);
		goto exit;
	}

        return get_control_lines(port);

exit:
	return -EINVAL;
}

static int serial_tiocmset (struct tty_struct *tty, struct file *file,
			    unsigned int set, unsigned int clear)
{
	struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data;
	struct usb_serial *serial = get_usb_serial (port, __FUNCTION__);

	if (!serial)
		goto exit;

	dbg("%s - port %d", __FUNCTION__, port->number);

	if (!port->open_count) {
		dbg("%s - port not open", __FUNCTION__);
		goto exit;
	}

        return set_control_lines(port, set, clear, 1);

exit:
	return -EINVAL;
}


static int serial_ioctl (struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg)
{
	struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data;
	struct usb_serial *serial = get_usb_serial (port, _DUMMY_FUNCTION_);

	if (!serial) {
		return -ENODEV;
	}

	dbg(_DUMMY_FUNCTION_ " - port %d, cmd 0x%.4x", port->number, cmd);

	if (!port->active) {
		dbg (_DUMMY_FUNCTION_ " - port not open");
		return -ENODEV;
	}

	/* pass on to the driver specific version of this function if it is available */
/*	if (serial->type->ioctl) {
		return (serial->type->ioctl(port, file, cmd, arg));
	} else  */

        return -ENOIOCTLCMD;
}

#else

static int serial_ioctl (struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg_param)
{
	struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data;
	struct usb_serial *serial = get_usb_serial (port, _DUMMY_FUNCTION_);
        unsigned int* pui_arg = (unsigned int*) arg_param;

	if (!serial) {
		return -ENODEV;
	}

	dbg(_DUMMY_FUNCTION_ " - port %d, cmd 0x%.4x", port->number, cmd);

	if (!port->active) {
		dbg (_DUMMY_FUNCTION_ " - port not open");
		return -ENODEV;
	}


	switch (cmd) {
		
		case TIOCMGET:
			dbg("%s (%d) TIOCMGET", _DUMMY_FUNCTION_, port->number);
                        {
                            unsigned int control = (unsigned int) get_control_lines(port);
                            if (copy_to_user(pui_arg, &control, sizeof(int)))
                                return -EFAULT;
                            else
                                return 0;
                        }

		case TIOCMBIS:
		case TIOCMBIC:
		case TIOCMSET:
                        {
                            unsigned int arg;
                            unsigned int set = 0;
                            unsigned int clear = 0;
                            if (copy_from_user(&arg, pui_arg, sizeof(int)))
                                    return -EFAULT;
                            switch (cmd) {
                                case TIOCMBIS:
                                    set = arg;
                                    break;
                                case TIOCMBIC:
                                    clear = arg;
                                    break;
                                case TIOCMSET:
                                    set = arg;
                                    clear = ~set;
                                    break;
                            }
                            if (set != 0 || clear != 0)
                                return set_control_lines(port, set, clear, 1);
                            else
                                return 0;
                        }

		default:
			dbg("%s not supported = 0x%04x", _DUMMY_FUNCTION_, cmd);
			break;
	}

        return -ENOIOCTLCMD;
}

#endif

static void serial_set_termios (struct tty_struct *tty, struct termios * old)
{
	struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data;
	struct usb_serial *serial = get_usb_serial (port, _DUMMY_FUNCTION_);

	if (!serial) {
		return;
	}

	dbg(_DUMMY_FUNCTION_ " - port %d", port->number);

	if (!port->active) {
		dbg (_DUMMY_FUNCTION_ " - port not open");
		return;
	}

	/* pass on to the driver specific version of this function if it is available */
        /*
	if (serial->type->set_termios) {
		serial->type->set_termios(port, old);
	}
        */
	
	return;
}

static void serial_break (struct tty_struct *tty, int break_state)
{
	struct usb_serial_port *port = (struct usb_serial_port *) tty->driver_data;
	struct usb_serial *serial = get_usb_serial (port, _DUMMY_FUNCTION_);

	if (!serial) {
		return;
	}

	dbg(_DUMMY_FUNCTION_ " - port %d", port->number);

	if (!port->active) {
		dbg (_DUMMY_FUNCTION_ " - port not open");
		return;
	}

	/* pass on to the driver specific version of this function if it is
           available */
        /*
	if (serial->type->break_ctl) {
		serial->type->break_ctl(port, break_state);
	}
        */
}


static void serial_shutdown (struct usb_serial *serial)
{
        generic_shutdown(serial);
}



/*****************************************************************************
 * IPWireless USB specific driver functions
 *****************************************************************************/

static char* iftype_name(unsigned int ifnum)
{
    return ifnum == 0 ? "modem" : "console";
}

static void show_status(struct usb_serial* serial)
{
    unsigned int interface = serial->ifnum;
    unsigned char buf[10];
    int response;
    response = usb_control_msg(
        serial->dev,
        usb_rcvctrlpipe(serial->dev, 0),
        0x08,
        USB_TYPE_VENDOR|USB_DIR_IN|USB_RECIP_INTERFACE,  // Request type
        0x0000,
        interface,
        buf, sizeof(buf), URB_TIMEOUT);
    if (response >= 1)
        info("status = 0x%02d", (int) buf[0]);
    else
        info("failed to read status: %d\n", (int) response);
}

static void fetch_0x10(struct usb_serial* serial)
{
    unsigned char buf[32];
    int response;
    unsigned int interface = serial->ifnum;
    response = usb_control_msg(
        serial->dev,
        usb_rcvctrlpipe(serial->dev, 0),
        0x10,
        USB_TYPE_VENDOR|USB_DIR_IN|USB_RECIP_INTERFACE,  // Request type
        0x0000,
        interface,
        buf, sizeof(buf), URB_TIMEOUT);
    if (response >= 0)
        info("0x10: received 0x%02x bytes", (int) response);
    else
        info("0x10: failed : %d\n", (int) response);
}

static int setup_ipwireless_usb(struct usb_serial_port* port)
{
    struct usb_serial *serial = port->serial;
    int response;

    unsigned int interface = serial->ifnum;
    int isConsole = interface == 0x0001;

    static unsigned char data_0x13[] = {
        0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    };
    static unsigned char data_0x19[] = {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    };
    unsigned char* copy_0x13 = kmalloc(sizeof(data_0x13), GFP_KERNEL);
    unsigned char* copy_0x19 = kmalloc(sizeof(data_0x19), GFP_KERNEL);
    memcpy(copy_0x13, data_0x13, sizeof(data_0x13));
    memcpy(copy_0x19, data_0x19, sizeof(data_0x19));

    info("setting up %s interface on ttyIPWu%d...",
        iftype_name(serial->ifnum), (int) serial->minor);
    
    /* This stuff was observed being sent by the Windows driver.
      Without these, communication with the modem does not work.
      I don't know what any of this does. */

    response = usb_control_msg(
        serial->dev,
        usb_sndctrlpipe(serial->dev, 0),
        0x11,  // Request
        USB_TYPE_VENDOR|USB_DIR_OUT|USB_RECIP_INTERFACE,  // Request type
        0x0000,
        interface,
        NULL, 0, URB_TIMEOUT);
    if (response < 0) {
        err("setup failed!! response to 0x11 = %d", response);
        goto exit;
    }
    response = usb_control_msg(
        serial->dev,
        usb_sndctrlpipe(serial->dev, 0),
        0x00,  // Request
        USB_TYPE_VENDOR|USB_DIR_OUT|USB_RECIP_INTERFACE,  // Request type
        0x0001,
        interface,
        NULL, 0, URB_TIMEOUT);
    if (response < 0) {
        err("setup failed!! response to 0x00 = %d", response);
        goto exit;
    }
    response = usb_control_msg(
        serial->dev,
        usb_sndctrlpipe(serial->dev, 0),
        0x13,
        USB_TYPE_VENDOR|USB_DIR_OUT|USB_RECIP_INTERFACE,  // Request type
        0x0000,
        interface,
        copy_0x13, sizeof(data_0x13), URB_TIMEOUT);
    if (response < 0) {
        err("setup failed!! response to 0x13 = %d", response);
        goto exit;
    }
    show_status(serial);
    if (!isConsole) {
        response = set_control_lines(port, TIOCM_DTR, 0, 0);  // Set DTR
        if (response < 0) {
            err("setup failed!! response to 0x07 = %d", response);
            goto exit;
        }
    }
    fetch_0x10(serial);
    response = usb_control_msg(
        serial->dev,
        usb_sndctrlpipe(serial->dev, 0),
        0x12,
        USB_TYPE_VENDOR|USB_DIR_OUT|USB_RECIP_INTERFACE,  // Request type
        0x000f,
        interface,
        NULL, 0, URB_TIMEOUT);
    if (response < 0) {
        err("setup failed!! response to 0x12 = %d", response);
        goto exit;
    }
    response = usb_control_msg(
        serial->dev,
        usb_sndctrlpipe(serial->dev, 0),
        0x01,
        USB_TYPE_VENDOR|USB_DIR_OUT|USB_RECIP_INTERFACE,  // Request type
        0x0180,
        interface,
        NULL, 0, URB_TIMEOUT);
    if (response < 0) {
        err("setup failed!! response to 0x01 = %d", response);
        goto exit;
    }
    response = set_control_lines(port, TIOCM_RTS, 0, 0);  // Set RTS
    if (response < 0) {
        err("setup failed!! response to 0x07 = %d", response);
        goto exit;
    }
    response = set_control_lines(port, TIOCM_DTR, 0, 0);  // Set DTR
    if (response < 0) {
        err("setup failed!! response to 0x07 = %d", response);
        goto exit;
    }
    response = usb_control_msg(
        serial->dev,
        usb_sndctrlpipe(serial->dev, 0),
        0x03,
        USB_TYPE_VENDOR|USB_DIR_OUT|USB_RECIP_INTERFACE,  // Request type
        0x0800,
        interface,
        NULL, 0, URB_TIMEOUT);
    if (response < 0) {
        err("setup failed!! response to 0x03 = %d", response);
        goto exit;
    }
    response = usb_control_msg(
        serial->dev,
        usb_sndctrlpipe(serial->dev, 0),
        0x19,  // Request
        USB_TYPE_VENDOR|USB_DIR_OUT|USB_RECIP_INTERFACE,  // Request type
        0x0000,
        interface,
        copy_0x19, sizeof(data_0x19), URB_TIMEOUT);
    if (response < 0) {
        err("setup failed!! response to 0x19 = %d", response);
        goto exit;
    }
    response = usb_control_msg(
        serial->dev,
        usb_sndctrlpipe(serial->dev, 0),
        0x13,
        USB_TYPE_VENDOR|USB_DIR_OUT|USB_RECIP_INTERFACE,  // Request type
        0x0000,
        interface,
        copy_0x13, sizeof(data_0x13), URB_TIMEOUT);
    if (response < 0) {
        err("setup failed!! response to 0x13 = %d", response);
        goto exit;
    }
    show_status(serial);

exit:
    kfree(copy_0x13);
    kfree(copy_0x19);
    return response;
}

static int shutdown_ipwireless_usb(struct usb_serial_port* port)
{
    struct usb_serial *serial = port->serial;
    unsigned int interface = serial->ifnum;
    int response;
    info("shutting down %s interface on ttyIPWu%d...",
        iftype_name(serial->ifnum), (int) serial->minor);

    response = usb_control_msg(
        serial->dev,
        usb_sndctrlpipe(serial->dev, 0),
        0x12,
        USB_TYPE_VENDOR|USB_DIR_OUT|USB_RECIP_INTERFACE,  // Request type
        0x000f,
        interface,
        NULL, 0, URB_TIMEOUT);
    if (response < 0) {
        err("shutdown failed!! response to 0x12 = %d", response);
        goto exit;
    }
/*
  Sending this message breaks things.
    response = usb_control_msg(
        serial->dev,
        usb_sndctrlpipe(serial->dev, 0),
        0x07,
        USB_TYPE_VENDOR|USB_DIR_OUT|USB_RECIP_INTERFACE,  // Request type
        0x0100,
        0x0000,
        NULL, 0, URB_TIMEOUT);
    info("response to 0x07 = %d", response);
 */
    response = usb_control_msg(
        serial->dev,
        usb_sndctrlpipe(serial->dev, 0),
        0x00,  // Request
        USB_TYPE_VENDOR|USB_DIR_OUT|USB_RECIP_INTERFACE,  // Request type
        0x0001,
        interface,
        NULL, 0, URB_TIMEOUT);
    if (response < 0) {
        err("shutdown failed!! response to 0x00 = %d", response);
        goto exit;
    }

exit:
    return response;
}
 
static int generic_open (struct usb_serial_port *port, struct file *filp)
{
	struct usb_serial *serial = port->serial;
	int result = 0;

	if (port_paranoia_check (port, _DUMMY_FUNCTION_))
		return -ENODEV;

	/* only increment our usage count, if this device is _really_ a generic device */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
	MOD_INC_USE_COUNT;
#endif

	dbg(_DUMMY_FUNCTION_ " - port %d", port->number);

	down (&port->sem);
	
	++port->open_count;
	
	if (!port->active) {
		port->active = 1;

		/* force low_latency on so that our tty_push actually forces the data through, 
		   otherwise it is scheduled, and with high data rates (like with OHCI) data
		   can get lost. */
		port->tty->low_latency = 1;

                setup_ipwireless_usb(port);
		
		/* if we have a bulk interrupt, start reading from it */
		if (serial->num_bulk_in) {
			/* Start reading from the device */
			IPW_FILL_BULK_URB(port->read_urb, serial->dev, 
				      usb_rcvbulkpipe(serial->dev, port->bulk_in_endpointAddress),
				      port->read_urb->transfer_buffer, port->read_urb->transfer_buffer_length,
				      generic_read_bulk_callback, 
				      port);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) 
			result = usb_submit_urb(port->read_urb, GFP_KERNEL);
#else
			result = usb_submit_urb(port->read_urb);
#endif
			if (result)
				err(_DUMMY_FUNCTION_ " - failed resubmitting read urb, error %d", result);
		}
	}
	
	up (&port->sem);
	
	return result;
}


static void generic_close (struct usb_serial_port *port, struct file * filp)
{
	struct usb_serial *serial = port->serial;

          /* If the USB cable is unplugged while a client is connected to the
            serial port, then serial->dev will be NULL, so don't attempt to
            communicate with the device. */
        if (serial->dev != NULL)
            shutdown_ipwireless_usb(port);

	dbg(_DUMMY_FUNCTION_ " - port %d", port->number);

	down (&port->sem);

	--port->open_count;

	if (port->open_count <= 0) {
		if (serial->dev) {
			/* shutdown any bulk reads that might be going on */
			if (serial->num_bulk_out)
				usb_unlink_urb (port->write_urb);
			if (serial->num_bulk_in)
				usb_unlink_urb (port->read_urb);
		}
		
		port->active = 0;
		port->open_count = 0;
	}

	up (&port->sem);

	/* only decrement our usage count, if this device is _really_ a generic device */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
	MOD_DEC_USE_COUNT;
#endif
}


static int generic_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count)
{
	struct usb_serial *serial = port->serial;
	int result;

	dbg(_DUMMY_FUNCTION_ " - port %d", port->number);

	if (count == 0) {
		dbg(_DUMMY_FUNCTION_ " - write request of 0 bytes");
		return (0);
	}

	/* only do something if we have a bulk out endpoint */
	if (serial->num_bulk_out) {
		if (port->write_urb->status == -EINPROGRESS) {
			dbg (_DUMMY_FUNCTION_ " - already writing");
			return (0);
		}

		count = (count > port->bulk_out_size) ? port->bulk_out_size : count;

		if (from_user) {
			if (copy_from_user(port->write_urb->transfer_buffer, buf, count))
				return -EFAULT;
		}
		else {
			memcpy (port->write_urb->transfer_buffer, buf, count);
		}  

		usb_serial_debug_data (__FILE__, _DUMMY_FUNCTION_, count, port->write_urb->transfer_buffer);

		/* set up our urb */
		IPW_FILL_BULK_URB(port->write_urb, serial->dev, 
			      usb_sndbulkpipe(serial->dev, port->bulk_out_endpointAddress),
			      port->write_urb->transfer_buffer, count,
                              generic_write_bulk_callback, 
			      port);

		/* send the data out the bulk port */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) 
		result = usb_submit_urb(port->write_urb, GFP_ATOMIC);
#else
		result = usb_submit_urb(port->write_urb);
#endif
		if (result)
			err(_DUMMY_FUNCTION_ " - failed submitting write urb, error %d", result);
		else
			result = count;

		return result;
	}
	
	/* no bulk out, so return 0 bytes written */
	return (0);
} 


static int generic_write_room (struct usb_serial_port *port)
{
	struct usb_serial *serial = port->serial;
	int room = 0;

	dbg(_DUMMY_FUNCTION_ " - port %d", port->number);
	
	if (serial->num_bulk_out) {
		if (port->write_urb->status != -EINPROGRESS)
			room = port->bulk_out_size;
	}
	
	dbg(_DUMMY_FUNCTION_ " - returns %d", room);
	return (room);
}


static int generic_chars_in_buffer (struct usb_serial_port *port)
{
	struct usb_serial *serial = port->serial;
	int chars = 0;

	dbg(_DUMMY_FUNCTION_ " - port %d", port->number);
	
	if (serial->num_bulk_out) {
		if (port->write_urb->status == -EINPROGRESS)
			chars = port->write_urb->transfer_buffer_length;
	}

	dbg (_DUMMY_FUNCTION_ " - returns %d", chars);
	return (chars);
}


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
static void generic_read_bulk_callback (struct urb *urb, struct pt_regs *regs)
#else
static void generic_read_bulk_callback (struct urb *urb)
#endif
{
	struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
	struct usb_serial *serial = get_usb_serial (port, _DUMMY_FUNCTION_);
	struct tty_struct *tty;
	unsigned char *data = urb->transfer_buffer;
	int i;
	int result;

	dbg(_DUMMY_FUNCTION_ " - port %d", port->number);
	
	if (!serial) {
		dbg(_DUMMY_FUNCTION_ " - bad serial pointer, exiting");
		return;
	}

	if (urb->status) {
		dbg(_DUMMY_FUNCTION_ " - nonzero read bulk status received: %d", urb->status);
		return;
	}

	usb_serial_debug_data (__FILE__, _DUMMY_FUNCTION_, urb->actual_length, data);

	tty = port->tty;
	if (urb->actual_length) {
		for (i = 0; i < urb->actual_length ; ++i) {
			/* if we insert more than TTY_FLIPBUF_SIZE characters, we drop them. */
			if(tty->flip.count >= TTY_FLIPBUF_SIZE) {
				tty_flip_buffer_push(tty);
			}
			/* this doesn't actually push the data through unless tty->low_latency is set */
			tty_insert_flip_char(tty, data[i], 0);
		}
	  	tty_flip_buffer_push(tty);
	}

	/* Continue trying to always read  */
	IPW_FILL_BULK_URB(port->read_urb, serial->dev, 
		      usb_rcvbulkpipe(serial->dev, port->bulk_in_endpointAddress),
		      port->read_urb->transfer_buffer, port->read_urb->transfer_buffer_length,
		      generic_read_bulk_callback, 
		      port);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) 
	result = usb_submit_urb(port->read_urb, GFP_ATOMIC);
#else
	result = usb_submit_urb(port->read_urb);
#endif
	if (result)
		err(_DUMMY_FUNCTION_ " - failed resubmitting read urb, error %d", result);
}


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
static void generic_write_bulk_callback (struct urb *urb, struct pt_regs *regs)
#else
static void generic_write_bulk_callback (struct urb *urb)
#endif
{
	struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
	struct usb_serial *serial = get_usb_serial (port, _DUMMY_FUNCTION_);

	dbg(_DUMMY_FUNCTION_ " - port %d", port->number);
	
	if (!serial) {
		dbg(_DUMMY_FUNCTION_ " - bad serial pointer, exiting");
		return;
	}

	if (urb->status) {
		dbg(_DUMMY_FUNCTION_ " - nonzero write bulk status received: %d", urb->status);
		return;
	}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
	usb_serial_port_softint((void *)port);
	schedule_work(&port->work);
#else
	queue_task(&port->tqueue, &tq_immediate);
	mark_bh(IMMEDIATE_BH);
#endif
}


static void generic_shutdown (struct usb_serial *serial)
{
	int i;

	dbg (_DUMMY_FUNCTION_);

	/* stop reads and writes on all ports */
	for (i=0; i < serial->num_ports; ++i) {
		while (serial->port[i].open_count > 0) {
                        if (serial->port[i].tty != NULL)
                                tty_hangup(serial->port[i].tty);
			generic_close (&serial->port[i], NULL);
		}
	}
}


static void usb_serial_port_softint(void *private)
{
	struct usb_serial_port *port = (struct usb_serial_port *)private;
	struct usb_serial *serial = get_usb_serial (port, _DUMMY_FUNCTION_);
	struct tty_struct *tty;

	dbg(_DUMMY_FUNCTION_ " - port %d", port->number);
	
	if (!serial) {
		return;
	}
 	
	tty = port->tty;
	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) {
		dbg(_DUMMY_FUNCTION_ " - write wakeup call.");
		(tty->ldisc.write_wakeup)(tty);
	}

	wake_up_interruptible(&tty->write_wait);
}



#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
static int usb_serial_probe(struct usb_interface *interface,
			       const struct usb_device_id *id)
{
	struct usb_device *dev = interface_to_usbdev(interface);
	struct usb_host_interface *iface_desc;
        unsigned int ifnum;
#else
static void * usb_serial_probe(struct usb_device *dev, unsigned int ifnum,
			       const struct usb_device_id *id)
{
	struct usb_interface *interface = &dev->actconfig->interface[ifnum];
	struct usb_interface_descriptor *iface_desc;
#endif
	struct usb_serial *serial = NULL;
	struct usb_serial_port *port;
	struct usb_endpoint_descriptor *endpoint;
	struct usb_endpoint_descriptor *interrupt_in_endpoint[MAX_NUM_PORTS];
	struct usb_endpoint_descriptor *bulk_in_endpoint[MAX_NUM_PORTS];
	struct usb_endpoint_descriptor *bulk_out_endpoint[MAX_NUM_PORTS];
	struct usb_serial_device_type *type = NULL;
	struct list_head *tmp;
	int found;
	int minor;
	int buffer_size;
	int i;
	char interrupt_pipe;
	char bulk_in_pipe;
	char bulk_out_pipe;
	int num_interrupt_in = 0;
	int num_bulk_in = 0;
	int num_bulk_out = 0;
	int num_ports;
	int max_endpoints;
	const struct usb_device_id *id_pattern = NULL;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
          /* Work out the number for this interface, since in 2.6 we are not
            just handed it by the kernel. */
        for (ifnum = 0; 1; ifnum++) {
            struct usb_interface* this_if = usb_ifnum_to_if(dev, ifnum);
            if (this_if == NULL) {
                err("Failed to determine ifnum!");
                return -ENODEV;
            }
            if (this_if == interface)
                break;
        }
#endif

	/* loop through our list of known serial converters, and see if this
	   device matches. */
	found = 0;
	list_for_each (tmp, &ipwireless_usb_driver_list) {
		type = list_entry(tmp, struct usb_serial_device_type, driver_list);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
		id_pattern = usb_match_id(interface, type->id_table);
#else
		id_pattern = usb_match_id(dev, interface, type->id_table);
#endif
		if (id_pattern != NULL) {
			dbg("descriptor matches");
			found = 1;
			break;
		}
	}
	if (!found) {
		/* no match */
		dbg("none matched");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
		return -ENODEV;
#else
		return NULL;
#endif
	}
          
	/* descriptor matches, let's find the endpoints needed */
	interrupt_pipe = bulk_in_pipe = bulk_out_pipe = HAS_NOT;
			
	/* check out the endpoints */
	iface_desc = &interface->altsetting[0];
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
	for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
		endpoint = &iface_desc->endpoint[i].desc;
#else
	for (i = 0; i < iface_desc->bNumEndpoints; ++i) {
		endpoint = &iface_desc->endpoint[i];
#endif
		
		if ((endpoint->bEndpointAddress & 0x80) &&
		    ((endpoint->bmAttributes & 3) == 0x02)) {
			/* we found a bulk in endpoint */
			dbg("found bulk in");
			bulk_in_pipe = HAS;
			bulk_in_endpoint[num_bulk_in] = endpoint;
			++num_bulk_in;
		}

		if (((endpoint->bEndpointAddress & 0x80) == 0x00) &&
		    ((endpoint->bmAttributes & 3) == 0x02)) {
			/* we found a bulk out endpoint */
			dbg("found bulk out");
			bulk_out_pipe = HAS;
			bulk_out_endpoint[num_bulk_out] = endpoint;
			++num_bulk_out;
		}
		
		if ((endpoint->bEndpointAddress & 0x80) &&
		    ((endpoint->bmAttributes & 3) == 0x03)) {
			/* we found a interrupt in endpoint */
			dbg("found interrupt in");
			interrupt_pipe = HAS;
			interrupt_in_endpoint[num_interrupt_in] = endpoint;
			++num_interrupt_in;
		}
	}
	
	/* verify that we found all of the endpoints that we need */
	if (!((interrupt_pipe & type->needs_interrupt_in) &&
	      (bulk_in_pipe & type->needs_bulk_in) &&
	      (bulk_out_pipe & type->needs_bulk_out))) {
		/* nope, they don't match what we expected */
		info("descriptors matched, but endpoints did not");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
		return -ENODEV;
#else
		return NULL;
#endif
	}

	/* found all that we need */
	/*info("%s converter detected", type->name);*/

        num_ports = num_bulk_out;
        if (num_ports != 1) {
                err("The device has %d bulk outs - should be 1!", num_ports);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
		return -EIO;
#else
		return NULL;
#endif
        }

	serial = get_free_serial (&minor, dev, ifnum);
	if (serial == NULL) {
		err("No more free ttyIPWu devices");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
		return -ENOMEM;
#else
		return NULL;
#endif
	}

	serial->dev = dev;
        serial->ifnum = ifnum;
	serial->type = type;
	serial->interface = interface;
	serial->minor = minor;
	serial->num_ports = num_ports;
	serial->num_bulk_in = num_bulk_in;
	serial->num_bulk_out = num_bulk_out;
	serial->num_interrupt_in = num_interrupt_in;
	serial->vendor = dev->descriptor.idVendor;
	serial->product = dev->descriptor.idProduct;

	/* if this device type has a startup function, call it */
/*        
	if (type->startup) {
		i = type->startup (serial);
		if (i < 0)
			goto probe_error;
		if (i > 0)
			return serial;
	}
        */

	/* set up the endpoint information */
	for (i = 0; i < num_bulk_in; ++i) {
		endpoint = bulk_in_endpoint[i];
		port = &serial->port[i];
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
		port->read_urb = usb_alloc_urb(0, GFP_KERNEL);
#else
		port->read_urb = usb_alloc_urb(0);
#endif
		if (!port->read_urb) {
			err("No free urbs available");
			goto probe_error;
		}
		buffer_size = endpoint->wMaxPacketSize;
		port->bulk_in_endpointAddress = endpoint->bEndpointAddress;
		port->bulk_in_buffer = kmalloc (buffer_size, GFP_KERNEL);
		if (!port->bulk_in_buffer) {
			err("Couldn't allocate bulk_in_buffer");
			goto probe_error;
		}
		IPW_FILL_BULK_URB(port->read_urb, dev, 
			      usb_rcvbulkpipe(dev, endpoint->bEndpointAddress),
			      port->bulk_in_buffer, buffer_size, 
			      generic_read_bulk_callback, 
			      port);
	}

	for (i = 0; i < num_bulk_out; ++i) {
		endpoint = bulk_out_endpoint[i];
		port = &serial->port[i];
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
		port->write_urb = usb_alloc_urb(0, GFP_KERNEL);
#else
		port->write_urb = usb_alloc_urb(0);
#endif
		if (!port->write_urb) {
			err("No free urbs available");
			goto probe_error;
		}
		buffer_size = endpoint->wMaxPacketSize;
		port->bulk_out_size = buffer_size;
		port->bulk_out_endpointAddress = endpoint->bEndpointAddress;
		port->bulk_out_buffer = kmalloc (buffer_size, GFP_KERNEL);
		if (!port->bulk_out_buffer) {
			err("Couldn't allocate bulk_out_buffer");
			goto probe_error;
		}
		IPW_FILL_BULK_URB(port->write_urb, dev, 
			      usb_sndbulkpipe(dev, endpoint->bEndpointAddress),
			      port->bulk_out_buffer, buffer_size,
			      generic_write_bulk_callback, 
			      port);
	}

	for (i = 0; i < num_interrupt_in; ++i) {
		endpoint = interrupt_in_endpoint[i];
		port = &serial->port[i];
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
		port->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL);
#else
		port->interrupt_in_urb = usb_alloc_urb(0);
#endif
		if (!port->interrupt_in_urb) {
			err("No free urbs available");
			goto probe_error;
		}
		buffer_size = endpoint->wMaxPacketSize;
		port->interrupt_in_endpointAddress = endpoint->bEndpointAddress;
		port->interrupt_in_buffer = kmalloc (buffer_size, GFP_KERNEL);
		if (!port->interrupt_in_buffer) {
			err("Couldn't allocate interrupt_in_buffer");
			goto probe_error;
		}
		IPW_FILL_INT_URB(port->interrupt_in_urb, dev, 
			     usb_rcvintpipe(dev, endpoint->bEndpointAddress),
			     port->interrupt_in_buffer, buffer_size, 
			     /*serial->type->read_int_callback*/NULL,
			     port, 
			     endpoint->bInterval);
	}

	/* initialize some parts of the port structures */
	/* we don't use num_ports here cauz some devices have more endpoint pairs than ports */
	max_endpoints = max(num_bulk_in, num_bulk_out);
	max_endpoints = max(max_endpoints, num_interrupt_in);
	max_endpoints = max(max_endpoints, (int)serial->num_ports);
	dbg (_DUMMY_FUNCTION_ " - setting up %d port structures for this device", max_endpoints);
	for (i = 0; i < max_endpoints; ++i) {
		port = &serial->port[i];
		port->number = i + serial->minor;
		port->serial = serial;
		port->magic = USB_SERIAL_PORT_MAGIC;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
		INIT_WORK(&port->work, usb_serial_port_softint, port);
#else
		port->tqueue.routine = usb_serial_port_softint;
		port->tqueue.data = port;
#endif
                port->control_lines = 0;
		init_MUTEX (&port->sem);
	}

	/* initialize the devfs nodes for this device and let the user know what ports we are bound to */
	for (i = 0; i < serial->num_ports; ++i) {
                char* iftype = iftype_name(ifnum);
#ifdef CONFIG_DEVFS_FS
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
		tty_register_devfs (&serial_tty_driver, 0, serial->port[i].number);
#endif
		info("allocated %s interface on ttyIPWu%d (or IPWu/%d for devfs)", 
		     iftype, serial->port[i].number, serial->port[i].number);
#else
		info("allocated %s interface on ttyIPWu%d", 
		     iftype, serial->port[i].number);
#endif
	}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
	/* success */
	usb_set_intfdata(interface, serial);
	return 0;
#else
	return serial; /* success */
#endif


probe_error:
	for (i = 0; i < num_bulk_in; ++i) {
		port = &serial->port[i];
		if (port->read_urb)
			usb_free_urb (port->read_urb);
		if (port->bulk_in_buffer)
			kfree (port->bulk_in_buffer);
	}
	for (i = 0; i < num_bulk_out; ++i) {
		port = &serial->port[i];
		if (port->write_urb)
			usb_free_urb (port->write_urb);
		if (port->bulk_out_buffer)
			kfree (port->bulk_out_buffer);
	}
	for (i = 0; i < num_interrupt_in; ++i) {
		port = &serial->port[i];
		if (port->interrupt_in_urb)
			usb_free_urb (port->interrupt_in_urb);
		if (port->interrupt_in_buffer)
			kfree (port->interrupt_in_buffer);
	}
		
	/* return the minor range that this device had */
	return_serial (serial);

	/* free up any memory that we allocated */
	kfree (serial);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
	return -EIO;
#else
	return NULL;
#endif
}


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
static void usb_serial_disconnect(struct usb_interface *interface)
{
	struct usb_serial *serial = usb_get_intfdata (interface);
#else
static void usb_serial_disconnect(struct usb_device *dev, void *ptr)
{
	struct usb_serial *serial = (struct usb_serial *) ptr;
#endif
	struct usb_serial_port *port;
	int i;

	if (serial) {
		/* fail all future close/read/write/ioctl/etc calls */
		for (i = 0; i < serial->num_ports; ++i) {
			if (serial->port[i].tty != NULL)
				serial->port[i].tty->driver_data = NULL;
		}

		serial->dev = NULL;
		serial_shutdown (serial);

		for (i = 0; i < serial->num_ports; ++i)
			serial->port[i].active = 0;

		for (i = 0; i < serial->num_bulk_in; ++i) {
			port = &serial->port[i];
			if (port->read_urb) {
				usb_unlink_urb (port->read_urb);
				usb_free_urb (port->read_urb);
			}
			if (port->bulk_in_buffer)
				kfree (port->bulk_in_buffer);
		}
		for (i = 0; i < serial->num_bulk_out; ++i) {
			port = &serial->port[i];
			if (port->write_urb) {
				usb_unlink_urb (port->write_urb);
				usb_free_urb (port->write_urb);
			}
			if (port->bulk_out_buffer)
				kfree (port->bulk_out_buffer);
		}
		for (i = 0; i < serial->num_interrupt_in; ++i) {
			port = &serial->port[i];
			if (port->interrupt_in_urb) {
				usb_unlink_urb (port->interrupt_in_urb);
				usb_free_urb (port->interrupt_in_urb);
			}
			if (port->interrupt_in_buffer)
				kfree (port->interrupt_in_buffer);
		}

		for (i = 0; i < serial->num_ports; ++i) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
			tty_unregister_devfs (&serial_tty_driver, serial->port[i].number);
#endif
			info("%s now disconnected from ttyIPWu%d", serial->type->name, serial->port[i].number);
		}

		/* return the minor range that this device had */
		return_serial (serial);

		/* free up any memory that we allocated */
		kfree (serial);

	} else {
		info("device disconnected");
	}
	
}


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
static struct tty_operations serial_ops = {
	.open =			serial_open,
	.close =		serial_close,
	.write =		serial_write,
	.write_room =		serial_write_room,
	.ioctl =		serial_ioctl,
	.set_termios =		serial_set_termios,
	.throttle =		serial_throttle,
	.unthrottle =		serial_unthrottle,
	.break_ctl =		serial_break,
	.chars_in_buffer =	serial_chars_in_buffer,
/*
	.read_proc =		serial_read_proc,
        */
	.tiocmget =		serial_tiocmget,
	.tiocmset =		serial_tiocmset,
};

struct tty_driver *usb_serial_tty_driver;


/*
struct bus_type usb_serial_bus_type = {
	.name =		"ipwireless_usb",
	.match =	usb_serial_device_match,
};
*/

#else
static struct tty_driver serial_tty_driver = {
	magic:			TTY_DRIVER_MAGIC,
	driver_name:		"ipwireless_usb",
	name:			"ttyIPWu",
	major:			0,  /* Major number is automatically allocated. */
	minor_start:		0,
	num:			IPWIRELESS_USB_MINORS,
	type:			TTY_DRIVER_TYPE_SERIAL,
	subtype:		SERIAL_TYPE_NORMAL,
	flags:			TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS,

	refcount:		&serial_refcount,
	table:			serial_tty,
	termios:		serial_termios,
	termios_locked:		serial_termios_locked,
	
	open:			serial_open,
	close:			serial_close,
	write:			serial_write,
	write_room:		serial_write_room,
	ioctl:			serial_ioctl,
	set_termios:		serial_set_termios,
	throttle:		serial_throttle,
	unthrottle:		serial_unthrottle,
	break_ctl:		serial_break,
	chars_in_buffer:	serial_chars_in_buffer,
};
#endif

static int proc_read(char* page, char **start, off_t off, int count, int* eof, void* data)
{
    int i;
    char* p = page;
    int len;

    if (off == 0) {
        p += sprintf(p, "driver: %s\nversion: %s\ntype: vendor\n\n", DRIVER_DESC, DRIVER_VERSION);
        for (i = 0; i < IPWIRELESS_USB_MINORS; i ++)
            if (serial_table[i] != NULL)
                p += sprintf(p, "port: /dev/ttyIPWu%d %s %d\n", i, iftype_name(serial_table[i]->ifnum),
                   serial_table[i]->port[0].open_count);
    }

    len = (p - page);
    if (len <= off+count)
        *eof = 1;
    *start = page + off;
    len -= off;
    if (len > count)
        len = count;
    if (len < 0)
        len = 0;
    return len;
}

static int __init ipwireless_usb_init(void)
{
	int i;
	int result;

	/* Initalize our global data */
	for (i = 0; i < IPWIRELESS_USB_MINORS; ++i) {
		serial_table[i] = NULL;
	}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
	usb_serial_tty_driver = alloc_tty_driver(IPWIRELESS_USB_MINORS);
	if (!usb_serial_tty_driver)
		return -ENOMEM;

        /*bus_register(&usb_serial_bus_type);*/

	usb_serial_tty_driver->owner = THIS_MODULE;
	usb_serial_tty_driver->driver_name = "ipwireless_usb";
	usb_serial_tty_driver->devfs_name = "IPWu/";
	usb_serial_tty_driver->name = 	"ttyIPWu";
	usb_serial_tty_driver->major = 0;  /* Major number is automatically allocated. */
	usb_serial_tty_driver->minor_start = 0;
	usb_serial_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
	usb_serial_tty_driver->subtype = SERIAL_TYPE_NORMAL;
	usb_serial_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS;
	usb_serial_tty_driver->init_termios = tty_std_termios;
	usb_serial_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
	tty_set_operations(usb_serial_tty_driver, &serial_ops);
	result = tty_register_driver(usb_serial_tty_driver);
	if (result) {
		err("%s - tty_register_driver failed", __FUNCTION__);
                put_tty_driver(usb_serial_tty_driver);
		return result;
	}
#else
	/* register the tty driver */
	serial_tty_driver.init_termios          = tty_std_termios;
	serial_tty_driver.init_termios.c_cflag  = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
        result = tty_register_driver(&serial_tty_driver);
	if (result < 0) {
		err(_DUMMY_FUNCTION_ " - failed to register tty driver");
		return -1;
	}
#endif

	info(DRIVER_DESC " " DRIVER_VERSION " installed with major number %d",
          (int)serial_tty_driver.major);

#ifdef CONFIG_DEVFS_FS
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
        devfs_mk_dir(NULL, "IPWu", NULL);
#endif
#endif

          /* The device has a different PRODUCT_ID if it is in its two-port mode. */
	generic_device_ids[0].idVendor = IPWIRELESS_USB_VENDOR_ID;
	generic_device_ids[0].idProduct = IPWIRELESS_USB_PRODUCT_ID1;
	generic_device_ids[0].match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT;
	generic_device_ids[1].idVendor = IPWIRELESS_USB_VENDOR_ID;
	generic_device_ids[1].idProduct = IPWIRELESS_USB_PRODUCT_ID2;
	generic_device_ids[1].match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT;

	/* register our generic driver with ourselves */
	usb_serial_register (&generic_device);

	/* register the USB driver */
	result = usb_register(&ipwireless_usb_driver);
	if (result < 0) {
                usb_serial_deregister(&generic_device);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
                put_tty_driver(usb_serial_tty_driver);
#else
		tty_unregister_driver(&serial_tty_driver);
#endif
		err("usb_register failed for the usb-serial driver. Error number %d", result);
		return -1;
	}

        {
            struct proc_dir_entry* proc_ent;
            proc_ent = create_proc_entry("ipwireless_usb", S_IRUGO, &proc_root);
            if (proc_ent != NULL)
                proc_ent->read_proc = proc_read;
        }

	return 0;
}

static void __exit ipwireless_usb_exit(void)
{
        remove_proc_entry("ipwireless_usb", &proc_root);

	/* remove our generic driver */
        usb_serial_deregister(&generic_device);
        
        usb_deregister(&ipwireless_usb_driver);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
	tty_unregister_driver(usb_serial_tty_driver);
	put_tty_driver(usb_serial_tty_driver);
	/*bus_unregister(&usb_serial_bus_type);*/
#else
        tty_unregister_driver(&serial_tty_driver);
#endif
}


module_init(ipwireless_usb_init);
module_exit(ipwireless_usb_exit);


int usb_serial_register(struct usb_serial_device_type *new_device)
{
	/* Add this device to our list of devices */
	list_add(&new_device->driver_list, &ipwireless_usb_driver_list);

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
	usb_scan_devices();
#endif

	return 0;
}


void usb_serial_deregister(struct usb_serial_device_type *device)
{
	struct usb_serial *serial;
	int i;

	/* clear out the serial_table if the device is attached to a port */
	for(i = 0; i < IPWIRELESS_USB_MINORS; ++i) {
		serial = serial_table[i];
		if ((serial != NULL) && (serial->type == device)) {
			usb_driver_release_interface (&ipwireless_usb_driver, serial->interface);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
			usb_serial_disconnect (serial->interface);
#else
			usb_serial_disconnect (NULL, serial);
#endif
		}
	}

	list_del(&device->driver_list);
}




/* Module information */
MODULE_AUTHOR( DRIVER_AUTHOR );
MODULE_DESCRIPTION( DRIVER_DESC );
MODULE_LICENSE("GPL");

MODULE_PARM(debug, "i");
MODULE_PARM_DESC(debug, "Debug enabled or not");

