cdc-acm.c 21.3 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1
/*
2
 * cdc-acm.c
Linus Torvalds's avatar
Linus Torvalds committed
3 4 5
 *
 * Copyright (c) 1999 Armin Fuerst	<fuerst@in.tum.de>
 * Copyright (c) 1999 Pavel Machek	<pavel@suse.cz>
Linus Torvalds's avatar
Linus Torvalds committed
6
 * Copyright (c) 1999 Johannes Erdfelt	<johannes@erdfelt.com>
Linus Torvalds's avatar
Linus Torvalds committed
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 * Copyright (c) 2000 Vojtech Pavlik	<vojtech@suse.cz>
 *
 * USB Abstract Control Model driver for USB modems and ISDN adapters
 *
 * Sponsored by SuSE
 *
 * ChangeLog:
 *	v0.9  - thorough cleaning, URBification, almost a rewrite
 *	v0.10 - some more cleanups
 *	v0.11 - fixed flow control, read error doesn't stop reads
 *	v0.12 - added TIOCM ioctls, added break handling, made struct acm kmalloced
 *	v0.13 - added termios, added hangup
 *	v0.14 - sized down struct acm
 *	v0.15 - fixed flow control again - characters could be lost
 *	v0.16 - added code for modems with swapped data and control interfaces
 *	v0.17 - added new style probing
 *	v0.18 - fixed new style probing for devices with more configurations
Linus Torvalds's avatar
Linus Torvalds committed
24
 *	v0.19 - fixed CLOCAL handling (thanks to Richard Shih-Ping Chan)
Linus Torvalds's avatar
Linus Torvalds committed
25 26
 *	v0.20 - switched to probing on interface (rather than device) class
 *	v0.21 - revert to probing on device for devices with multiple configs
27 28
 *	v0.22 - probe only the control interface. if usbcore doesn't choose the
 *		config we want, sysadmin changes bConfigurationValue in sysfs.
29
 *	v0.23 - use softirq for rx processing, as needed by tty layer
30
 *	v0.24 - change probe method to evaluate CDC union descriptor
Linus Torvalds's avatar
Linus Torvalds committed
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
 */

/*
 * 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
 */

49 50
#undef DEBUG

Linus Torvalds's avatar
Linus Torvalds committed
51 52 53
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
Linus Torvalds's avatar
Linus Torvalds committed
54 55
#include <linux/slab.h>
#include <linux/tty.h>
Linus Torvalds's avatar
Linus Torvalds committed
56 57 58
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
Linus Torvalds's avatar
Linus Torvalds committed
59
#include <linux/smp_lock.h>
60
#include <asm/uaccess.h>
Linus Torvalds's avatar
Linus Torvalds committed
61
#include <linux/usb.h>
62
#include <asm/byteorder.h>
Linus Torvalds's avatar
Linus Torvalds committed
63

64 65
#include "cdc-acm.h"

Linus Torvalds's avatar
Linus Torvalds committed
66 67 68
/*
 * Version Information
 */
69
#define DRIVER_VERSION "v0.23"
Linus Torvalds's avatar
Linus Torvalds committed
70 71 72
#define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik"
#define DRIVER_DESC "USB Abstract Control Model driver for USB modems and ISDN adapters"

Linus Torvalds's avatar
Linus Torvalds committed
73
static struct usb_driver acm_driver;
74
static struct tty_driver *acm_tty_driver;
Linus Torvalds's avatar
Linus Torvalds committed
75 76
static struct acm *acm_table[ACM_TTY_MINORS];

77 78
static DECLARE_MUTEX(open_sem);

Linus Torvalds's avatar
Linus Torvalds committed
79 80 81 82 83 84 85 86 87
#define ACM_READY(acm)	(acm && acm->dev && acm->used)

/*
 * Functions for ACM control messages.
 */

static int acm_ctrl_msg(struct acm *acm, int request, int value, void *buf, int len)
{
	int retval = usb_control_msg(acm->dev, usb_sndctrlpipe(acm->dev, 0),
88
		request, USB_RT_ACM, value,
89
		acm->control->altsetting[0].desc.bInterfaceNumber,
90
		buf, len, HZ * 5);
Linus Torvalds's avatar
Linus Torvalds committed
91 92 93 94
	dbg("acm_control_msg: rq: 0x%02x val: %#x len: %#x result: %d", request, value, len, retval);
	return retval < 0 ? retval : 0;
}

95 96 97
/* devices aren't required to support these requests.
 * the cdc acm descriptor tells whether they do...
 */
