/* hacked version which runs on my 2.4.4-pre6 kernel, please * ask Al Borchers for a real * version of this file. * Bernhard Reiter */ /* * USB ZyXEL omni 56K plus driver * * 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. * * See Documentation/usb/usb-serial.txt for more information on using this driver * * Please report both successes and troubles to the author at omninet@kroah.com * * (10/05/2000) gkh * Fixed bug with urb->dev not being set properly, now that the usb * core needs it. * * (08/28/2000) gkh * Added locks for SMP safeness. * Fixed MOD_INC and MOD_DEC logic and the ability to open a port more * than once. * Fixed potential race in omninet_write_bulk_callback * * (07/19/2000) gkh * Added module_init and module_exit functions to handle the fact that this * driver is a loadable module now. * * Notes: * * The Omni USB protocol always exchanges 64 bytes of data with the host. * The first four bytes are the header, described by the omninet_header * structure. * * struct omninet_header { * __u8 oh_sequence; * __u8 oh_length; * __u8 oh_modem_status; * __u8 oh_reserved; * }; * * oh_sequence is a USB packet sequence number. * oh_length is the length of the data bytes in the packet. * oh_modem_status is the modem status bit flags. Bits have the following * meanings: * * 7: CD, from modem * 5: DSR, from modem * 4: CTS, from modem * 1: RTS, to modem * 0: DTR, to modem * * After the header are oh_length data bytes, then pad bytes to fill * the packet to the full 64 bytes. * * These devices have five endpoints including the control endpoint. * However, endpoints 1 (bulk out) and 2 (interrupt in), the first two * endpoints after the control endpoint, are not used. This complicates * things, since the bulk out endpoint that is used is associated with * the second (non-existent) serial port by the usb serial subsystem. * This driver fixes this by replacing the bulk out urb for the first * serial port with a bulk out urb addressed to the 3rd endpoint, the * correct bulk out endpoint to send data to the modem. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_USB_SERIAL_DEBUG static int debug = 1; #else static int debug; #endif #include "usb-serial.h" #define ZYXEL_VENDOR_ID 0x0586 /* #define ZYXEL_OMNINET_ID 0x1500 */ #define ZYXEL_OMNINET_ID 0x1000 #define ZYXEL_DTR 0x0001 #define ZYXEL_RTS 0x0002 #define ZYXEL_CTS 0x0010 #define ZYXEL_DSR 0x0020 #define ZYXEL_CD 0x0080 #define ZYXEL_READY 0x0030 #define ZYXEL_READ 0x00fc #define ZYXEL_WRITE 0x0003 #define ZYXEL_PKTSIZE 64 /* function prototypes */ static int omninet_open (struct usb_serial_port *port, struct file *filp); static void omninet_close (struct usb_serial_port *port, struct file *filp); static void omninet_read_bulk_callback (struct urb *urb); static void omninet_write_bulk_callback (struct urb *urb); static int omninet_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count); static int omninet_write_room (struct usb_serial_port *port); static void omninet_rx_throttle (struct usb_serial_port *port); static void omninet_rx_unthrottle (struct usb_serial_port *port); static int omninet_ioctl (struct usb_serial_port *port, struct file *file, unsigned int cmd, unsigned long arg); static int omninet_convert_tiocm_to_zyxel (int status); static int omninet_convert_zyxel_to_tiocm (int status); static int omninet_startup (struct usb_serial *serial); static void omninet_shutdown (struct usb_serial *serial); /* All of the device info needed for the omni plus */ /*static __u16 zyxel_vendor_id = ZYXEL_VENDOR_ID; static __u16 zyxel_omninet_product_id = ZYXEL_OMNINET_ID; */ static __devinitdata struct usb_device_id id_table [] = { { USB_DEVICE(ZYXEL_VENDOR_ID, ZYXEL_OMNINET_ID) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE (usb, id_table); struct usb_serial_device_type zyxel_omninet_device = { name: "ZyXEL - omni 56k plus usb", id_table: id_table, needs_interrupt_in: MUST_HAVE, needs_bulk_in: MUST_HAVE, needs_bulk_out: MUST_HAVE, num_interrupt_in: 1, num_bulk_in: 1, num_bulk_out: 2, num_ports: 1, open: omninet_open, close: omninet_close, write: omninet_write, write_room: omninet_write_room, read_bulk_callback: omninet_read_bulk_callback, write_bulk_callback: omninet_write_bulk_callback, throttle: omninet_rx_throttle, unthrottle: omninet_rx_unthrottle, ioctl: omninet_ioctl, startup: omninet_startup, shutdown: omninet_shutdown, }; struct omninet_header { __u8 oh_sequence; __u8 oh_length; __u8 oh_modem_status; __u8 oh_reserved; }; struct omninet_data { __u8 od_out_sequence; // sequence number for bulk_out URBs __u8 od_modem_status; // modem status }; static int data_offset = sizeof(struct omninet_header); static int data_size = ZYXEL_PKTSIZE - sizeof(struct omninet_header); static int omninet_startup (struct usb_serial *serial) { struct usb_serial_port *port; struct omninet_data *od; if ((serial->port[0].private = kmalloc (sizeof(struct omninet_data), GFP_KERNEL)) == NULL) { err(__FUNCTION__ " - kmalloc(%d) failed.", sizeof(struct omninet_data)); return -ENOMEM; } port = &serial->port[0]; od = port->private; od->od_out_sequence = 0; od->od_modem_status = ZYXEL_READY; return (0); } static void omninet_shutdown (struct usb_serial *serial) { dbg (__FUNCTION__); while (serial->port[0].open_count > 0) { omninet_close (&serial->port[0], NULL); } if (serial->port[0].private) kfree (serial->port[0].private); } static int omninet_open (struct usb_serial_port *port, struct file *filp) { struct usb_serial *serial; struct usb_serial_port *wport; struct omninet_data *od; unsigned long flags; int result; if (port_paranoia_check (port, __FUNCTION__)) return -ENODEV; dbg (__FUNCTION__ " - port %d", port->number); serial = get_usb_serial (port, __FUNCTION__); if (!serial) return -ENODEV; spin_lock_irqsave (&port->port_lock, flags); MOD_INC_USE_COUNT; ++port->open_count; if (!port->active) { port->active = 1; wport = &serial->port[1]; wport->tty = port->tty; /* Start reading from the device */ 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, omninet_read_bulk_callback, port); result = usb_submit_urb (port->read_urb); if (result) err (__FUNCTION__ " - failed submitting read urb, error %d", result); if (port->tty->termios->c_cflag & CBAUD) { od = port->private; od->od_modem_status |= (ZYXEL_DTR|ZYXEL_RTS); spin_unlock_irqrestore (&port->port_lock, flags); omninet_write (port, 0, 0, 0); spin_lock_irqsave (&port->port_lock, flags); } } spin_unlock_irqrestore (&port->port_lock, flags); return (0); } static void omninet_close (struct usb_serial_port *port, struct file *filp) { struct usb_serial *serial; struct usb_serial_port *wport; struct omninet_data *od; unsigned long flags; if (port_paranoia_check (port, __FUNCTION__)) return; dbg (__FUNCTION__ " - port %d", port->number); serial = get_usb_serial (port, __FUNCTION__); if (!serial) return; spin_lock_irqsave (&port->port_lock, flags); --port->open_count; if (port->open_count <= 0) { if (port->tty->termios->c_cflag & HUPCL) { od = port->private; od->od_modem_status &= ~(ZYXEL_DTR|ZYXEL_RTS); spin_unlock_irqrestore (&port->port_lock, flags); omninet_write (port, 0, 0, 0); spin_lock_irqsave (&port->port_lock, flags); } wport = &serial->port[1]; usb_unlink_urb (wport->write_urb); usb_unlink_urb (port->read_urb); port->active = 0; port->open_count = 0; } MOD_DEC_USE_COUNT; spin_unlock_irqrestore (&port->port_lock, flags); } static void omninet_read_bulk_callback (struct urb *urb) { struct usb_serial_port *port = (struct usb_serial_port *) urb->context; struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); unsigned char *data = urb->transfer_buffer; struct omninet_header *header = (struct omninet_header *) &data[0]; struct omninet_data *od = (struct omninet_data *) port->private; int i; int result; if (!serial) { dbg (__FUNCTION__ " - bad serial pointer, exiting"); return; } if (urb->status) { dbg (__FUNCTION__ " - nonzero read bulk status received: %d", urb->status); return; } spin_lock (&port->port_lock); /* if CD is dropped and the line is not CLOCAL then we should hangup */ if (!(header->oh_modem_status & ZYXEL_CD) && (od->od_modem_status & ZYXEL_CD) && port->tty && !C_CLOCAL(port->tty)) tty_hangup(port->tty); od->od_modem_status = (header->oh_modem_status & ZYXEL_READ) | (od->od_modem_status & ZYXEL_WRITE); if (od->od_modem_status & ZYXEL_CTS) { /* port must be open to use tty struct */ if (port->active && (port->tty->termios->c_cflag & CRTSCTS) && port->tty->hw_stopped) { port->tty->hw_stopped = 0; queue_task (&port->tqueue, &tq_immediate); mark_bh (IMMEDIATE_BH); dbg (__FUNCTION__ " - unstopped"); } } else { /* port must be open to use tty struct */ if (port->active && (port->tty->termios->c_cflag & CRTSCTS)) { port->tty->hw_stopped = 1; dbg (__FUNCTION__" - stopped"); } } spin_unlock (&port->port_lock); if (urb->actual_length && header->oh_length) { for (i = 0; i < header->oh_length; i++) { tty_insert_flip_char (port->tty, data[data_offset + i], 0); } tty_flip_buffer_push (port->tty); } /* Continue trying to always read */ FILL_BULK_URB (urb, serial->dev, usb_rcvbulkpipe(serial->dev, port->bulk_in_endpointAddress), urb->transfer_buffer, urb->transfer_buffer_length, omninet_read_bulk_callback, port); result = usb_submit_urb (urb); if (result) err(__FUNCTION__ " - failed resubmitting read urb, error %d", result); return; } static int omninet_write (struct usb_serial_port *port, int from_user, const unsigned char *buf, int count) { struct usb_serial *serial = port->serial; struct usb_serial_port *wport = &serial->port[1]; struct omninet_header *header = (struct omninet_header *) wport->write_urb->transfer_buffer; struct omninet_data *od = (struct omninet_data *) port->private; unsigned long flags; int result; if (count == 0) { dbg (__FUNCTION__" - write request of 0 bytes"); } if (port->active && (port->tty->hw_stopped || port->tty->stopped)) return (0); if (wport->write_urb->status == -EINPROGRESS) { dbg (__FUNCTION__" - already writing"); return (0); } usb_serial_debug_data (__FILE__, __FUNCTION__, count, buf); spin_lock_irqsave (&port->port_lock, flags); count = (count > data_size) ? data_size : count; if (from_user) copy_from_user (wport->write_urb->transfer_buffer + data_offset, buf, count); else memcpy (wport->write_urb->transfer_buffer + data_offset, buf, count); header->oh_sequence = od->od_out_sequence++; header->oh_length = count; header->oh_modem_status = od->od_modem_status & ZYXEL_WRITE; header->oh_reserved = 0x00; /* send the data out the bulk port */ wport->write_urb->transfer_buffer_length = ZYXEL_PKTSIZE; wport->write_urb->dev = serial->dev; result = usb_submit_urb (wport->write_urb); if (result) { err(__FUNCTION__ " - failed submitting write urb, error %d", result); spin_unlock_irqrestore (&port->port_lock, flags); return (0); } spin_unlock_irqrestore (&port->port_lock, flags); return (count); } static int omninet_write_room (struct usb_serial_port *port) { struct usb_serial *serial = port->serial; struct usb_serial_port *wport = &serial->port[1]; int room = 0; if (wport->write_urb->status != -EINPROGRESS) room = data_size; return (room); } static void omninet_write_bulk_callback (struct urb *urb) { struct usb_serial_port *port = (struct usb_serial_port *) urb->context; struct usb_serial *serial; if (port_paranoia_check (port, __FUNCTION__)) { return; } serial = port->serial; if (serial_paranoia_check (serial, __FUNCTION__)) { return; } if (urb->status) { dbg (__FUNCTION__" - nonzero write bulk status received: %d", urb->status); return; } spin_lock (&port->port_lock); queue_task (&port->tqueue, &tq_immediate); mark_bh (IMMEDIATE_BH); spin_unlock (&port->port_lock); return; } static void omninet_rx_throttle (struct usb_serial_port *port) { unsigned long flags; struct omninet_data *od = (struct omninet_data *) port->private; dbg(__FUNCTION__ " - port=%d", port->number); if (!port->active) return; spin_lock_irqsave (&port->port_lock, flags); od->od_modem_status &= ~ZYXEL_RTS; spin_unlock_irqrestore (&port->port_lock, flags); omninet_write (port, 0, 0, 0); } static void omninet_rx_unthrottle (struct usb_serial_port *port) { unsigned long flags; struct omninet_data *od = (struct omninet_data *) port->private; dbg(__FUNCTION__ " - port=%d", port->number); if (!port->active) return; spin_lock_irqsave (&port->port_lock, flags); od->od_modem_status |= ZYXEL_RTS; spin_unlock_irqrestore (&port->port_lock, flags); omninet_write (port, 0, 0, 0); } static int omninet_ioctl (struct usb_serial_port *port, struct file *file, unsigned int cmd, unsigned long arg) { struct usb_serial *serial = get_usb_serial (port, __FUNCTION__); struct omninet_data *od = (struct omninet_data *) port->private; unsigned long flags; unsigned int value; unsigned char status; dbg (__FUNCTION__ " - port=%d, cmd=0x%x", port->number, cmd); if (!serial) return -ENODEV; switch (cmd) { case TIOCMGET: spin_lock_irqsave (&port->port_lock, flags); value = omninet_convert_zyxel_to_tiocm (od->od_modem_status); spin_unlock_irqrestore (&port->port_lock, flags); if (copy_to_user ((unsigned int *) arg, &value, sizeof(int))) return -EFAULT; return (0); case TIOCMSET: case TIOCMBIS: case TIOCMBIC: if (copy_from_user (&value, (unsigned int *) arg, sizeof(int))) return -EFAULT; status = omninet_convert_tiocm_to_zyxel (value); spin_lock_irqsave (&port->port_lock, flags); if (cmd == TIOCMBIS) od->od_modem_status |= status; else if (cmd == TIOCMBIC) od->od_modem_status &= ~status; spin_unlock_irqrestore (&port->port_lock, flags); omninet_write(port, 0, 0, 0); return (0); case TIOCMIWAIT: /* wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) */ /* TODO */ return (0); case TIOCGICOUNT: /* return count of modemline transitions */ /* TODO */ return (0); } return -ENOIOCTLCMD; } static int omninet_convert_tiocm_to_zyxel (int status) { return ((status & TIOCM_DTR) ? ZYXEL_DTR : 0) | ((status & TIOCM_RTS) ? ZYXEL_RTS : 0) | ((status & TIOCM_CTS) ? ZYXEL_CTS : 0) | ((status & TIOCM_DSR) ? ZYXEL_DSR : 0) | ((status & TIOCM_CD) ? ZYXEL_CD : 0); } static int omninet_convert_zyxel_to_tiocm (int status) { return ((status & ZYXEL_DTR) ? TIOCM_DTR : 0) | ((status & ZYXEL_RTS) ? TIOCM_RTS : 0) | ((status & ZYXEL_CTS) ? TIOCM_CTS : 0) | ((status & ZYXEL_DSR) ? TIOCM_DSR : 0) | ((status & ZYXEL_CD) ? TIOCM_CD : 0); } static int __init omninet_init (void) { usb_serial_register (&zyxel_omninet_device); return (0); } static void __exit omninet_exit (void) { usb_serial_deregister (&zyxel_omninet_device); } module_init (omninet_init); module_exit (omninet_exit); MODULE_DESCRIPTION ("USB ZyXEL omni 56K plus driver");