omninet.c 10.3 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3
/*
 * USB ZyXEL omni.net LCD PLUS driver
 *
4 5 6
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License version
 *	2 as published by the Free Software Foundation.
Linus Torvalds's avatar
Linus Torvalds committed
7
 *
Alan Cox's avatar
Alan Cox committed
8 9
 * See Documentation/usb/usb-serial.txt for more information on using this
 * driver
Linus Torvalds's avatar
Linus Torvalds committed
10 11
 *
 * Please report both successes and troubles to the author at omninet@kroah.com
Alan Cox's avatar
Alan Cox committed
12
 *
Linus Torvalds's avatar
Linus Torvalds committed
13
 * (05/30/2001) gkh
Alan Cox's avatar
Alan Cox committed
14 15
 *	switched from using spinlock to a semaphore, which fixes lots of
 *	problems.
Linus Torvalds's avatar
Linus Torvalds committed
16 17 18 19 20 21
 *
 * (04/08/2001) gb
 *	Identify version on module load.
 *
 * (11/01/2000) Adam J. Richter
 *	usb_device_id table support
Alan Cox's avatar
Alan Cox committed
22
 *
Linus Torvalds's avatar
Linus Torvalds committed
23 24 25
 * (10/05/2000) gkh
 *	Fixed bug with urb->dev not being set properly, now that the usb
 *	core needs it.
Alan Cox's avatar
Alan Cox committed
26
 *
Linus Torvalds's avatar
Linus Torvalds committed
27 28
 * (08/28/2000) gkh
 *	Added locks for SMP safeness.
Alan Cox's avatar
Alan Cox committed
29
 *	Fixed MOD_INC and MOD_DEC logic and the ability to open a port more
Linus Torvalds's avatar
Linus Torvalds committed
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
 *	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.
 *
 */

#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>
Alan Cox's avatar
Alan Cox committed
48
#include <linux/uaccess.h>
Linus Torvalds's avatar
Linus Torvalds committed
49
#include <linux/usb.h>
50
#include <linux/usb/serial.h>
Linus Torvalds's avatar
Linus Torvalds committed
51 52 53 54 55 56 57 58 59 60 61 62

static int debug;

/*
 * Version Information
 */
#define DRIVER_VERSION "v1.1"
#define DRIVER_AUTHOR "Alessandro Zummo"
#define DRIVER_DESC "USB ZyXEL omni.net LCD PLUS Driver"

#define ZYXEL_VENDOR_ID		0x0586
#define ZYXEL_OMNINET_ID	0x1000
Alan Cox's avatar
Alan Cox committed
63 64
/* This one seems to be a re-branded ZyXEL device */
#define BT_IGNITIONPRO_ID	0x2000
Linus Torvalds's avatar
Linus Torvalds committed
65 66

/* function prototypes */
67
static int  omninet_open(struct tty_struct *tty, struct usb_serial_port *port);
68
static void omninet_close(struct usb_serial_port *port);
Alan Cox's avatar
Alan Cox committed
69 70 71 72 73
static void omninet_read_bulk_callback(struct urb *urb);
static void omninet_write_bulk_callback(struct urb *urb);
static int  omninet_write(struct tty_struct *tty, struct usb_serial_port *port,
				const unsigned char *buf, int count);
static int  omninet_write_room(struct tty_struct *tty);
74 75
static void omninet_disconnect(struct usb_serial *serial);
static void omninet_release(struct usb_serial *serial);
Alan Cox's avatar
Alan Cox committed
76 77 78
static int omninet_attach(struct usb_serial *serial);

static struct usb_device_id id_table[] = {
Linus Torvalds's avatar
Linus Torvalds committed
79 80 81 82 83
	{ USB_DEVICE(ZYXEL_VENDOR_ID, ZYXEL_OMNINET_ID) },
	{ USB_DEVICE(ZYXEL_VENDOR_ID, BT_IGNITIONPRO_ID) },
	{ }						/* Terminating entry */
};

Alan Cox's avatar
Alan Cox committed
84
MODULE_DEVICE_TABLE(usb, id_table);
Linus Torvalds's avatar
Linus Torvalds committed
85 86 87 88 89 90

static struct usb_driver omninet_driver = {
	.name =		"omninet",
	.probe =	usb_serial_probe,
	.disconnect =	usb_serial_disconnect,
	.id_table =	id_table,
91
	.no_dynamic_id = 	1,
Linus Torvalds's avatar
Linus Torvalds committed
92 93 94
};