Linus Torvalds's avatar
Linus Torvalds committed
98 99 100 101 102
#define acm_set_control(acm, control)	acm_ctrl_msg(acm, ACM_REQ_SET_CONTROL, control, NULL, 0)
#define acm_set_line(acm, line)		acm_ctrl_msg(acm, ACM_REQ_SET_LINE, 0, line, sizeof(struct acm_line))
#define acm_send_break(acm, ms)		acm_ctrl_msg(acm, ACM_REQ_SEND_BREAK, ms, NULL, 0)

/*
103
 * Interrupt handlers for various ACM device responses
Linus Torvalds's avatar
Linus Torvalds committed
104 105
 */

106
/* control interface reports status changes with "interrupt" transfers */
David S. Miller's avatar
David S. Miller committed
107
static void acm_ctrl_irq(struct urb *urb, struct pt_regs *regs)
Linus Torvalds's avatar
Linus Torvalds committed
108 109
{
	struct acm *acm = urb->context;
Linus Torvalds's avatar
Linus Torvalds committed
110
	struct usb_ctrlrequest *dr = urb->transfer_buffer;
Linus Torvalds's avatar
Linus Torvalds committed
111 112
	unsigned char *data = (unsigned char *)(dr + 1);
	int newctrl;
113 114 115 116 117 118 119 120 121 122 123
	int status;

	switch (urb->status) {
	case 0:
		/* success */
		break;
	case -ECONNRESET:
	case -ENOENT:
	case -ESHUTDOWN:
		/* this urb is terminated, clean up */
		dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status);
Linus Torvalds's avatar
Linus Torvalds committed
124
		return;
125 126 127
	default:
		dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status);
		goto exit;
Linus Torvalds's avatar
Linus Torvalds committed
128 129
	}

130 131 132
	if (!ACM_READY(acm))
		goto exit;

Linus Torvalds's avatar
Linus Torvalds committed
133
	switch (dr->bRequest) {
Linus Torvalds's avatar
Linus Torvalds committed
134 135 136

		case ACM_IRQ_NETWORK:

137
			dbg("%s network", dr->wValue ? "connected to" : "disconnected from");
138
			break;
Linus Torvalds's avatar
Linus Torvalds committed
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156

		case ACM_IRQ_LINE_STATE:

			newctrl = le16_to_cpup((__u16 *) data);

			if (acm->tty && !acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) {
				dbg("calling hangup");
				tty_hangup(acm->tty);
			}

			acm->ctrlin = newctrl;

			dbg("input control lines: dcd%c dsr%c break%c ring%c framing%c parity%c overrun%c",
				acm->ctrlin & ACM_CTRL_DCD ? '+' : '-',	acm->ctrlin & ACM_CTRL_DSR ? '+' : '-',
				acm->ctrlin & ACM_CTRL_BRK ? '+' : '-',	acm->ctrlin & ACM_CTRL_RI  ? '+' : '-',
				acm->ctrlin & ACM_CTRL_FRAMING ? '+' : '-',	acm->ctrlin & ACM_CTRL_PARITY ? '+' : '-',
				acm->ctrlin & ACM_CTRL_OVERRUN ? '+' : '-');

157
			break;
Linus Torvalds's avatar
Linus Torvalds committed
158 159 160

		default:
			dbg("unknown control event received: request %d index %d len %d data0 %d data1 %d",
Linus Torvalds's avatar
Linus Torvalds committed
161
				dr->bRequest, dr->wIndex, dr->wLength, data[0], data[1]);
162
			break;
Linus Torvalds's avatar
Linus Torvalds committed
163
	}
164 165 166 167 168
exit:
	status = usb_submit_urb (urb, GFP_ATOMIC);
	if (status)
		err ("%s - usb_submit_urb failed with result %d",
		     __FUNCTION__, status);
Linus Torvalds's avatar
Linus Torvalds committed
169 170
}

171
/* data interface returns incoming bytes, or we got unthrottled */
David S. Miller's avatar
David S. Miller committed
172
static void acm_read_bulk(struct urb *urb, struct pt_regs *regs)
Linus Torvalds's avatar
Linus Torvalds committed
173 174 175
{
	struct acm *acm = urb->context;

176 177
	if (!ACM_READY(acm))
		return;
Linus Torvalds's avatar
Linus Torvalds committed
178 179

	if (urb->status)
180 181 182 183 184 185 186 187 188 189 190 191 192
		dev_dbg(&acm->data->dev, "bulk rx status %d\n", urb->status);

	/* calling tty_flip_buffer_push() in_irq() isn't allowed */
	tasklet_schedule(&acm->bh);
}

