X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fusb%2Fclass%2Fcdc-acm.c;h=98199628e394e66df0e3c1c7a42e0874d420d3c4;hb=refs%2Fheads%2Fvserver;hp=441817076a73be714a8e7269ee732862d0606e35;hpb=5273a3df6485dc2ad6aa7ddd441b9a21970f003b;p=linux-2.6.git diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 441817076..98199628e 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -5,6 +5,8 @@ * Copyright (c) 1999 Pavel Machek * Copyright (c) 1999 Johannes Erdfelt * Copyright (c) 2000 Vojtech Pavlik + * Copyright (c) 2004 Oliver Neukum + * Copyright (c) 2005 David Kubicek * * USB Abstract Control Model driver for USB modems and ISDN adapters * @@ -27,6 +29,8 @@ * v0.22 - probe only the control interface. if usbcore doesn't choose the * config we want, sysadmin changes bConfigurationValue in sysfs. * v0.23 - use softirq for rx processing, as needed by tty layer + * v0.24 - change probe method to evaluate CDC union descriptor + * v0.25 - downstream tasks paralelized to maximize throughput */ /* @@ -56,146 +60,169 @@ #include #include #include +#include #include #include +#include #include +#include +#include + +#include "cdc-acm.h" /* * Version Information */ -#define DRIVER_VERSION "v0.23" -#define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik" +#define DRIVER_VERSION "v0.25" +#define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik, David Kubicek" #define DRIVER_DESC "USB Abstract Control Model driver for USB modems and ISDN adapters" -/* - * CMSPAR, some architectures can't have space and mark parity. - */ - -#ifndef CMSPAR -#define CMSPAR 0 -#endif +static struct usb_driver acm_driver; +static struct tty_driver *acm_tty_driver; +static struct acm *acm_table[ACM_TTY_MINORS]; -/* - * Major and minor numbers. - */ +static DEFINE_MUTEX(open_mutex); -#define ACM_TTY_MAJOR 166 -#define ACM_TTY_MINORS 32 +#define ACM_READY(acm) (acm && acm->dev && acm->used) /* - * Requests. + * Functions for ACM control messages. */ -#define USB_RT_ACM (USB_TYPE_CLASS | USB_RECIP_INTERFACE) - -#define ACM_REQ_COMMAND 0x00 -#define ACM_REQ_RESPONSE 0x01 -#define ACM_REQ_SET_FEATURE 0x02 -#define ACM_REQ_GET_FEATURE 0x03 -#define ACM_REQ_CLEAR_FEATURE 0x04 - -#define ACM_REQ_SET_LINE 0x20 -#define ACM_REQ_GET_LINE 0x21 -#define ACM_REQ_SET_CONTROL 0x22 -#define ACM_REQ_SEND_BREAK 0x23 +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), + request, USB_RT_ACM, value, + acm->control->altsetting[0].desc.bInterfaceNumber, + buf, len, 5000); + dbg("acm_control_msg: rq: 0x%02x val: %#x len: %#x result: %d", request, value, len, retval); + return retval < 0 ? retval : 0; +} -/* - * IRQs. +/* devices aren't required to support these requests. + * the cdc acm descriptor tells whether they do... */ - -#define ACM_IRQ_NETWORK 0x00 -#define ACM_IRQ_LINE_STATE 0x20 +#define acm_set_control(acm, control) \ + acm_ctrl_msg(acm, USB_CDC_REQ_SET_CONTROL_LINE_STATE, control, NULL, 0) +#define acm_set_line(acm, line) \ + acm_ctrl_msg(acm, USB_CDC_REQ_SET_LINE_CODING, 0, line, sizeof *(line)) +#define acm_send_break(acm, ms) \ + acm_ctrl_msg(acm, USB_CDC_REQ_SEND_BREAK, ms, NULL, 0) /* - * Output control lines. + * Write buffer management. + * All of these assume proper locks taken by the caller. */ -#define ACM_CTRL_DTR 0x01 -#define ACM_CTRL_RTS 0x02 +static int acm_wb_alloc(struct acm *acm) +{ + int i, wbn; + struct acm_wb *wb; + + wbn = acm->write_current; + i = 0; + for (;;) { + wb = &acm->wb[wbn]; + if (!wb->use) { + wb->use = 1; + return wbn; + } + wbn = (wbn + 1) % ACM_NW; + if (++i >= ACM_NW) + return -1; + } +} -/* - * Input control lines and line errors. - */ +static void acm_wb_free(struct acm *acm, int wbn) +{ + acm->wb[wbn].use = 0; +} -#define ACM_CTRL_DCD 0x01 -#define ACM_CTRL_DSR 0x02 -#define ACM_CTRL_BRK 0x04 -#define ACM_CTRL_RI 0x08 +static int acm_wb_is_avail(struct acm *acm) +{ + int i, n; + + n = ACM_NW; + for (i = 0; i < ACM_NW; i++) { + n -= acm->wb[i].use; + } + return n; +} -#define ACM_CTRL_FRAMING 0x10 -#define ACM_CTRL_PARITY 0x20 -#define ACM_CTRL_OVERRUN 0x40 +static inline int acm_wb_is_used(struct acm *acm, int wbn) +{ + return acm->wb[wbn].use; +} /* - * Line speed and caracter encoding. + * Finish write. */ - -struct acm_line { - __u32 speed; - __u8 stopbits; - __u8 parity; - __u8 databits; -} __attribute__ ((packed)); +static void acm_write_done(struct acm *acm) +{ + unsigned long flags; + int wbn; + + spin_lock_irqsave(&acm->write_lock, flags); + acm->write_ready = 1; + wbn = acm->write_current; + acm_wb_free(acm, wbn); + acm->write_current = (wbn + 1) % ACM_NW; + spin_unlock_irqrestore(&acm->write_lock, flags); +} /* - * Internal driver structures. + * Poke write. */ +static int acm_write_start(struct acm *acm) +{ + unsigned long flags; + int wbn; + struct acm_wb *wb; + int rc; + + spin_lock_irqsave(&acm->write_lock, flags); + if (!acm->dev) { + spin_unlock_irqrestore(&acm->write_lock, flags); + return -ENODEV; + } -struct acm { - struct usb_device *dev; /* the corresponding usb device */ - struct usb_interface *control; /* control interface */ - struct usb_interface *data; /* data interface */ - struct tty_struct *tty; /* the corresponding tty */ - struct urb *ctrlurb, *readurb, *writeurb; /* urbs */ - struct acm_line line; /* line coding (bits, stop, parity) */ - struct work_struct work; /* work queue entry for line discipline waking up */ - struct tasklet_struct bh; /* rx processing */ - unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ - unsigned int ctrlout; /* output control lines (DTR, RTS) */ - unsigned int writesize; /* max packet size for the output bulk endpoint */ - unsigned int used; /* someone has this acm's device open */ - unsigned int minor; /* acm minor number */ - unsigned char throttle; /* throttled by tty layer */ - unsigned char clocal; /* termios CLOCAL */ -}; + if (!acm->write_ready) { + spin_unlock_irqrestore(&acm->write_lock, flags); + return 0; /* A white lie */ + } -static struct usb_driver acm_driver; -static struct tty_driver *acm_tty_driver; -static struct acm *acm_table[ACM_TTY_MINORS]; + wbn = acm->write_current; + if (!acm_wb_is_used(acm, wbn)) { + spin_unlock_irqrestore(&acm->write_lock, flags); + return 0; + } + wb = &acm->wb[wbn]; -#define ACM_READY(acm) (acm && acm->dev && acm->used) + acm->write_ready = 0; + spin_unlock_irqrestore(&acm->write_lock, flags); -/* - * Functions for ACM control messages. - */ + acm->writeurb->transfer_buffer = wb->buf; + acm->writeurb->transfer_dma = wb->dmah; + acm->writeurb->transfer_buffer_length = wb->len; + acm->writeurb->dev = acm->dev; -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), - request, USB_RT_ACM, value, - acm->control->altsetting[0].desc.bInterfaceNumber, - buf, len, HZ * 5); - dbg("acm_control_msg: rq: 0x%02x val: %#x len: %#x result: %d", request, value, len, retval); - return retval < 0 ? retval : 0; + if ((rc = usb_submit_urb(acm->writeurb, GFP_ATOMIC)) < 0) { + dbg("usb_submit_urb(write bulk) failed: %d", rc); + acm_write_done(acm); + } + return rc; } -/* devices aren't required to support these requests. - * the cdc acm descriptor tells whether they do... - */ -#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) - /* * Interrupt handlers for various ACM device responses */ /* control interface reports status changes with "interrupt" transfers */ -static void acm_ctrl_irq(struct urb *urb, struct pt_regs *regs) +static void acm_ctrl_irq(struct urb *urb) { struct acm *acm = urb->context; - struct usb_ctrlrequest *dr = urb->transfer_buffer; - unsigned char *data = (unsigned char *)(dr + 1); + struct usb_cdc_notification *dr = urb->transfer_buffer; + unsigned char *data; int newctrl; int status; @@ -217,16 +244,17 @@ static void acm_ctrl_irq(struct urb *urb, struct pt_regs *regs) if (!ACM_READY(acm)) goto exit; - switch (dr->bRequest) { + data = (unsigned char *)(dr + 1); + switch (dr->bNotificationType) { - case ACM_IRQ_NETWORK: + case USB_CDC_NOTIFY_NETWORK_CONNECTION: dbg("%s network", dr->wValue ? "connected to" : "disconnected from"); break; - case ACM_IRQ_LINE_STATE: + case USB_CDC_NOTIFY_SERIAL_STATE: - newctrl = le16_to_cpup((__u16 *) data); + newctrl = le16_to_cpu(get_unaligned((__le16 *) data)); if (acm->tty && !acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) { dbg("calling hangup"); @@ -244,8 +272,9 @@ static void acm_ctrl_irq(struct urb *urb, struct pt_regs *regs) break; default: - dbg("unknown control event received: request %d index %d len %d data0 %d data1 %d", - dr->bRequest, dr->wIndex, dr->wLength, data[0], data[1]); + dbg("unknown notification %d received: index %d len %d data0 %d data1 %d", + dr->bNotificationType, dr->wIndex, + dr->wLength, data[0], data[1]); break; } exit: @@ -256,80 +285,150 @@ exit: } /* data interface returns incoming bytes, or we got unthrottled */ -static void acm_read_bulk(struct urb *urb, struct pt_regs *regs) +static void acm_read_bulk(struct urb *urb) { - struct acm *acm = urb->context; + struct acm_rb *buf; + struct acm_ru *rcv = urb->context; + struct acm *acm = rcv->instance; + int status = urb->status; + dbg("Entering acm_read_bulk with status %d", urb->status); if (!ACM_READY(acm)) return; - if (urb->status) - 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); + if (status) + dev_dbg(&acm->data->dev, "bulk rx status %d", status); + + buf = rcv->buffer; + buf->size = urb->actual_length; + + if (likely(status == 0)) { + spin_lock(&acm->read_lock); + list_add_tail(&rcv->list, &acm->spare_read_urbs); + list_add_tail(&buf->list, &acm->filled_read_bufs); + spin_unlock(&acm->read_lock); + } else { + /* we drop the buffer due to an error */ + spin_lock(&acm->read_lock); + list_add_tail(&rcv->list, &acm->spare_read_urbs); + list_add(&buf->list, &acm->spare_read_bufs); + spin_unlock(&acm->read_lock); + /* nevertheless the tasklet must be kicked unconditionally + so the queue cannot dry up */ + } + tasklet_schedule(&acm->urb_task); } static void acm_rx_tasklet(unsigned long _acm) { struct acm *acm = (void *)_acm; - struct urb *urb = acm->readurb; + struct acm_rb *buf; struct tty_struct *tty = acm->tty; - unsigned char *data = urb->transfer_buffer; + struct acm_ru *rcv; + unsigned long flags; int i = 0; + dbg("Entering acm_rx_tasklet"); - if (urb->actual_length > 0 && !acm->throttle) { - 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); - } - tty_insert_flip_char(tty, data[i], 0); - } - tty_flip_buffer_push(tty); + if (!ACM_READY(acm) || acm->throttle) + return; + +next_buffer: + spin_lock_irqsave(&acm->read_lock, flags); + if (list_empty(&acm->filled_read_bufs)) { + spin_unlock_irqrestore(&acm->read_lock, flags); + goto urbs; } + buf = list_entry(acm->filled_read_bufs.next, + struct acm_rb, list); + list_del(&buf->list); + spin_unlock_irqrestore(&acm->read_lock, flags); + + dbg("acm_rx_tasklet: procesing buf 0x%p, size = %d", buf, buf->size); + + tty_buffer_request_room(tty, buf->size); + if (!acm->throttle) + tty_insert_flip_string(tty, buf->base, buf->size); + tty_flip_buffer_push(tty); + spin_lock(&acm->throttle_lock); if (acm->throttle) { - memmove(data, data + i, urb->actual_length - i); - urb->actual_length -= i; + dbg("Throtteling noticed"); + memmove(buf->base, buf->base + i, buf->size - i); + buf->size -= i; + spin_unlock(&acm->throttle_lock); + spin_lock_irqsave(&acm->read_lock, flags); + list_add(&buf->list, &acm->filled_read_bufs); + spin_unlock_irqrestore(&acm->read_lock, flags); return; } - - urb->actual_length = 0; - urb->dev = acm->dev; - - i = usb_submit_urb(urb, GFP_ATOMIC); - if (i) - dev_dbg(&acm->data->dev, "bulk rx resubmit %d\n", i); + spin_unlock(&acm->throttle_lock); + + spin_lock_irqsave(&acm->read_lock, flags); + list_add(&buf->list, &acm->spare_read_bufs); + spin_unlock_irqrestore(&acm->read_lock, flags); + goto next_buffer; + +urbs: + while (!list_empty(&acm->spare_read_bufs)) { + spin_lock_irqsave(&acm->read_lock, flags); + if (list_empty(&acm->spare_read_urbs)) { + spin_unlock_irqrestore(&acm->read_lock, flags); + return; + } + rcv = list_entry(acm->spare_read_urbs.next, + struct acm_ru, list); + list_del(&rcv->list); + spin_unlock_irqrestore(&acm->read_lock, flags); + + buf = list_entry(acm->spare_read_bufs.next, + struct acm_rb, list); + list_del(&buf->list); + + rcv->buffer = buf; + + usb_fill_bulk_urb(rcv->urb, acm->dev, + acm->rx_endpoint, + buf->base, + acm->readsize, + acm_read_bulk, rcv); + rcv->urb->transfer_dma = buf->dma; + rcv->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + dbg("acm_rx_tasklet: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf); + + /* This shouldn't kill the driver as unsuccessful URBs are returned to the + free-urbs-pool and resubmited ASAP */ + if (usb_submit_urb(rcv->urb, GFP_ATOMIC) < 0) { + list_add(&buf->list, &acm->spare_read_bufs); + spin_lock_irqsave(&acm->read_lock, flags); + list_add(&rcv->list, &acm->spare_read_urbs); + spin_unlock_irqrestore(&acm->read_lock, flags); + return; + } + } } /* data interface wrote those outgoing bytes */ -static void acm_write_bulk(struct urb *urb, struct pt_regs *regs) +static void acm_write_bulk(struct urb *urb) { struct acm *acm = (struct acm *)urb->context; - if (!ACM_READY(acm)) - return; - - if (urb->status) - dbg("nonzero write bulk status received: %d", urb->status); + dbg("Entering acm_write_bulk with status %d", urb->status); - schedule_work(&acm->work); + acm_write_done(acm); + acm_write_start(acm); + if (ACM_READY(acm)) + schedule_work(&acm->work); } -static void acm_softint(void *private) +static void acm_softint(struct work_struct *work) { - struct acm *acm = private; - struct tty_struct *tty = acm->tty; - + struct acm *acm = container_of(work, struct acm, work); + dbg("Entering acm_softint."); + if (!ACM_READY(acm)) return; - - if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) - (tty->ldisc.write_wakeup)(tty); - - wake_up_interruptible(&tty->write_wait); + tty_wakeup(acm->tty); } /* @@ -338,94 +437,133 @@ static void acm_softint(void *private) static int acm_tty_open(struct tty_struct *tty, struct file *filp) { - struct acm *acm = acm_table[tty->index]; + struct acm *acm; + int rv = -EINVAL; + int i; + dbg("Entering acm_tty_open."); + + mutex_lock(&open_mutex); + acm = acm_table[tty->index]; if (!acm || !acm->dev) - return -EINVAL; + goto err_out; + else + rv = 0; tty->driver_data = acm; acm->tty = tty; - lock_kernel(); + /* 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; if (acm->used++) { - unlock_kernel(); - return 0; + goto done; } - unlock_kernel(); - acm->ctrlurb->dev = acm->dev; - if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) + if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) { dbg("usb_submit_urb(ctrl irq) failed"); + goto bail_out; + } - acm->readurb->dev = acm->dev; - if (usb_submit_urb(acm->readurb, GFP_KERNEL)) - dbg("usb_submit_urb(read bulk) failed"); + if (0 > acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS)) + goto full_bailout; - acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS); + INIT_LIST_HEAD(&acm->spare_read_urbs); + INIT_LIST_HEAD(&acm->spare_read_bufs); + INIT_LIST_HEAD(&acm->filled_read_bufs); + for (i = 0; i < acm->rx_buflimit; i++) { + list_add(&(acm->ru[i].list), &acm->spare_read_urbs); + } + for (i = 0; i < acm->rx_buflimit; i++) { + list_add(&(acm->rb[i].list), &acm->spare_read_bufs); + } - /* 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; + tasklet_schedule(&acm->urb_task); - return 0; +done: +err_out: + mutex_unlock(&open_mutex); + return rv; + +full_bailout: + usb_kill_urb(acm->ctrlurb); +bail_out: + acm->used--; + mutex_unlock(&open_mutex); + return -EIO; +} + +static void acm_tty_unregister(struct acm *acm) +{ + int i,nr; + + nr = acm->rx_buflimit; + tty_unregister_device(acm_tty_driver, acm->minor); + usb_put_intf(acm->control); + acm_table[acm->minor] = NULL; + usb_free_urb(acm->ctrlurb); + usb_free_urb(acm->writeurb); + for (i = 0; i < nr; i++) + usb_free_urb(acm->ru[i].urb); + kfree(acm); } static void acm_tty_close(struct tty_struct *tty, struct file *filp) { struct acm *acm = tty->driver_data; + int i,nr; if (!acm || !acm->used) return; + nr = acm->rx_buflimit; + mutex_lock(&open_mutex); if (!--acm->used) { if (acm->dev) { acm_set_control(acm, acm->ctrlout = 0); - usb_unlink_urb(acm->ctrlurb); - usb_unlink_urb(acm->writeurb); - usb_unlink_urb(acm->readurb); - } else { - tty_unregister_device(acm_tty_driver, acm->minor); - acm_table[acm->minor] = NULL; - usb_free_urb(acm->ctrlurb); - usb_free_urb(acm->readurb); - usb_free_urb(acm->writeurb); - kfree(acm); - } + usb_kill_urb(acm->ctrlurb); + usb_kill_urb(acm->writeurb); + for (i = 0; i < nr; i++) + usb_kill_urb(acm->ru[i].urb); + } else + acm_tty_unregister(acm); } + mutex_unlock(&open_mutex); } -static int acm_tty_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count) +static int acm_tty_write(struct tty_struct *tty, const unsigned char *buf, int count) { struct acm *acm = tty->driver_data; int stat; + unsigned long flags; + int wbn; + struct acm_wb *wb; + + dbg("Entering acm_tty_write to write %d bytes,", count); if (!ACM_READY(acm)) return -EINVAL; - if (acm->writeurb->status == -EINPROGRESS) - return 0; if (!count) return 0; - count = (count > acm->writesize) ? acm->writesize : count; - - if (from_user) { - if (copy_from_user(acm->writeurb->transfer_buffer, (void __user *)buf, count)) - return -EFAULT; - } else - memcpy(acm->writeurb->transfer_buffer, buf, count); + spin_lock_irqsave(&acm->write_lock, flags); + if ((wbn = acm_wb_alloc(acm)) < 0) { + spin_unlock_irqrestore(&acm->write_lock, flags); + acm_write_start(acm); + return 0; + } + wb = &acm->wb[wbn]; - acm->writeurb->transfer_buffer_length = count; - acm->writeurb->dev = acm->dev; + count = (count > acm->writesize) ? acm->writesize : count; + dbg("Get %d bytes...", count); + memcpy(wb->buf, buf, count); + wb->len = count; + spin_unlock_irqrestore(&acm->write_lock, flags); - /* GFP_KERNEL probably works if from_user */ - stat = usb_submit_urb(acm->writeurb, GFP_ATOMIC); - if (stat < 0) { - dbg("usb_submit_urb(write bulk) failed"); + if ((stat = acm_write_start(acm)) < 0) return stat; - } - return count; } @@ -434,7 +572,11 @@ static int acm_tty_write_room(struct tty_struct *tty) struct acm *acm = tty->driver_data; if (!ACM_READY(acm)) return -EINVAL; - return acm->writeurb->status == -EINPROGRESS ? 0 : acm->writesize; + /* + * Do not let the line discipline to know that we have a reserve, + * or it might get too enthusiastic. + */ + return (acm->write_ready && acm_wb_is_avail(acm)) ? acm->writesize : 0; } static int acm_tty_chars_in_buffer(struct tty_struct *tty) @@ -442,7 +584,10 @@ static int acm_tty_chars_in_buffer(struct tty_struct *tty) struct acm *acm = tty->driver_data; if (!ACM_READY(acm)) return -EINVAL; - return acm->writeurb->status == -EINPROGRESS ? acm->writeurb->transfer_buffer_length : 0; + /* + * This is inaccurate (overcounts), but it works. + */ + return (ACM_NW - acm_wb_is_avail(acm)) * acm->writesize; } static void acm_tty_throttle(struct tty_struct *tty) @@ -450,7 +595,9 @@ static void acm_tty_throttle(struct tty_struct *tty) struct acm *acm = tty->driver_data; if (!ACM_READY(acm)) return; + spin_lock_bh(&acm->throttle_lock); acm->throttle = 1; + spin_unlock_bh(&acm->throttle_lock); } static void acm_tty_unthrottle(struct tty_struct *tty) @@ -458,9 +605,10 @@ static void acm_tty_unthrottle(struct tty_struct *tty) struct acm *acm = tty->driver_data; if (!ACM_READY(acm)) return; + spin_lock_bh(&acm->throttle_lock); acm->throttle = 0; - if (acm->readurb->status != -EINPROGRESS) - acm_read_bulk(acm->readurb, NULL); + spin_unlock_bh(&acm->throttle_lock); + tasklet_schedule(&acm->urb_task); } static void acm_tty_break_ctl(struct tty_struct *tty, int state) @@ -517,7 +665,7 @@ static int acm_tty_ioctl(struct tty_struct *tty, struct file *file, unsigned int return -ENOIOCTLCMD; } -static __u32 acm_tty_speed[] = { +static const __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, @@ -525,40 +673,42 @@ static __u32 acm_tty_speed[] = { 2500000, 3000000, 3500000, 4000000 }; -static __u8 acm_tty_size[] = { +static const __u8 acm_tty_size[] = { 5, 6, 7, 8 }; -static void acm_tty_set_termios(struct tty_struct *tty, struct termios *termios_old) +static void acm_tty_set_termios(struct tty_struct *tty, struct ktermios *termios_old) { struct acm *acm = tty->driver_data; - struct termios *termios = tty->termios; - struct acm_line newline; + struct ktermios *termios = tty->termios; + struct usb_cdc_line_coding newline; int newctrl = acm->ctrlout; if (!ACM_READY(acm)) return; - newline.speed = cpu_to_le32p(acm_tty_speed + + newline.dwDTERate = 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 ? + newline.bCharFormat = termios->c_cflag & CSTOPB ? 2 : 0; + newline.bParityType = 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]; + newline.bDataBits = acm_tty_size[(termios->c_cflag & CSIZE) >> 4]; acm->clocal = ((termios->c_cflag & CLOCAL) != 0); - if (!newline.speed) { - newline.speed = acm->line.speed; + if (!newline.dwDTERate) { + newline.dwDTERate = acm->line.dwDTERate; 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); + if (memcmp(&acm->line, &newline, sizeof newline)) { + memcpy(&acm->line, &newline, sizeof newline); + dbg("set line: %d %d %d %d", le32_to_cpu(newline.dwDTERate), + newline.bCharFormat, newline.bParityType, + newline.bDataBits); acm_set_line(acm, &acm->line); } } @@ -567,190 +717,356 @@ static void acm_tty_set_termios(struct tty_struct *tty, struct termios *termios_ * USB probe and disconnect routines. */ +/* Little helper: write buffers free */ +static void acm_write_buffers_free(struct acm *acm) +{ + int i; + struct acm_wb *wb; + + for (wb = &acm->wb[0], i = 0; i < ACM_NW; i++, wb++) { + usb_buffer_free(acm->dev, acm->writesize, wb->buf, wb->dmah); + } +} + +/* Little helper: write buffers allocate */ +static int acm_write_buffers_alloc(struct acm *acm) +{ + int i; + struct acm_wb *wb; + + for (wb = &acm->wb[0], i = 0; i < ACM_NW; i++, wb++) { + wb->buf = usb_buffer_alloc(acm->dev, acm->writesize, GFP_KERNEL, + &wb->dmah); + if (!wb->buf) { + while (i != 0) { + --i; + --wb; + usb_buffer_free(acm->dev, acm->writesize, + wb->buf, wb->dmah); + } + return -ENOMEM; + } + } + return 0; +} + static int acm_probe (struct usb_interface *intf, const struct usb_device_id *id) { - struct usb_device *dev; + struct usb_cdc_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); struct acm *acm; - struct usb_host_config *cfacm; - struct usb_interface *data = NULL; - struct usb_host_interface *ifcom, *ifdata = NULL; - struct usb_endpoint_descriptor *epctrl = NULL; - struct usb_endpoint_descriptor *epread = NULL; - struct usb_endpoint_descriptor *epwrite = NULL; - int readsize, ctrlsize, minor, j; - unsigned char *buf; - - dev = interface_to_usbdev (intf); - - cfacm = dev->actconfig; + int minor; + int ctrlsize,readsize; + u8 *buf; + u8 ac_management_function = 0; + u8 call_management_function = 0; + int call_interface_num = -1; + int data_interface_num; + unsigned long quirks; + int num_rx_buf; + int i; + + /* normal quirks */ + quirks = (unsigned long)id->driver_info; + num_rx_buf = (quirks == SINGLE_RX_URB) ? 1 : ACM_NR; + + /* handle quirks deadly to normal probing*/ + if (quirks == NO_UNION_NORMAL) { + data_interface = usb_ifnum_to_if(usb_dev, 1); + control_interface = usb_ifnum_to_if(usb_dev, 0); + goto skip_normal_probe; + } - /* We know we're probe()d with the control interface. */ - ifcom = intf->cur_altsetting; - - /* ACM doesn't guarantee the data interface is - * adjacent to the control interface, or that if one - * is there it's not for call management ... so find - * it - */ - for (j = 0; j < cfacm->desc.bNumInterfaces; j++) { - ifdata = cfacm->interface[j]->cur_altsetting; - data = cfacm->interface[j]; - - if (ifdata->desc.bInterfaceClass == 10 && - ifdata->desc.bNumEndpoints == 2) { - epctrl = &ifcom->endpoint[0].desc; - epread = &ifdata->endpoint[0].desc; - epwrite = &ifdata->endpoint[1].desc; - - if ((epctrl->bEndpointAddress & 0x80) != 0x80 || - (epctrl->bmAttributes & 3) != 3 || - (epread->bmAttributes & 3) != 2 || - (epwrite->bmAttributes & 3) != 2 || - ((epread->bEndpointAddress & 0x80) ^ (epwrite->bEndpointAddress & 0x80)) != 0x80) - goto next_interface; - - if ((epread->bEndpointAddress & 0x80) != 0x80) { - epread = &ifdata->endpoint[1].desc; - epwrite = &ifdata->endpoint[0].desc; - } - dbg("found data interface at %d\n", j); - break; - } else { -next_interface: - ifdata = NULL; - data = NULL; + /* normal probing*/ + if (!buffer) { + err("Wierd descriptor references\n"); + return -EINVAL; + } + + if (!buflen) { + if (intf->cur_altsetting->endpoint->extralen && intf->cur_altsetting->endpoint->extra) { + dev_dbg(&intf->dev,"Seeking extra descriptors on endpoint"); + buflen = intf->cur_altsetting->endpoint->extralen; + buffer = intf->cur_altsetting->endpoint->extra; + } else { + err("Zero length descriptor references\n"); + return -EINVAL; + } + } + + while (buflen > 0) { + if (buffer [1] != USB_DT_CS_INTERFACE) { + err("skipping garbage\n"); + goto next_desc; + } + + switch (buffer [2]) { + case USB_CDC_UNION_TYPE: /* we've found it */ + if (union_header) { + err("More than one union descriptor, skipping ..."); + goto next_desc; } + union_header = (struct usb_cdc_union_desc *) + buffer; + break; + case USB_CDC_COUNTRY_TYPE: /* maybe somehow export */ + break; /* for now we ignore it */ + case USB_CDC_HEADER_TYPE: /* maybe check version */ + break; /* for now we ignore it */ + case USB_CDC_ACM_TYPE: + ac_management_function = buffer[3]; + break; + case USB_CDC_CALL_MANAGEMENT_TYPE: + call_management_function = buffer[3]; + call_interface_num = buffer[4]; + if ((call_management_function & 3) != 3) + err("This device cannot do calls on its own. It is no modem."); + break; + + default: + err("Ignoring extra header, type %d, length %d", buffer[2], buffer[0]); + break; } +next_desc: + buflen -= buffer[0]; + buffer += buffer[0]; + } - /* there's been a problem */ - if (!ifdata) { - dbg("interface not found (%p)\n", ifdata); - return -ENODEV; + if (!union_header) { + if (call_interface_num > 0) { + dev_dbg(&intf->dev,"No union descriptor, using call management descriptor"); + data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = call_interface_num)); + control_interface = intf; + } else { + dev_dbg(&intf->dev,"No union descriptor, giving up"); + return -ENODEV; + } + } else { + control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0); + data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = union_header->bSlaveInterface0)); + if (!control_interface || !data_interface) { + dev_dbg(&intf->dev,"no interfaces"); + return -ENODEV; + } + } + + if (data_interface_num != call_interface_num) + dev_dbg(&intf->dev,"Seperate call control interface. That is not fully supported."); - } +skip_normal_probe: - for (minor = 0; minor < ACM_TTY_MINORS && acm_table[minor]; minor++); - if (acm_table[minor]) { - err("no more free acm devices"); - return -ENODEV; - } + /*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."); - if (!(acm = kmalloc(sizeof(struct acm), GFP_KERNEL))) { - err("out of memory"); - return -ENOMEM; - } - memset(acm, 0, sizeof(struct acm)); - - ctrlsize = epctrl->wMaxPacketSize; - readsize = epread->wMaxPacketSize; - acm->writesize = epwrite->wMaxPacketSize; - acm->control = intf; - acm->data = data; - acm->minor = minor; - acm->dev = dev; - - acm->bh.func = acm_rx_tasklet; - acm->bh.data = (unsigned long) acm; - INIT_WORK(&acm->work, acm_softint, acm); - - if (!(buf = kmalloc(ctrlsize + readsize + acm->writesize, GFP_KERNEL))) { - err("out of memory"); - kfree(acm); - return -ENOMEM; - } + t = control_interface; + control_interface = data_interface; + data_interface = t; + } else { + return -EINVAL; + } + } + + if (usb_interface_claimed(data_interface)) { /* valid in this context */ + dev_dbg(&intf->dev,"The data interface isn't available"); + return -EBUSY; + } - acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL); - if (!acm->ctrlurb) { - err("out of memory"); - kfree(acm); - kfree(buf); - return -ENOMEM; - } - acm->readurb = usb_alloc_urb(0, GFP_KERNEL); - if (!acm->readurb) { - err("out of memory"); - usb_free_urb(acm->ctrlurb); - kfree(acm); - kfree(buf); - return -ENOMEM; - } - acm->writeurb = usb_alloc_urb(0, GFP_KERNEL); - if (!acm->writeurb) { - err("out of memory"); - usb_free_urb(acm->readurb); - usb_free_urb(acm->ctrlurb); - kfree(acm); - kfree(buf); - return -ENOMEM; - } - usb_fill_int_urb(acm->ctrlurb, dev, usb_rcvintpipe(dev, epctrl->bEndpointAddress), - buf, ctrlsize, acm_ctrl_irq, acm, epctrl->bInterval); + 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; - usb_fill_bulk_urb(acm->readurb, dev, usb_rcvbulkpipe(dev, epread->bEndpointAddress), - buf += ctrlsize, readsize, acm_read_bulk, acm); - acm->readurb->transfer_flags |= URB_NO_FSBR; - usb_fill_bulk_urb(acm->writeurb, dev, usb_sndbulkpipe(dev, epwrite->bEndpointAddress), - buf += readsize, acm->writesize, acm_write_bulk, acm); - acm->writeurb->transfer_flags |= URB_NO_FSBR; + /* workaround for switched endpoints */ + if (!usb_endpoint_dir_in(epread)) { + /* descriptors are swapped */ + struct usb_endpoint_descriptor *t; + dev_dbg(&intf->dev,"The data interface has switched endpoints"); + + t = epread; + epread = epwrite; + epwrite = t; + } + dbg("interfaces are valid"); + for (minor = 0; minor < ACM_TTY_MINORS && acm_table[minor]; minor++); - dev_info(&intf->dev, "ttyACM%d: USB ACM device", minor); + if (minor == ACM_TTY_MINORS) { + err("no more free acm devices"); + return -ENODEV; + } - acm_set_control(acm, acm->ctrlout); + if (!(acm = kzalloc(sizeof(struct acm), GFP_KERNEL))) { + dev_dbg(&intf->dev, "out of memory (acm kzalloc)"); + goto alloc_fail; + } - acm->line.speed = cpu_to_le32(9600); - acm->line.databits = 8; - acm_set_line(acm, &acm->line); + ctrlsize = le16_to_cpu(epctrl->wMaxPacketSize); + readsize = le16_to_cpu(epread->wMaxPacketSize)* ( quirks == SINGLE_RX_URB ? 1 : 2); + acm->writesize = le16_to_cpu(epwrite->wMaxPacketSize); + acm->control = control_interface; + acm->data = data_interface; + acm->minor = minor; + acm->dev = usb_dev; + acm->ctrl_caps = ac_management_function; + acm->ctrlsize = ctrlsize; + acm->readsize = readsize; + acm->rx_buflimit = num_rx_buf; + acm->urb_task.func = acm_rx_tasklet; + acm->urb_task.data = (unsigned long) acm; + INIT_WORK(&acm->work, acm_softint); + spin_lock_init(&acm->throttle_lock); + spin_lock_init(&acm->write_lock); + spin_lock_init(&acm->read_lock); + acm->write_ready = 1; + acm->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress); + + buf = usb_buffer_alloc(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma); + if (!buf) { + dev_dbg(&intf->dev, "out of memory (ctrl buffer alloc)"); + goto alloc_fail2; + } + acm->ctrl_buffer = buf; - if ( (j = usb_driver_claim_interface(&acm_driver, data, acm)) != 0) { - err("claim failed"); - usb_free_urb(acm->ctrlurb); - usb_free_urb(acm->readurb); - usb_free_urb(acm->writeurb); - kfree(acm); - kfree(buf); - return j; - } + if (acm_write_buffers_alloc(acm) < 0) { + dev_dbg(&intf->dev, "out of memory (write buffer alloc)"); + goto alloc_fail4; + } - tty_register_device(acm_tty_driver, minor, &intf->dev); + acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL); + if (!acm->ctrlurb) { + dev_dbg(&intf->dev, "out of memory (ctrlurb kmalloc)"); + goto alloc_fail5; + } + for (i = 0; i < num_rx_buf; i++) { + struct acm_ru *rcv = &(acm->ru[i]); - acm_table[minor] = acm; - usb_set_intfdata (intf, acm); - return 0; + if (!(rcv->urb = usb_alloc_urb(0, GFP_KERNEL))) { + dev_dbg(&intf->dev, "out of memory (read urbs usb_alloc_urb)"); + goto alloc_fail7; + } + + rcv->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + rcv->instance = acm; + } + for (i = 0; i < num_rx_buf; i++) { + struct acm_rb *buf = &(acm->rb[i]); + + if (!(buf->base = usb_buffer_alloc(acm->dev, readsize, GFP_KERNEL, &buf->dma))) { + dev_dbg(&intf->dev, "out of memory (read bufs usb_buffer_alloc)"); + goto alloc_fail7; + } + } + acm->writeurb = usb_alloc_urb(0, GFP_KERNEL); + if (!acm->writeurb) { + dev_dbg(&intf->dev, "out of memory (writeurb kmalloc)"); + goto alloc_fail7; + } + + usb_fill_int_urb(acm->ctrlurb, usb_dev, usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress), + acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm, epctrl->bInterval); + acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + acm->ctrlurb->transfer_dma = acm->ctrl_dma; + + usb_fill_bulk_urb(acm->writeurb, usb_dev, usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress), + NULL, acm->writesize, acm_write_bulk, acm); + acm->writeurb->transfer_flags |= URB_NO_FSBR | URB_NO_TRANSFER_DMA_MAP; + + dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor); + + acm_set_control(acm, acm->ctrlout); + + acm->line.dwDTERate = cpu_to_le32(9600); + acm->line.bDataBits = 8; + acm_set_line(acm, &acm->line); + + usb_driver_claim_interface(&acm_driver, data_interface, acm); + + usb_get_intf(control_interface); + tty_register_device(acm_tty_driver, minor, &control_interface->dev); + + acm_table[minor] = acm; + usb_set_intfdata (intf, acm); + return 0; + +alloc_fail7: + for (i = 0; i < num_rx_buf; i++) + usb_buffer_free(usb_dev, acm->readsize, acm->rb[i].base, acm->rb[i].dma); + for (i = 0; i < num_rx_buf; i++) + usb_free_urb(acm->ru[i].urb); + usb_free_urb(acm->ctrlurb); +alloc_fail5: + acm_write_buffers_free(acm); +alloc_fail4: + usb_buffer_free(usb_dev, ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); +alloc_fail2: + kfree(acm); +alloc_fail: + return -ENOMEM; } static void acm_disconnect(struct usb_interface *intf) { struct acm *acm = usb_get_intfdata (intf); + struct usb_device *usb_dev = interface_to_usbdev(intf); + int i; if (!acm || !acm->dev) { dbg("disconnect on nonexisting interface"); return; } + mutex_lock(&open_mutex); + if (!usb_get_intfdata(intf)) { + mutex_unlock(&open_mutex); + return; + } acm->dev = NULL; - usb_set_intfdata (intf, NULL); + usb_set_intfdata(acm->control, NULL); + usb_set_intfdata(acm->data, NULL); - usb_unlink_urb(acm->ctrlurb); - usb_unlink_urb(acm->readurb); - usb_unlink_urb(acm->writeurb); + tasklet_disable(&acm->urb_task); - kfree(acm->ctrlurb->transfer_buffer); + usb_kill_urb(acm->ctrlurb); + usb_kill_urb(acm->writeurb); + for (i = 0; i < acm->rx_buflimit; i++) + usb_kill_urb(acm->ru[i].urb); - usb_driver_release_interface(&acm_driver, acm->data); + INIT_LIST_HEAD(&acm->filled_read_bufs); + INIT_LIST_HEAD(&acm->spare_read_bufs); + + tasklet_enable(&acm->urb_task); + + flush_scheduled_work(); /* wait for acm_softint */ + + acm_write_buffers_free(acm); + usb_buffer_free(usb_dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); + for (i = 0; i < acm->rx_buflimit; i++) + usb_buffer_free(usb_dev, acm->readsize, acm->rb[i].base, acm->rb[i].dma); + + usb_driver_release_interface(&acm_driver, intf == acm->control ? acm->data : intf); if (!acm->used) { - tty_unregister_device(acm_tty_driver, acm->minor); - acm_table[acm->minor] = NULL; - usb_free_urb(acm->ctrlurb); - usb_free_urb(acm->readurb); - usb_free_urb(acm->writeurb); - kfree(acm); + acm_tty_unregister(acm); + mutex_unlock(&open_mutex); return; } + mutex_unlock(&open_mutex); + if (acm->tty) tty_hangup(acm->tty); } @@ -760,22 +1076,43 @@ static void acm_disconnect(struct usb_interface *intf) */ static struct usb_device_id acm_ids[] = { + /* quirky and broken devices */ + { USB_DEVICE(0x0870, 0x0001), /* Metricom GS Modem */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x0482, 0x0203), /* KYOCERA AH-K3001V */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x079b, 0x000f), /* BT On-Air USB MODEM */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x0ace, 0x1608), /* ZyDAS 56K USB MODEM */ + .driver_info = SINGLE_RX_URB, /* firmware bug */ + }, + { USB_DEVICE(0x0ace, 0x1611), /* ZyDAS 56K USB MODEM - new version */ + .driver_info = SINGLE_RX_URB, /* firmware bug */ + }, /* 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!! */ + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_V25TER) }, + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_PCCA101) }, + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_PCCA101_WAKE) }, + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_GSM) }, + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_3G ) }, + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_CDMA) }, + + /* NOTE: COMM/ACM/0xff is likely MSFT RNDIS ... NOT a modem!! */ { } }; MODULE_DEVICE_TABLE (usb, acm_ids); static struct usb_driver acm_driver = { - .owner = THIS_MODULE, .name = "cdc_acm", .probe = acm_probe, .disconnect = acm_disconnect, @@ -786,7 +1123,7 @@ static struct usb_driver acm_driver = { * TTY driver structures. */ -static struct tty_operations acm_ops = { +static const struct tty_operations acm_ops = { .open = acm_tty_open, .close = acm_tty_close, .write = acm_tty_write, @@ -814,12 +1151,11 @@ static int __init acm_init(void) acm_tty_driver->owner = THIS_MODULE, acm_tty_driver->driver_name = "acm", acm_tty_driver->name = "ttyACM", - acm_tty_driver->devfs_name = "usb/acm/", 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->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; 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);