95
static struct usb_serial_driver zyxel_omninet_device = {
96 97
	.driver = {
		.owner =	THIS_MODULE,
98
		.name =		"omninet",
99
	},
100
	.description =		"ZyXEL - omni.net lcd plus usb",
101
	.usb_driver =		&omninet_driver,
Linus Torvalds's avatar
Linus Torvalds committed
102 103
	.id_table =		id_table,
	.num_ports =		1,
104
	.attach =		omninet_attach,
Linus Torvalds's avatar
Linus Torvalds committed
105 106 107 108 109 110
	.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,
111 112
	.disconnect =		omninet_disconnect,
	.release =		omninet_release,
Linus Torvalds's avatar
Linus Torvalds committed
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
};


/* The protocol.
 *
 * The omni.net always exchange 64 bytes of data with the host. The first
 * four bytes are the control header, you can see it in the above structure.
 *
 * oh_seq is a sequence number. Don't know if/how it's used.
 * oh_len is the length of the data bytes in the packet.
 * oh_xxx Bit-mapped, related to handshaking and status info.
 *	I normally set it to 0x03 in trasmitted frames.
 *	7: Active when the TA is in a CONNECTed state.
 *	6: unknown
 *	5: handshaking, unknown
 *	4: handshaking, unknown
 *	3: unknown, usually 0
 *	2: unknown, usually 0
 *	1: handshaking, unknown, usually set to 1 in trasmitted frames
 *	0: handshaking, unknown, usually set to 1 in trasmitted frames
 * oh_pad Probably a pad byte.
 *
 * After the header you will find data bytes if oh_len was greater than zero.
 *
 */

Alan Cox's avatar
Alan Cox committed
139
struct omninet_header {
Linus Torvalds's avatar
Linus Torvalds committed
140 141 142 143 144 145
	__u8	oh_seq;
	__u8	oh_len;
	__u8	oh_xxx;
	__u8	oh_pad;
};

Alan Cox's avatar
Alan Cox committed
146 147
struct omninet_data {
	__u8	od_outseq;	/* Sequence number for bulk_out URBs */
Linus Torvalds's avatar
Linus Torvalds committed
148 149
};

Alan Cox's avatar
Alan Cox committed
150
static int omninet_attach(struct usb_serial *serial)
151 152 153 154
{
	struct omninet_data *od;
	struct usb_serial_port *port = serial->port[0];

Alan Cox's avatar
Alan Cox committed
155 156
	od = kmalloc(sizeof(struct omninet_data), GFP_KERNEL);
	if (!od) {
157 158
		dev_err(&port->dev, "%s- kmalloc(%Zd) failed.\n",
			__func__, sizeof(struct omninet_data));
159 160 161 162 163 164
		return -ENOMEM;
	}
	usb_set_serial_port_data(port, od);
	return 0;
}

165
static int omninet_open(struct tty_struct *tty, struct usb_serial_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
166 167 168 169 170
{
	struct usb_serial	*serial = port->serial;
	struct usb_serial_port	*wport;
	int			result = 0;

171
	dbg("%s - port %d", __func__, port->number);
Linus Torvalds's avatar
Linus Torvalds committed
172 173

	wport = serial->port[1];
Alan Cox's avatar
Alan Cox committed
174
	tty_port_tty_set(&wport->port, tty);
Linus Torvalds's avatar
Linus Torvalds committed
175 176

	/* Start reading from the device */
Alan Cox's avatar
Alan Cox committed
177 178 179 180 181 182
	usb_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);
Linus Torvalds's avatar
Linus Torvalds committed
183
	result = usb_submit_urb(port->read_urb, GFP_KERNEL);
Alan Cox's avatar
Alan Cox committed
184
	if (result)
185 186 187
		dev_err(&port->dev,
			"%s - failed submitting read urb, error %d\n",
			__func__, result);
Linus Torvalds's avatar
Linus Torvalds committed
188 189 190
	return result;
}

191
static void omninet_close(struct usb_serial_port *port)
Linus Torvalds's avatar
Linus Torvalds committed
192
{
193
	dbg("%s - port %d", __func__, port->number);
Linus Torvalds's avatar
Linus Torvalds committed
194 195 196 197 198 199 200 201
	usb_kill_urb(port->read_urb);
}


#define OMNINET_DATAOFFSET	0x04
#define OMNINET_HEADERLEN	sizeof(struct omninet_header)
#define OMNINET_BULKOUTSIZE 	(64 - OMNINET_HEADERLEN)