static void acm_rx_tasklet(unsigned long _acm)
{
	struct acm *acm = (void *)_acm;
	struct urb *urb = acm->readurb;
	struct tty_struct *tty = acm->tty;
	unsigned char *data = urb->transfer_buffer;
	int i = 0;
Linus Torvalds's avatar
Linus Torvalds committed
193

194
	if (urb->actual_length > 0 && !acm->throttle)  {
Linus Torvalds's avatar
Linus Torvalds committed
195 196 197 198 199 200
		for (i = 0; i < urb->actual_length && !acm->throttle; 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);
			}
Linus Torvalds's avatar
Linus Torvalds committed
201
			tty_insert_flip_char(tty, data[i], 0);
Linus Torvalds's avatar
Linus Torvalds committed
202
		}
Linus Torvalds's avatar
Linus Torvalds committed
203 204 205 206 207 208 209 210 211 212 213 214
		tty_flip_buffer_push(tty);
	}

	if (acm->throttle) {
		memmove(data, data + i, urb->actual_length - i);
		urb->actual_length -= i;
		return;
	}

	urb->actual_length = 0;
	urb->dev = acm->dev;

215 216 217
	i = usb_submit_urb(urb, GFP_ATOMIC);
	if (i)
		dev_dbg(&acm->data->dev, "bulk rx resubmit %d\n", i);
Linus Torvalds's avatar
Linus Torvalds committed
218 219
}

220
/* data interface wrote those outgoing bytes */
David S. Miller's avatar
David S. Miller committed
221
static void acm_write_bulk(struct urb *urb, struct pt_regs *regs)
Linus Torvalds's avatar
Linus Torvalds committed
222 223 224
{
	struct acm *acm = (struct acm *)urb->context;

225
	if (!ACM_READY(acm))
226
		goto out;
Linus Torvalds's avatar
Linus Torvalds committed
227 228 229 230

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

Ingo Molnar's avatar
Ingo Molnar committed
231
	schedule_work(&acm->work);
232 233
out:
	acm->ready_for_write = 1;
Linus Torvalds's avatar
Linus Torvalds committed
234 235 236 237 238 239 240
}

static void acm_softint(void *private)
{
	struct acm *acm = private;
	struct tty_struct *tty = acm->tty;

241 242
	if (!ACM_READY(acm))
		return;
Linus Torvalds's avatar
Linus Torvalds committed
243 244 245 246 247 248 249 250 251 252 253 254 255

	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup)
		(tty->ldisc.write_wakeup)(tty);

	wake_up_interruptible(&tty->write_wait);
}

/*
 * TTY handlers
 */

static int acm_tty_open(struct tty_struct *tty, struct file *filp)
{
Alexander Viro's avatar
Alexander Viro committed
256
	struct acm *acm = acm_table[tty->index];
Linus Torvalds's avatar
Linus Torvalds committed
257

258 259
	if (!acm || !acm->dev)
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
260 261 262 263

	tty->driver_data = acm;
	acm->tty = tty;

264
        down(&open_sem);
Linus Torvalds's avatar
Linus Torvalds committed
265

266 267
	if (acm->used) {
		goto done;
Linus Torvalds's avatar
Linus Torvalds committed
268 269
        }

Greg Kroah-Hartman's avatar
Greg Kroah-Hartman committed
270
	acm->ctrlurb->dev = acm->dev;
271
	if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) {
Linus Torvalds's avatar
Linus Torvalds committed
272
		dbg("usb_submit_urb(ctrl irq) failed");
273 274
		goto bail_out;
	}
Linus Torvalds's avatar
Linus Torvalds committed
275

Greg Kroah-Hartman's avatar
Greg Kroah-Hartman committed
276
	acm->readurb->dev = acm->dev;
277
	if (usb_submit_urb(acm->readurb, GFP_KERNEL)) {
Linus Torvalds's avatar
Linus Torvalds committed
278
		dbg("usb_submit_urb(read bulk) failed");
279 280
		goto bail_out_and_unlink;
	}
Linus Torvalds's avatar
Linus Torvalds committed
281 282 283

	acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS);

Linus Torvalds's avatar
Linus Torvalds committed
284 285 286 287
	/* force low_latency on so that our tty_push actually forces the data through, 
	   otherwise it is scheduled, and with high data rates data can get lost. */
	tty->low_latency = 1;

288 289 290
done:
	acm->used++;
	up(&open_sem);
Linus Torvalds's avatar
Linus Torvalds committed
291
	return 0;
292 293 294 295 296 297

bail_out_and_unlink:
	usb_unlink_urb(acm->ctrlurb);
bail_out:
	up(&open_sem);
	return -EIO;
Linus Torvalds's avatar
Linus Torvalds committed
298 299 300 301 302 303
}

