| Start/ | End/ | |||
| True | False | - | Line | Source |
| 1 | /* | |||
| 2 | * cdc-acm.c | |||
| 3 | * | |||
| 4 | * Copyright (c) 1999 Armin Fuerst <fuerst@in.tum.de> | |||
| 5 | * Copyright (c) 1999 Pavel Machek <pavel@suse.cz> | |||
| 6 | * Copyright (c) 1999 Johannes Erdfelt <johannes@erdfelt.com> | |||
| 7 | * Copyright (c) 2000 Vojtech Pavlik <vojtech@suse.cz> | |||
| 8 | * Copyright (c) 2004 Oliver Neukum <oliver@neukum.name> | |||
| 9 | * Copyright (c) 2005 David Kubicek <dave@awk.cz> | |||
| 10 | * | |||
| 11 | * USB Abstract Control Model driver for USB modems and ISDN adapters | |||
| 12 | * | |||
| 13 | * Sponsored by SuSE | |||
| 14 | * | |||
| 15 | * ChangeLog: | |||
| 16 | * v0.9 - thorough cleaning, URBification, almost a rewrite | |||
| 17 | * v0.10 - some more cleanups | |||
| 18 | * v0.11 - fixed flow control, read error doesn't stop reads | |||
| 19 | * v0.12 - added TIOCM ioctls, added break handling, made struct acm kmalloced | |||
| 20 | * v0.13 - added termios, added hangup | |||
| 21 | * v0.14 - sized down struct acm | |||
| 22 | * v0.15 - fixed flow control again - characters could be lost | |||
| 23 | * v0.16 - added code for modems with swapped data and control interfaces | |||
| 24 | * v0.17 - added new style probing | |||
| 25 | * v0.18 - fixed new style probing for devices with more configurations | |||
| 26 | * v0.19 - fixed CLOCAL handling (thanks to Richard Shih-Ping Chan) | |||
| 27 | * v0.20 - switched to probing on interface (rather than device) class | |||
| 28 | * v0.21 - revert to probing on device for devices with multiple configs | |||
| 29 | * v0.22 - probe only the control interface. if usbcore doesn't choose the | |||
| 30 | * config we want, sysadmin changes bConfigurationValue in sysfs. | |||
| 31 | * v0.23 - use softirq for rx processing, as needed by tty layer | |||
| 32 | * v0.24 - change probe method to evaluate CDC union descriptor | |||
| 33 | * v0.25 - downstream tasks paralelized to maximize throughput | |||
| 34 | */ | |||
| 35 | ||||
| 36 | /* | |||
| 37 | * This program is free software; you can redistribute it and/or modify | |||
| 38 | * it under the terms of the GNU General Public License as published by | |||
| 39 | * the Free Software Foundation; either version 2 of the License, or | |||
| 40 | * (at your option) any later version. | |||
| 41 | * | |||
| 42 | * This program is distributed in the hope that it will be useful, | |||
| 43 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| 44 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| 45 | * GNU General Public License for more details. | |||
| 46 | * | |||
| 47 | * You should have received a copy of the GNU General Public License | |||
| 48 | * along with this program; if not, write to the Free Software | |||
| 49 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |||
| 50 | */ | |||
| 51 | ||||
| 52 | #undef DEBUG | |||
| 53 | ||||
| 54 | #include <linux/kernel.h> | |||
| 55 | #include <linux/errno.h> | |||
| 56 | #include <linux/init.h> | |||
| 57 | #include <linux/slab.h> | |||
| 58 | #include <linux/tty.h> | |||
| 59 | #include <linux/tty_driver.h> | |||
| 60 | #include <linux/tty_flip.h> | |||
| 61 | #include <linux/module.h> | |||
| 62 | #include <linux/smp_lock.h> | |||
| 63 | #include <asm/uaccess.h> | |||
| 64 | #include <linux/usb.h> | |||
| 65 | #include <linux/usb_cdc.h> | |||
| 66 | #include <asm/byteorder.h> | |||
| 67 | #include <asm/unaligned.h> | |||
| 68 | #include <linux/list.h> | |||
| 69 | ||||
| 70 | #include "cdc-acm.h" | |||
| 71 | ||||
| 72 | /* | |||
| 73 | * Version Information | |||
| 74 | */ | |||
| 75 | #define DRIVER_VERSION "v0.25" | |||
| 76 | #define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik, David Kubicek" | |||
| 77 | #define DRIVER_DESC "USB Abstract Control Model driver for USB modems and ISDN adapters" | |||
| 78 | ||||
| 79 | static struct usb_driver acm_driver; | |||
| 80 | static struct tty_driver *acm_tty_driver; | |||
| 81 | static struct acm *acm_table[ACM_TTY_MINORS]; | |||
| 82 | ||||
| 83 | static DECLARE_MUTEX(open_sem); | |||
| 84 | ||||
| 85 | #define ACM_READY(acm) (acm && acm->dev && acm->used) | |||
| 86 | ||||
| 87 | /* | |||
| 88 | * Functions for ACM control messages. | |||
| 89 | */ | |||
| 90 | ||||
| 0 | 0 | - | 91 | static int acm_ctrl_msg(struct acm *acm, int request, int value, void *buf, int len) |
| 92 | { | |||
| 93 | int retval = usb_control_msg(acm->dev, usb_sndctrlpipe(acm->dev, 0), | |||
| 94 | request, USB_RT_ACM, value, | |||
| 95 | acm->control->altsetting[0].desc.bInterfaceNumber, | |||
| 96 | buf, len, 5000); | |||
| 97 | dbg("acm_control_msg: rq: 0x%02x val: %#x len: %#x result: %d", request, value, len, retval); | |||
| 0 | 0 | - | 97 | do-while (0) |
| 98 | return retval < 0 ? retval : 0; | |||
| 0 | 0 | - | 98 | ternary-?: retval < 0 |
| 0 | - | 98 | return retval < 0 ? retval : 0 | |
| 99 | } | |||
| 100 | ||||
| 101 | /* devices aren't required to support these requests. | |||
| 102 | * the cdc acm descriptor tells whether they do... | |||
| 103 | */ | |||
| 104 | #define acm_set_control(acm, control) \ | |||
| 105 | acm_ctrl_msg(acm, USB_CDC_REQ_SET_CONTROL_LINE_STATE, control, NULL, 0) | |||
| 106 | #define acm_set_line(acm, line) \ | |||
| 107 | acm_ctrl_msg(acm, USB_CDC_REQ_SET_LINE_CODING, 0, line, sizeof *(line)) | |||
| 108 | #define acm_send_break(acm, ms) \ | |||
| 109 | acm_ctrl_msg(acm, USB_CDC_REQ_SEND_BREAK, ms, NULL, 0) | |||
| 110 | ||||
| 111 | /* | |||
| 112 | * Write buffer management. | |||
| 113 | * All of these assume proper locks taken by the caller. | |||
| 114 | */ | |||
| 115 | ||||
| 0 | 0 | - | 116 | static int acm_wb_alloc(struct acm *acm) |
| 117 | { | |||
| 118 | int i, wbn; | |||
| 119 | struct acm_wb *wb; | |||
| 120 | ||||
| 121 | wbn = acm->write_current; | |||
| 122 | i = 0; | |||
| 0 | 0 | - | 123 | for (;;) { |
| 124 | wb = &acm->wb[wbn]; | |||
| 0 | 0 | - | 125 | if (!wb->use) { |
| 126 | wb->use = 1; | |||
| 0 | - | 127 | return wbn; | |
| 128 | } | |||
| 129 | wbn = (wbn + 1) % ACM_NWB; | |||
| 0 | 0 | - | 130 | if (++i >= ACM_NWB) |
| 0 | - | 131 | return -1; | |
| 132 | } | |||
| 133 | } | |||
| 134 | ||||
| 0 | 0 | - | 135 | static void acm_wb_free(struct acm *acm, int wbn) |
| 136 | { | |||
| 137 | acm->wb[wbn].use = 0; | |||
| 138 | } | |||
| 139 | ||||
| 0 | 0 | - | 140 | static int acm_wb_is_avail(struct acm *acm) |
| 141 | { | |||
| 142 | int i, n; | |||
| 143 | ||||
| 144 | n = 0; | |||
| 0 | 0 | - | 145 | for (i = 0; i < ACM_NWB; i++) { |
| 0 | 0 | - | 146 | if (!acm->wb[i].use) |
| 147 | n++; | |||
| 148 | } | |||
| 0 | - | 149 | return n; | |
| 150 | } | |||
| 151 | ||||
| 0 | 0 | - | 152 | static inline int acm_wb_is_used(struct acm *acm, int wbn) |
| 153 | { | |||
| 0 | - | 154 | return acm->wb[wbn].use; | |
| 155 | } | |||
| 156 | ||||
| 157 | /* | |||
| 158 | * Finish write. | |||
| 159 | */ | |||
| 0 | 0 | - | 160 | static void acm_write_done(struct acm *acm) |
| 161 | { | |||
| 162 | unsigned long flags; | |||
| 163 | int wbn; | |||
| 164 | ||||
| 165 | spin_lock_irqsave(&acm->write_lock, flags); | |||
| 165 | do | |||
| 165 | do | |||
| 0 | 0 | - | 165 | do-while (0) |
| 0 | 0 | - | 165 | do-while (0) |
| 165 | do | |||
| 165 | do | |||
| 0 | 0 | - | 165 | do-while (0) |
| 0 | 0 | - | 165 | do-while (0) |
| 0 | 0 | - | 165 | do-while (0) |
| 166 | acm->write_ready = 1; | |||
| 167 | wbn = acm->write_current; | |||
| 168 | acm_wb_free(acm, wbn); | |||
| 169 | acm->write_current = (wbn + 1) % ACM_NWB; | |||
| 170 | spin_unlock_irqrestore(&acm->write_lock, flags); | |||
| 170 | do | |||
| 170 | do | |||
| 0 | 0 | - | 170 | do-while (0) |
| 0 | 0 | - | 170 | do-while (0) |
| 0 | 0 | - | 170 | do-while (0) |
| 171 | } | |||
| 172 | ||||
| 173 | /* | |||
| 174 | * Poke write. | |||
| 175 | */ | |||
| 0 | 0 | - | 176 | static int acm_write_start(struct acm *acm) |
| 177 | { | |||
| 178 | unsigned long flags; | |||
| 179 | int wbn; | |||
| 180 | struct acm_wb *wb; | |||
| 181 | int rc; | |||
| 182 | ||||
| 183 | spin_lock_irqsave(&acm->write_lock, flags); | |||
| 183 | do | |||
| 183 | do | |||
| 0 | 0 | - | 183 | do-while (0) |
| 0 | 0 | - | 183 | do-while (0) |
| 183 | do | |||
| 183 | do | |||
| 0 | 0 | - | 183 | do-while (0) |
| 0 | 0 | - | 183 | do-while (0) |
| 0 | 0 | - | 183 | do-while (0) |
| 0 | 0 | - | 184 | if (!acm->dev) { |
| 185 | spin_unlock_irqrestore(&acm->write_lock, flags); | |||
| 185 | do | |||
| 185 | do | |||
| 0 | 0 | - | 185 | do-while (0) |
| 0 | 0 | - | 185 | do-while (0) |
| 0 | 0 | - | 185 | do-while (0) |
| 0 | - | 186 | return -ENODEV; | |
| 187 | } | |||
| 188 | ||||
| 0 | 0 | - | 189 | if (!acm->write_ready) { |
| 190 | spin_unlock_irqrestore(&acm->write_lock, flags); | |||
| 190 | do | |||
| 190 | do | |||
| 0 | 0 | - | 190 | do-while (0) |
| 0 | 0 | - | 190 | do-while (0) |
| 0 | 0 | - | 190 | do-while (0) |
| 0 | - | 191 | return 0; /* A white lie */ | |
| 192 | } | |||
| 193 | ||||
| 194 | wbn = acm->write_current; | |||
| 0 | 0 | - | 195 | if (!acm_wb_is_used(acm, wbn)) { |
| 196 | spin_unlock_irqrestore(&acm->write_lock, flags); | |||
| 196 | do | |||
| 196 | do | |||
| 0 | 0 | - | 196 | do-while (0) |
| 0 | 0 | - | 196 | do-while (0) |
| 0 | 0 | - | 196 | do-while (0) |
| 0 | - | 197 | return 0; | |
| 198 | } | |||
| 199 | wb = &acm->wb[wbn]; | |||
| 200 | ||||
| 201 | acm->write_ready = 0; | |||
| 202 | spin_unlock_irqrestore(&acm->write_lock, flags); | |||
| 202 | do | |||
| 202 | do | |||
| 0 | 0 | - | 202 | do-while (0) |
| 0 | 0 | - | 202 | do-while (0) |
| 0 | 0 | - | 202 | do-while (0) |
| 203 | ||||
| 204 | acm->writeurb->transfer_buffer = wb->buf; | |||
| 205 | acm->writeurb->transfer_dma = wb->dmah; | |||
| 206 | acm->writeurb->transfer_buffer_length = wb->len; | |||
| 207 | acm->writeurb->dev = acm->dev; | |||
| 208 | ||||
| 0 | 0 | - | 209 | if ((rc = usb_submit_urb(acm->writeurb, GFP_ATOMIC)) < 0) { |
| 210 | dbg("usb_submit_urb(write bulk) failed: %d", rc); | |||
| 0 | 0 | - | 210 | do-while (0) |
| 211 | acm_write_done(acm); | |||
| 212 | } | |||
| 0 | - | 213 | return rc; | |
| 214 | } | |||
| 215 | ||||
| 216 | /* | |||
| 217 | * Interrupt handlers for various ACM device responses | |||
| 218 | */ | |||
| 219 | ||||
| 220 | /* control interface reports status changes with "interrupt" transfers */ | |||
| 0 | 0 | - | 221 | static void acm_ctrl_irq(struct urb *urb, struct pt_regs *regs) |
| 222 | { | |||
| 223 | struct acm *acm = urb->context; | |||
| 224 | struct usb_cdc_notification *dr = urb->transfer_buffer; | |||
| 225 | unsigned char *data; | |||
| 226 | int newctrl; | |||
| 227 | int status; | |||
| 228 | ||||
| 229 | switch (urb->status) { | |||
| 0 | - | 230 | case 0: | |
| 231 | /* success */ | |||
| 0 | - | 232 | break; | |
| 0 | - | 233 | case -ECONNRESET: | |
| 0 | - | 234 | case -ENOENT: | |
| 0 | - | 235 | case -ESHUTDOWN: | |
| 236 | /* this urb is terminated, clean up */ | |||
| 237 | dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status); | |||
| 0 | 0 | - | 237 | do-while (0) |
| 0 | - | 238 | return; | |
| 0 | - | 239 | default: | |
| 240 | dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status); | |||
| 0 | 0 | - | 240 | do-while (0) |
| 0 | - | 241 | goto exit; | |
| 242 | } | |||
| 243 | ||||
| 0 | 0 | - | 244 | if (!ACM_READY(acm)) |
| 0 | - | 244 | !(T && T && F) | |
| 0 | - | 244 | !(T && F && _) | |
| 0 | - | 244 | !(F && _ && _) | |
| 0 | - | 244 | !(T && T && T) | |
| 0 | - | 245 | goto exit; | |
| 246 | ||||
| 247 | data = (unsigned char *)(dr + 1); | |||
| 248 | switch (dr->bNotificationType) { | |||
| 249 | ||||
| 0 | - | 250 | case USB_CDC_NOTIFY_NETWORK_CONNECTION: | |
| 251 | ||||
| 252 | dbg("%s network", dr->wValue ? "connected to" : "disconnected from"); | |||
| 0 | 0 | - | 252 | do-while (0) |
| 0 | - | 253 | break; | |
| 254 | ||||
| 0 | - | 255 | case USB_CDC_NOTIFY_SERIAL_STATE: | |
| 256 | ||||
| 257 | newctrl = le16_to_cpu(get_unaligned((__le16 *) data)); | |||
| 258 | ||||
| 0 | 0 | - | 259 | if (acm->tty && !acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) { |
| 0 | - | 259 | T && T && (T) | |
| 0 | - | 259 | T && T && (F) | |
| 0 | - | 259 | T && F && (_) | |
| 0 | - | 259 | F && _ && (_) | |
| 260 | dbg("calling hangup"); | |||
| 0 | 0 | - | 260 | do-while (0) |
| 261 | tty_hangup(acm->tty); | |||
| 262 | } | |||
| 263 | ||||
| 264 | acm->ctrlin = newctrl; | |||
| 265 | ||||
| 266 | dbg("input control lines: dcd%c dsr%c break%c ring%c framing%c parity%c overrun%c", | |||
| 0 | 0 | - | 266 | do-while (0) |
| 267 | acm->ctrlin & ACM_CTRL_DCD ? '+' : '-', acm->ctrlin & ACM_CTRL_DSR ? '+' : '-', | |||
| 268 | acm->ctrlin & ACM_CTRL_BRK ? '+' : '-', acm->ctrlin & ACM_CTRL_RI ? '+' : '-', | |||
| 269 | acm->ctrlin & ACM_CTRL_FRAMING ? '+' : '-', acm->ctrlin & ACM_CTRL_PARITY ? '+' : '-', | |||
| 270 | acm->ctrlin & ACM_CTRL_OVERRUN ? '+' : '-'); | |||
| 271 | ||||
| 0 | - | 272 | break; | |
| 273 | ||||
| 0 | - | 274 | default: | |
| 275 | dbg("unknown notification %d received: index %d len %d data0 %d data1 %d", | |||
| 0 | 0 | - | 275 | do-while (0) |
| 276 | dr->bNotificationType, dr->wIndex, | |||
| 277 | dr->wLength, data[0], data[1]); | |||
| 0 | - | 278 | break; | |
| 279 | } | |||
| 280 | exit: | |||
| 281 | status = usb_submit_urb (urb, GFP_ATOMIC); | |||
| 0 | 0 | - | 282 | if (status) |
| 283 | err ("%s - usb_submit_urb failed with result %d", | |||
| 284 | __FUNCTION__, status); | |||
| 285 | } | |||
| 286 | ||||
| 287 | /* data interface returns incoming bytes, or we got unthrottled */ | |||
| 0 | 0 | - | 288 | static void acm_read_bulk(struct urb *urb, struct pt_regs *regs) |
| 289 | { | |||
| 290 | struct acm_rb *buf; | |||
| 291 | struct acm_ru *rcv = urb->context; | |||
| 292 | struct acm *acm = rcv->instance; | |||
| 293 | dbg("Entering acm_read_bulk with status %d\n", urb->status); | |||
| 0 | 0 | - | 293 | do-while (0) |
| 294 | ||||
| 0 | 0 | - | 295 | if (!ACM_READY(acm)) |
| 0 | - | 295 | !(T && T && F) | |
| 0 | - | 295 | !(T && F && _) | |
| 0 | - | 295 | !(F && _ && _) | |
| 0 | - | 295 | !(T && T && T) | |
| 0 | - | 296 | return; | |
| 297 | ||||
| 0 | 0 | - | 298 | if (urb->status) |
| 299 | dev_dbg(&acm->data->dev, "bulk rx status %d\n", urb->status); | |||
| 0 | 0 | - | 299 | do-while (0) |
| 300 | ||||
| 301 | buf = rcv->buffer; | |||
| 302 | buf->size = urb->actual_length; | |||
| 303 | ||||
| 304 | spin_lock(&acm->read_lock); | |||
| 304 | do | |||
| 0 | 0 | - | 304 | do-while (0) |
| 0 | 0 | - | 304 | do-while (0) |
| 305 | list_add_tail(&rcv->list, &acm->spare_read_urbs); | |||
| 306 | list_add_tail(&buf->list, &acm->filled_read_bufs); | |||
| 307 | spin_unlock(&acm->read_lock); | |||
| 307 | do | |||
| 0 | 0 | - | 307 | do-while (0) |
| 0 | 0 | - | 307 | do-while (0) |
| 308 | ||||
| 309 | tasklet_schedule(&acm->urb_task); | |||
| 310 | } | |||
| 311 | ||||
| 0 | 0 | - | 312 | static void acm_rx_tasklet(unsigned long _acm) |
| 313 | { | |||
| 314 | struct acm *acm = (void *)_acm; | |||
| 315 | struct acm_rb *buf; | |||
| 316 | struct tty_struct *tty = acm->tty; | |||
| 317 | struct acm_ru *rcv; | |||
| 318 | //unsigned long flags; | |||
| 319 | int i = 0; | |||
| 320 | dbg("Entering acm_rx_tasklet"); | |||
| 0 | 0 | - | 320 | do-while (0) |
| 321 | ||||
| 0 | 0 | - | 322 | if (!ACM_READY(acm) || acm->throttle) |
| 0 | - | 322 | !(T && T && T) || T | |
| 0 | - | 322 | !(T && T && F) || _ | |
| 0 | - | 322 | !(T && F && _) || _ | |
| 0 | - | 322 | !(F && _ && _) || _ | |
| 0 | - | 322 | !(T && T && T) || F | |
| 0 | - | 323 | return; | |
| 324 | ||||
| 325 | next_buffer: | |||
| 326 | spin_lock(&acm->read_lock); | |||
| 326 | do | |||
| 0 | 0 | - | 326 | do-while (0) |
| 0 | 0 | - | 326 | do-while (0) |
| 0 | 0 | - | 327 | if (list_empty(&acm->filled_read_bufs)) { |
| 328 | spin_unlock(&acm->read_lock); | |||
| 328 | do | |||
| 0 | 0 | - | 328 | do-while (0) |
| 0 | 0 | - | 328 | do-while (0) |
| 0 | - | 329 | goto urbs; | |
| 330 | } | |||
| 331 | buf = list_entry(acm->filled_read_bufs.next, | |||
| 332 | struct acm_rb, list); | |||
| 333 | list_del(&buf->list); | |||
| 334 | spin_unlock(&acm->read_lock); | |||
| 334 | do | |||
| 0 | 0 | - | 334 | do-while (0) |
| 0 | 0 | - | 334 | do-while (0) |
| 335 | ||||
| 336 | dbg("acm_rx_tasklet: procesing buf 0x%p, size = %d\n", buf, buf->size); | |||
| 0 | 0 | - | 336 | do-while (0) |
| 337 | ||||
| 338 | tty_buffer_request_room(tty, buf->size); | |||
| 0 | 0 | - | 339 | if (!acm->throttle) |
| 340 | tty_insert_flip_string(tty, buf->base, buf->size); | |||
| 341 | tty_flip_buffer_push(tty); | |||
| 342 | ||||
| 343 | spin_lock(&acm->throttle_lock); | |||
| 343 | do | |||
| 0 | 0 | - | 343 | do-while (0) |
| 0 | 0 | - | 343 | do-while (0) |
| 0 | 0 | - | 344 | if (acm->throttle) { |
| 345 | dbg("Throtteling noticed"); | |||
| 0 | 0 | - | 345 | do-while (0) |
| 346 | memmove(buf->base, buf->base + i, buf->size - i); | |||
| 347 | buf->size -= i; | |||
| 348 | spin_unlock(&acm->throttle_lock); | |||
| 348 | do | |||
| 0 | 0 | - | 348 | do-while (0) |
| 0 | 0 | - | 348 | do-while (0) |
| 349 | spin_lock(&acm->read_lock); | |||
| 349 | do | |||
| 0 | 0 | - | 349 | do-while (0) |
| 0 | 0 | - | 349 | do-while (0) |
| 350 | list_add(&buf->list, &acm->filled_read_bufs); | |||
| 351 | spin_unlock(&acm->read_lock); | |||
| 351 | do | |||
| 0 | 0 | - | 351 | do-while (0) |
| 0 | 0 | - | 351 | do-while (0) |
| 0 | - | 352 | return; | |
| 353 | } | |||
| 354 | spin_unlock(&acm->throttle_lock); | |||
| 354 | do | |||
| 0 | 0 | - | 354 | do-while (0) |
| 0 | 0 | - | 354 | do-while (0) |
| 355 | ||||
| 356 | spin_lock(&acm->read_lock); | |||
| 356 | do | |||
| 0 | 0 | - | 356 | do-while (0) |
| 0 | 0 | - | 356 | do-while (0) |
| 357 | list_add(&buf->list, &acm->spare_read_bufs); | |||
| 358 | spin_unlock(&acm->read_lock); | |||
| 358 | do | |||
| 0 | 0 | - | 358 | do-while (0) |
| 0 | 0 | - | 358 | do-while (0) |
| 0 | - | 359 | goto next_buffer; | |
| 360 | ||||
| 361 | urbs: | |||
| 0 | 0 | - | 362 | while (!list_empty(&acm->spare_read_bufs)) { |
| 363 | spin_lock(&acm->read_lock); | |||
| 363 | do | |||
| 0 | 0 | - | 363 | do-while (0) |
| 0 | 0 | - | 363 | do-while (0) |
| 0 | 0 | - | 364 | if (list_empty(&acm->spare_read_urbs)) { |
| 365 | spin_unlock(&acm->read_lock); | |||
| 365 | do | |||
| 0 | 0 | - | 365 | do-while (0) |
| 0 | 0 | - | 365 | do-while (0) |
| 0 | - | 366 | return; | |
| 367 | } | |||
| 368 | rcv = list_entry(acm->spare_read_urbs.next, | |||
| 369 | struct acm_ru, list); | |||
| 370 | list_del(&rcv->list); | |||
| 371 | spin_unlock(&acm->read_lock); | |||
| 371 | do | |||
| 0 | 0 | - | 371 | do-while (0) |
| 0 | 0 | - | 371 | do-while (0) |
| 372 | ||||
| 373 | buf = list_entry(acm->spare_read_bufs.next, | |||
| 374 | struct acm_rb, list); | |||
| 375 | list_del(&buf->list); | |||
| 376 | ||||
| 377 | rcv->buffer = buf; | |||
| 378 | ||||
| 379 | usb_fill_bulk_urb(rcv->urb, acm->dev, | |||
| 380 | acm->rx_endpoint, | |||
| 381 | buf->base, | |||
| 382 | acm->readsize, | |||
| 383 | acm_read_bulk, rcv); | |||