Alan Cox's avatar
Alan Cox committed
202
static void omninet_read_bulk_callback(struct urb *urb)
Linus Torvalds's avatar
Linus Torvalds committed
203
{
204
	struct usb_serial_port 	*port 	= urb->context;
Linus Torvalds's avatar
Linus Torvalds committed
205 206
	unsigned char 		*data 	= urb->transfer_buffer;
	struct omninet_header 	*header = (struct omninet_header *) &data[0];
207
	int status = urb->status;
Linus Torvalds's avatar
Linus Torvalds committed
208
	int result;
Alan Cox's avatar
Alan Cox committed
209
	int i;
Linus Torvalds's avatar
Linus Torvalds committed
210

211
	dbg("%s - port %d", __func__, port->number);
Linus Torvalds's avatar
Linus Torvalds committed
212

213 214
	if (status) {
		dbg("%s - nonzero read bulk status received: %d",
215
		    __func__, status);
Linus Torvalds's avatar
Linus Torvalds committed
216 217 218
		return;
	}

Alan Cox's avatar
Alan Cox committed
219
	if (debug && header->oh_xxx != 0x30) {
Linus Torvalds's avatar
Linus Torvalds committed
220
		if (urb->actual_length) {
Alan Cox's avatar
Alan Cox committed
221 222 223 224 225 226
			printk(KERN_DEBUG __FILE__
					": omninet_read %d: ", header->oh_len);
			for (i = 0; i < (header->oh_len +
						OMNINET_HEADERLEN); i++)
				printk("%.2x ", data[i]);
			printk("\n");
Linus Torvalds's avatar
Linus Torvalds committed
227 228 229 230
		}
	}

	if (urb->actual_length && header->oh_len) {
Alan Cox's avatar
Alan Cox committed
231 232 233 234 235
		struct tty_struct *tty = tty_port_tty_get(&port->port);
		tty_insert_flip_string(tty, data + OMNINET_DATAOFFSET,
							header->oh_len);
		tty_flip_buffer_push(tty);
		tty_kref_put(tty);
Linus Torvalds's avatar
Linus Torvalds committed
236 237 238
	}

	/* Continue trying to always read  */
Alan Cox's avatar
Alan Cox committed
239 240 241 242 243
	usb_fill_bulk_urb(urb, port->serial->dev,
			usb_rcvbulkpipe(port->serial->dev,
					port->bulk_in_endpointAddress),
			urb->transfer_buffer, urb->transfer_buffer_length,
			omninet_read_bulk_callback, port);
Linus Torvalds's avatar
Linus Torvalds committed
244 245
	result = usb_submit_urb(urb, GFP_ATOMIC);
	if (result)
246 247 248
		dev_err(&port->dev,
			"%s - failed resubmitting read urb, error %d\n",
			__func__, result);
Linus Torvalds's avatar
Linus Torvalds committed
249 250 251 252

	return;
}

Alan Cox's avatar
Alan Cox committed
253 254
static int omninet_write(struct tty_struct *tty, struct usb_serial_port *port,
					const unsigned char *buf, int count)
Linus Torvalds's avatar
Linus Torvalds committed
255
{
Alan Cox's avatar
Alan Cox committed
256 257
	struct usb_serial *serial = port->serial;
	struct usb_serial_port *wport = serial->port[1];
Linus Torvalds's avatar
Linus Torvalds committed
258

Alan Cox's avatar
Alan Cox committed
259 260 261
	struct omninet_data *od = usb_get_serial_port_data(port);
	struct omninet_header *header = (struct omninet_header *)
					wport->write_urb->transfer_buffer;
Linus Torvalds's avatar
Linus Torvalds committed
262 263 264

	int			result;

265
	dbg("%s - port %d", __func__, port->number);
Linus Torvalds's avatar
Linus Torvalds committed
266 267

	if (count == 0) {
268
		dbg("%s - write request of 0 bytes", __func__);
Alan Cox's avatar
Alan Cox committed
269
		return 0;
Linus Torvalds's avatar
Linus Torvalds committed
270
	}
271

272
	spin_lock_bh(&wport->lock);
273
	if (wport->write_urb_busy) {
274
		spin_unlock_bh(&wport->lock);
275
		dbg("%s - already writing", __func__);
276
		return 0;
Linus Torvalds's avatar
Linus Torvalds committed
277
	}
278
	wport->write_urb_busy = 1;
279
	spin_unlock_bh(&wport->lock);
Linus Torvalds's avatar
Linus Torvalds committed
280 281 282

	count = (count > OMNINET_BULKOUTSIZE) ? OMNINET_BULKOUTSIZE : count;

Alan Cox's avatar
Alan Cox committed
283 284
	memcpy(wport->write_urb->transfer_buffer + OMNINET_DATAOFFSET,
								buf, count);
Linus Torvalds's avatar
Linus Torvalds committed
285

Alan Cox's avatar
Alan Cox committed
286 287
	usb_serial_debug_data(debug, &port->dev, __func__, count,
					wport->write_urb->transfer_buffer);
Linus Torvalds's avatar
Linus Torvalds committed
288 289 290 291 292 293 294 295 296 297 298

	header->oh_seq 	= od->od_outseq++;
	header->oh_len 	= count;
	header->oh_xxx  = 0x03;
	header->oh_pad 	= 0x00;

	/* send the data out the bulk port, always 64 bytes */
	wport->write_urb->transfer_buffer_length = 64;

	wport->write_urb->dev = serial->dev;
	result = usb_submit_urb(wport->write_urb, GFP_ATOMIC);
299
	if (result) {
300
		wport->write_urb_busy = 0;
301 302 303
		dev_err(&port->dev,
			"%s - failed submitting write urb, error %d\n",
			__func__, result);
304
	} else
Linus Torvalds's avatar
Linus Torvalds committed
305 306 307 308 309 310
		result = count;

	return result;
}