static void acm_tty_close(struct tty_struct *tty, struct file *filp)
{
	struct acm *acm = tty->driver_data;

304 305
	if (!acm || !acm->used)
		return;
Linus Torvalds's avatar
Linus Torvalds committed
306

307
	down(&open_sem);
Linus Torvalds's avatar
Linus Torvalds committed
308 309 310
	if (!--acm->used) {
		if (acm->dev) {
			acm_set_control(acm, acm->ctrlout = 0);
Greg Kroah-Hartman's avatar
Greg Kroah-Hartman committed
311 312 313
			usb_unlink_urb(acm->ctrlurb);
			usb_unlink_urb(acm->writeurb);
			usb_unlink_urb(acm->readurb);
Linus Torvalds's avatar
Linus Torvalds committed
314
		} else {
315
			tty_unregister_device(acm_tty_driver, acm->minor);
Linus Torvalds's avatar
Linus Torvalds committed
316
			acm_table[acm->minor] = NULL;
Greg Kroah-Hartman's avatar
Greg Kroah-Hartman committed
317 318 319
			usb_free_urb(acm->ctrlurb);
			usb_free_urb(acm->readurb);
			usb_free_urb(acm->writeurb);
Linus Torvalds's avatar
Linus Torvalds committed
320 321 322
			kfree(acm);
		}
	}
323
	up(&open_sem);
Linus Torvalds's avatar
Linus Torvalds committed
324 325 326 327 328
}

static int acm_tty_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count)
{
	struct acm *acm = tty->driver_data;
329
	int stat;
Linus Torvalds's avatar
Linus Torvalds committed
330

331 332
	if (!ACM_READY(acm))
		return -EINVAL;
333
	if (!acm->ready_for_write)
334 335 336
		return 0;
	if (!count)
		return 0;
Linus Torvalds's avatar
Linus Torvalds committed
337 338 339

	count = (count > acm->writesize) ? acm->writesize : count;

Arnaldo Carvalho de Melo's avatar
Arnaldo Carvalho de Melo committed
340
	if (from_user) {
341
		if (copy_from_user(acm->writeurb->transfer_buffer, (void __user *)buf, count))
Arnaldo Carvalho de Melo's avatar
Arnaldo Carvalho de Melo committed
342 343
			return -EFAULT;
	} else
Greg Kroah-Hartman's avatar
Greg Kroah-Hartman committed
344
		memcpy(acm->writeurb->transfer_buffer, buf, count);
Linus Torvalds's avatar
Linus Torvalds committed
345

Greg Kroah-Hartman's avatar
Greg Kroah-Hartman committed
346 347
	acm->writeurb->transfer_buffer_length = count;
	acm->writeurb->dev = acm->dev;
Linus Torvalds's avatar
Linus Torvalds committed
348

349 350
	acm->ready_for_write = 0;
	stat = usb_submit_urb(acm->writeurb, GFP_NOIO);
351
	if (stat < 0) {
Linus Torvalds's avatar
Linus Torvalds committed
352
		dbg("usb_submit_urb(write bulk) failed");
353
		acm->ready_for_write = 1;
354 355
		return stat;
	}
Linus Torvalds's avatar
Linus Torvalds committed
356 357 358 359 360 361 362

	return count;
}

static int acm_tty_write_room(struct tty_struct *tty)
{
	struct acm *acm = tty->driver_data;
363 364
	if (!ACM_READY(acm))
		return -EINVAL;
365
	return !acm->ready_for_write ? 0 : acm->writesize;
Linus Torvalds's avatar
Linus Torvalds committed
366 367 368 369 370
}

static int acm_tty_chars_in_buffer(struct tty_struct *tty)
{
	struct acm *acm = tty->driver_data;
371 372
	if (!ACM_READY(acm))
		return -EINVAL;
373
	return !acm->ready_for_write ? acm->writeurb->transfer_buffer_length : 0;
Linus Torvalds's avatar
Linus Torvalds committed
374 375 376 377 378
}

static void acm_tty_throttle(struct tty_struct *tty)
{
	struct acm *acm = tty->driver_data;
379 380
	if (!ACM_READY(acm))
		return;
Linus Torvalds's avatar
Linus Torvalds committed
381 382 383 384 385 386
	acm->throttle = 1;
}

static void acm_tty_unthrottle(struct tty_struct *tty)
{
	struct acm *acm = tty->driver_data;
387 388
	if (!ACM_READY(acm))
		return;
Linus Torvalds's avatar
Linus Torvalds committed
389
	acm->throttle = 0;
Greg Kroah-Hartman's avatar
Greg Kroah-Hartman committed
390
	if (acm->readurb->status != -EINPROGRESS)
David S. Miller's avatar
David S. Miller committed
391
		acm_read_bulk(acm->readurb, NULL);
Linus Torvalds's avatar
Linus Torvalds committed
392 393 394 395 396
}

static void acm_tty_break_ctl(struct tty_struct *tty, int state)
{
	struct acm *acm = tty->driver_data;
397 398
	if (!ACM_READY(acm))
		return;
Linus Torvalds's avatar
Linus Torvalds committed
399 400 401 402
	if (acm_send_break(acm, state ? 0xffff : 0))
		dbg("send break failed");
}

403
static int acm_tty_tiocmget(struct tty_struct *tty, struct file *file)
Linus Torvalds's avatar
Linus Torvalds committed
404 405 406
{
	struct acm *acm = tty->driver_data;

407 408 409 410 411 412 413 414 415 416
	if (!ACM_READY(acm))
		return -EINVAL;

	return (acm->ctrlout & ACM_CTRL_DTR ? TIOCM_DTR : 0) |
	       (acm->ctrlout & ACM_CTRL_RTS ? TIOCM_RTS : 0) |
	       (acm->ctrlin  & ACM_CTRL_DSR ? TIOCM_DSR : 0) |
	       (acm->ctrlin  & ACM_CTRL_RI  ? TIOCM_RI  : 0) |
	       (acm->ctrlin  & ACM_CTRL_DCD ? TIOCM_CD  : 0) |
	       TIOCM_CTS;
}
Linus Torvalds's avatar
Linus Torvalds committed
417

418 419 420 421 422
static int acm_tty_tiocmset(struct tty_struct *tty, struct file *file,
			    unsigned int set, unsigned int clear)
{
	struct acm *acm = tty->driver_data;
	unsigned int newctrl;
Linus Torvalds's avatar
Linus Torvalds committed
423

424 425
	if (!ACM_READY(acm))
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
426

427 428 429
	newctrl = acm->ctrlout;
	set = (set & TIOCM_DTR ? ACM_CTRL_DTR : 0) | (set & TIOCM_RTS ? ACM_CTRL_RTS : 0);
	clear = (clear & TIOCM_DTR ? ACM_CTRL_DTR : 0) | (clear & TIOCM_RTS ? ACM_CTRL_RTS : 0);
Linus Torvalds's avatar
Linus Torvalds committed
430

431
	newctrl = (newctrl & ~clear) | set;
Linus Torvalds's avatar
Linus Torvalds committed
432

433 434 435 436
	if (acm->ctrlout == newctrl)
		return 0;
	return acm_set_control(acm, acm->ctrlout = newctrl);
}
Linus Torvalds's avatar
Linus Torvalds committed
437

438 439 440
static int acm_tty_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
{
	struct acm *acm = tty->driver_data;
Linus Torvalds's avatar
Linus Torvalds committed
441

442 443
	if (!ACM_READY(acm))
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466

	return -ENOIOCTLCMD;
}

static __u32 acm_tty_speed[] = {
	0, 50, 75, 110, 134, 150, 200, 300, 600,
	1200, 1800, 2400, 4800, 9600, 19200, 38400,
	57600, 115200, 230400, 460800, 500000, 576000,
	921600, 1000000, 1152000, 1500000, 2000000,
	2500000, 3000000, 3500000, 4000000
};

static __u8 acm_tty_size[] = {
	5, 6, 7, 8
};

static void acm_tty_set_termios(struct tty_struct *tty, struct termios *termios_old)
{
	struct acm *acm = tty->driver_data;
	struct termios *termios = tty->termios;
	struct acm_line newline;
	int newctrl = acm->ctrlout;

467 468
	if (!ACM_READY(acm))
		return;
Linus Torvalds's avatar
Linus Torvalds committed
469 470 471 472 473 474 475 476

	newline.speed = cpu_to_le32p(acm_tty_speed +
		(termios->c_cflag & CBAUD & ~CBAUDEX) + (termios->c_cflag & CBAUDEX ? 15 : 0));
	newline.stopbits = termios->c_cflag & CSTOPB ? 2 : 0;
	newline.parity = termios->c_cflag & PARENB ?
		(termios->c_cflag & PARODD ? 1 : 2) + (termios->c_cflag & CMSPAR ? 2 : 0) : 0;
	newline.databits = acm_tty_size[(termios->c_cflag & CSIZE) >> 4];

Linus Torvalds's avatar
Linus Torvalds committed
477
	acm->clocal = ((termios->c_cflag & CLOCAL) != 0);
Linus Torvalds's avatar
Linus Torvalds committed
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497

	if (!newline.speed) {
		newline.speed = acm->line.speed;
		newctrl &= ~ACM_CTRL_DTR;
	} else  newctrl |=  ACM_CTRL_DTR;

	if (newctrl != acm->ctrlout)
		acm_set_control(acm, acm->ctrlout = newctrl);

	if (memcmp(&acm->line, &newline, sizeof(struct acm_line))) {
		memcpy(&acm->line, &newline, sizeof(struct acm_line));
		dbg("set line: %d %d %d %d", newline.speed, newline.stopbits, newline.parity, newline.databits);
		acm_set_line(acm, &acm->line);
	}
}