Alan Cox's avatar
Alan Cox committed
311
static int omninet_write_room(struct tty_struct *tty)
Linus Torvalds's avatar
Linus Torvalds committed
312
{
Alan Cox's avatar
Alan Cox committed
313
	struct usb_serial_port *port = tty->driver_data;
Linus Torvalds's avatar
Linus Torvalds committed
314 315 316
	struct usb_serial 	*serial = port->serial;
	struct usb_serial_port 	*wport 	= serial->port[1];

317
	int room = 0; /* Default: no room */
Linus Torvalds's avatar
Linus Torvalds committed
318

319
	/* FIXME: no consistent locking for write_urb_busy */
320
	if (wport->write_urb_busy)
Linus Torvalds's avatar
Linus Torvalds committed
321 322
		room = wport->bulk_out_size - OMNINET_HEADERLEN;

323
	dbg("%s - returns %d", __func__, room);
Linus Torvalds's avatar
Linus Torvalds committed
324

Alan Cox's avatar
Alan Cox committed
325
	return room;
Linus Torvalds's avatar
Linus Torvalds committed
326 327
}

Alan Cox's avatar
Alan Cox committed
328
static void omninet_write_bulk_callback(struct urb *urb)
Linus Torvalds's avatar
Linus Torvalds committed
329
{
Alan Cox's avatar
Alan Cox committed
330 331
/*	struct omninet_header	*header = (struct omninet_header  *)
						urb->transfer_buffer; */
332
	struct usb_serial_port 	*port   =  urb->context;
333
	int status = urb->status;
Linus Torvalds's avatar
Linus Torvalds committed
334

335
	dbg("%s - port %0x\n", __func__, port->number);
Linus Torvalds's avatar
Linus Torvalds committed
336

337
	port->write_urb_busy = 0;
338 339
	if (status) {
		dbg("%s - nonzero write bulk status received: %d",
340
		    __func__, status);
Linus Torvalds's avatar
Linus Torvalds committed
341 342 343
		return;
	}

344
	usb_serial_port_softint(port);
Linus Torvalds's avatar
Linus Torvalds committed
345 346 347
}


348
static void omninet_disconnect(struct usb_serial *serial)
Linus Torvalds's avatar
Linus Torvalds committed
349
{
350
	struct usb_serial_port *wport = serial->port[1];
351

Alan Cox's avatar
Alan Cox committed
352
	dbg("%s", __func__);
353 354

	usb_kill_urb(wport->write_urb);
355 356 357 358 359 360 361 362 363
}


static void omninet_release(struct usb_serial *serial)
{
	struct usb_serial_port *port = serial->port[0];

	dbg("%s", __func__);

364
	kfree(usb_get_serial_port_data(port));
Linus Torvalds's avatar
Linus Torvalds committed
365 366 367
}


Alan Cox's avatar
Alan Cox committed
368
static int __init omninet_init(void)
Linus Torvalds's avatar
Linus Torvalds committed
369 370 371 372 373 374 375 376
{
	int retval;
	retval = usb_serial_register(&zyxel_omninet_device);
	if (retval)
		goto failed_usb_serial_register;
	retval = usb_register(&omninet_driver);
	if (retval)
		goto failed_usb_register;
377 378
	printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"
	       DRIVER_DESC "\n");
Linus Torvalds's avatar
Linus Torvalds committed
379 380 381 382 383 384 385 386
	return 0;
failed_usb_register:
	usb_serial_deregister(&zyxel_omninet_device);
failed_usb_serial_register:
	return retval;
}


Alan Cox's avatar
Alan Cox committed
387
static void __exit omninet_exit(void)
Linus Torvalds's avatar
Linus Torvalds committed
388
{
Alan Cox's avatar
Alan Cox committed
389 390
	usb_deregister(&omninet_driver);
	usb_serial_deregister(&zyxel_omninet_device);
Linus Torvalds's avatar
Linus Torvalds committed
391 392 393 394 395 396
}


module_init(omninet_init);
module_exit(omninet_exit);

Alan Cox's avatar
Alan Cox committed
397 398
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
Linus Torvalds's avatar
Linus Torvalds committed
399 400 401 402
MODULE_LICENSE("GPL");

module_param(debug, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "Debug enabled or not");