/*
 * USB probe and disconnect routines.
 */

498 499
static int acm_probe (struct usb_interface *intf,
		      const struct usb_device_id *id)
Linus Torvalds's avatar
Linus Torvalds committed
500
{
501 502 503 504 505 506 507 508 509
	struct union_desc *union_header = NULL;
	char *buffer = intf->altsetting->extra;
	int buflen = intf->altsetting->extralen;
	struct usb_interface *control_interface;
	struct usb_interface *data_interface;
	struct usb_endpoint_descriptor *epctrl;
	struct usb_endpoint_descriptor *epread;
	struct usb_endpoint_descriptor *epwrite;
	struct usb_device *usb_dev = interface_to_usbdev(intf);
Linus Torvalds's avatar
Linus Torvalds committed
510
	struct acm *acm;
511 512 513 514 515 516 517 518 519 520 521 522 523
	int minor;
	int ctrlsize,readsize;
	char *buf;

	if (!buffer) {
		err("Wierd descriptor references");
		return -EINVAL;
	}

	while (buflen > 0) {
		if (buffer [1] != USB_DT_CS_INTERFACE) {
			err("skipping garbage");
			goto next_desc;
524
		}
525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545

		switch (buffer [2]) {
			case CDC_UNION_TYPE: /* we've found it */
				if (union_header) {
					err("More than one union descriptor, skipping ...");
					goto next_desc;
				}
				union_header = (struct union_desc *)buffer;
				break;
			default:
				err("Ignoring extra header");
				break;
			}
next_desc:
		buflen -= buffer[0];
		buffer += buffer[0];
	}

	if (!union_header) {
		dev_dbg(&intf->dev,"No union descriptor, giving up\n");
		return -ENODEV;
546
	}
Linus Torvalds's avatar
Linus Torvalds committed
547

548 549 550 551
	control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0);
	data_interface = usb_ifnum_to_if(usb_dev, union_header->bSlaveInterface0);
	if (!control_interface || !data_interface) {
		dev_dbg(&intf->dev,"no interfaces\n");
552
		return -ENODEV;
553 554 555 556 557 558 559 560 561 562 563 564
	}

	if (usb_interface_claimed(data_interface)) { /* valid in this context */
		dev_dbg(&intf->dev,"The data interface isn't available\n");
		return -EBUSY;
	}

	/*workaround for switched interfaces */
	if (data_interface->cur_altsetting->desc.bInterfaceClass != CDC_DATA_INTERFACE_TYPE) {
		if (control_interface->cur_altsetting->desc.bInterfaceClass == CDC_DATA_INTERFACE_TYPE) {
			struct usb_interface *t;
			dev_dbg(&intf->dev,"Your device has switched interfaces.\n");
Linus Torvalds's avatar
Linus Torvalds committed
565

566 567 568 569 570 571
			t = control_interface;
			control_interface = data_interface;
			data_interface = t;
		} else {
			return -EINVAL;
		}
572
	}
573 574 575 576 577 578 579
	if (data_interface->cur_altsetting->desc.bNumEndpoints < 2)
		return -EINVAL;

	epctrl = &control_interface->cur_altsetting->endpoint[0].desc;
	epread = &data_interface->cur_altsetting->endpoint[0].desc;
	epwrite = &data_interface->cur_altsetting->endpoint[1].desc;

Linus Torvalds's avatar
Linus Torvalds committed
580

581 582 583 584 585 586 587 588 589 590 591
	/* workaround for switched endpoints */
	if ((epread->bEndpointAddress & USB_DIR_IN) != USB_DIR_IN) {
		/* descriptors are swapped */
		struct usb_endpoint_descriptor *t;
		dev_dbg(&intf->dev,"The data interface has switched endpoints\n");
		
		t = epread;
		epread = epwrite;
		epwrite = t;
	}
	dbg("interfaces are valid");
592
	for (minor = 0; minor < ACM_TTY_MINORS && acm_table[minor]; minor++);
593

594 595 596 597
	if (acm_table[minor]) {
		err("no more free acm devices");
		return -ENODEV;
	}
Linus Torvalds's avatar
Linus Torvalds committed
598

599 600 601 602 603 604 605 606 607
	if (!(acm = kmalloc(sizeof(struct acm), GFP_KERNEL))) {
		dev_dbg(&intf->dev, "out of memory (acm kmalloc)\n");
		return -ENOMEM;
	}
	memset(acm, 0, sizeof(struct acm));

	ctrlsize = epctrl->wMaxPacketSize;
	readsize = epread->wMaxPacketSize;
	acm->writesize = epwrite->wMaxPacketSize;
608 609
	acm->control = control_interface;
	acm->data = data_interface;
610
	acm->minor = minor;
611
	acm->dev = usb_dev;
612 613 614 615

	acm->bh.func = acm_rx_tasklet;
	acm->bh.data = (unsigned long) acm;
	INIT_WORK(&acm->work, acm_softint, acm);
616
	acm->ready_for_write = 1;
617

618

619 620 621 622 623
	if (!(buf = kmalloc(ctrlsize + readsize + acm->writesize, GFP_KERNEL))) {
		dev_dbg(&intf->dev, "out of memory (buf kmalloc)\n");
		kfree(acm);
		return -ENOMEM;
	}
Linus Torvalds's avatar
Linus Torvalds committed
624

625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
	acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL);
	if (!acm->ctrlurb) {
		dev_dbg(&intf->dev, "out of memory (ctrlurb kmalloc)\n");
		kfree(acm);
		kfree(buf);
		return -ENOMEM;
	}
	acm->readurb = usb_alloc_urb(0, GFP_KERNEL);
	if (!acm->readurb) {
		dev_dbg(&intf->dev, "out of memory (readurb kmalloc)\n");
		usb_free_urb(acm->ctrlurb);
		kfree(acm);
		kfree(buf);
		return -ENOMEM;
	}
	acm->writeurb = usb_alloc_urb(0, GFP_KERNEL);
	if (!acm->writeurb) {
		dev_dbg(&intf->dev, "out of memory (writeurb kmalloc)\n");
		usb_free_urb(acm->readurb);
		usb_free_urb(acm->ctrlurb);
		kfree(acm);
		kfree(buf);
		return -ENOMEM;
	}
Greg Kroah-Hartman's avatar
Greg Kroah-Hartman committed
649

650 651
	usb_fill_int_urb(acm->ctrlurb, usb_dev, usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress),
			 buf, ctrlsize, acm_ctrl_irq, acm, epctrl->bInterval);
Linus Torvalds's avatar
Linus Torvalds committed
652

653 654
	usb_fill_bulk_urb(acm->readurb, usb_dev, usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress),
			  buf += ctrlsize, readsize, acm_read_bulk, acm);
655
	acm->readurb->transfer_flags |= URB_NO_FSBR;
Linus Torvalds's avatar
Linus Torvalds committed
656

657 658
	usb_fill_bulk_urb(acm->writeurb, usb_dev, usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress),
			  buf += readsize, acm->writesize, acm_write_bulk, acm);
659
	acm->writeurb->transfer_flags |= URB_NO_FSBR;
Linus Torvalds's avatar
Linus Torvalds committed
660

661
	dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor);
Linus Torvalds's avatar
Linus Torvalds committed
662

663
	acm_set_control(acm, acm->ctrlout);
Linus Torvalds's avatar
Linus Torvalds committed
664

665 666 667
	acm->line.speed = cpu_to_le32(9600);
	acm->line.databits = 8;
	acm_set_line(acm, &acm->line);
668

669 670 671 672
	usb_driver_claim_interface(&acm_driver, data_interface, acm);

	tty_register_device(acm_tty_driver, minor, &intf->dev);

673 674 675
	acm_table[minor] = acm;
	usb_set_intfdata (intf, acm);
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
676 677
}

678
static void acm_disconnect(struct usb_interface *intf)
Linus Torvalds's avatar
Linus Torvalds committed
679
{
680
	struct acm *acm = usb_get_intfdata (intf);
Linus Torvalds's avatar
Linus Torvalds committed
681 682 683 684 685 686 687

	if (!acm || !acm->dev) {
		dbg("disconnect on nonexisting interface");
		return;
	}

	acm->dev = NULL;
688
	usb_set_intfdata (intf, NULL);
Linus Torvalds's avatar
Linus Torvalds committed
689

Greg Kroah-Hartman's avatar
Greg Kroah-Hartman committed
690 691 692
	usb_unlink_urb(acm->ctrlurb);
	usb_unlink_urb(acm->readurb);
	usb_unlink_urb(acm->writeurb);
Linus Torvalds's avatar
Linus Torvalds committed
693

694 695
	flush_scheduled_work(); /* wait for acm_softint */

Greg Kroah-Hartman's avatar
Greg Kroah-Hartman committed
696
	kfree(acm->ctrlurb->transfer_buffer);
Linus Torvalds's avatar
Linus Torvalds committed
697

698
	usb_driver_release_interface(&acm_driver, acm->data);
Linus Torvalds's avatar
Linus Torvalds committed
699 700

	if (!acm->used) {
701
		tty_unregister_device(acm_tty_driver, acm->minor);
Linus Torvalds's avatar
Linus Torvalds committed
702
		acm_table[acm->minor] = NULL;
Greg Kroah-Hartman's avatar
Greg Kroah-Hartman committed
703 704 705
		usb_free_urb(acm->ctrlurb);
		usb_free_urb(acm->readurb);
		usb_free_urb(acm->writeurb);
Linus Torvalds's avatar
Linus Torvalds committed
706 707 708 709 710 711 712 713 714 715 716 717 718
		kfree(acm);
		return;
	}

	if (acm->tty)
		tty_hangup(acm->tty);
}

/*
 * USB driver structure.
 */

static struct usb_device_id acm_ids[] = {
719 720 721 722 723 724 725 726 727
	/* control interfaces with various AT-command sets */
	{ USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 1) },
	{ USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 2) },
	{ USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 3) },
	{ USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 4) },
	{ USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 5) },
	{ USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 6) },

	/* NOTE:  COMM/2/0xff is likely MSFT RNDIS ... NOT a modem!! */
Linus Torvalds's avatar
Linus Torvalds committed
728 729 730 731 732 733
	{ }
};

MODULE_DEVICE_TABLE (usb, acm_ids);

static struct usb_driver acm_driver = {
734
	.owner =	THIS_MODULE,
735
	.name =		"cdc_acm",
736 737 738
	.probe =	acm_probe,
	.disconnect =	acm_disconnect,
	.id_table =	acm_ids,
Linus Torvalds's avatar
Linus Torvalds committed
739 740 741 742 743 744
};

/*
 * TTY driver structures.
 */

745
static struct tty_operations acm_ops = {
746 747 748 749 750 751 752 753 754
	.open =			acm_tty_open,
	.close =		acm_tty_close,
	.write =		acm_tty_write,
	.write_room =		acm_tty_write_room,
	.ioctl =		acm_tty_ioctl,
	.throttle =		acm_tty_throttle,
	.unthrottle =		acm_tty_unthrottle,
	.chars_in_buffer =	acm_tty_chars_in_buffer,
	.break_ctl =		acm_tty_break_ctl,
755 756 757
	.set_termios =		acm_tty_set_termios,
	.tiocmget =		acm_tty_tiocmget,
	.tiocmset =		acm_tty_tiocmset,
Linus Torvalds's avatar
Linus Torvalds committed
758 759 760 761 762 763 764 765
};

/*
 * Init / exit.
 */

static int __init acm_init(void)
{
766
	int retval;
767 768 769 770 771
	acm_tty_driver = alloc_tty_driver(ACM_TTY_MINORS);
	if (!acm_tty_driver)
		return -ENOMEM;
	acm_tty_driver->owner = THIS_MODULE,
	acm_tty_driver->driver_name = "acm",
772 773
	acm_tty_driver->name = "ttyACM",
	acm_tty_driver->devfs_name = "usb/acm/",
774 775 776 777 778 779 780 781 782
	acm_tty_driver->major = ACM_TTY_MAJOR,
	acm_tty_driver->minor_start = 0,
	acm_tty_driver->type = TTY_DRIVER_TYPE_SERIAL,
	acm_tty_driver->subtype = SERIAL_TYPE_NORMAL,
	acm_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS,
	acm_tty_driver->init_termios = tty_std_termios;
	acm_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
	tty_set_operations(acm_tty_driver, &acm_ops);

783 784
	retval = tty_register_driver(acm_tty_driver);
	if (retval) {
785
		put_tty_driver(acm_tty_driver);
786
		return retval;
787
	}
Linus Torvalds's avatar
Linus Torvalds committed
788

789 790
	retval = usb_register(&acm_driver);
	if (retval) {
791 792
		tty_unregister_driver(acm_tty_driver);
		put_tty_driver(acm_tty_driver);
793
		return retval;
Linus Torvalds's avatar
Linus Torvalds committed
794 795
	}

Linus Torvalds's avatar
Linus Torvalds committed
796
	info(DRIVER_VERSION ":" DRIVER_DESC);
Linus Torvalds's avatar
Linus Torvalds committed
797

Linus Torvalds's avatar
Linus Torvalds committed
798 799 800 801 802 803
	return 0;
}

static void __exit acm_exit(void)
{
	usb_deregister(&acm_driver);
804 805
	tty_unregister_driver(acm_tty_driver);
	put_tty_driver(acm_tty_driver);
Linus Torvalds's avatar
Linus Torvalds committed
806 807 808 809 810
}

module_init(acm_init);
module_exit(acm_exit);

Linus Torvalds's avatar
Linus Torvalds committed
811 812
MODULE_AUTHOR( DRIVER_AUTHOR );
MODULE_DESCRIPTION( DRIVER_DESC );
Linus Torvalds's avatar
Linus Torvalds committed
813
MODULE_LICENSE("GPL");
Linus Torvalds's avatar
Linus Torvalds committed
814