Commit 884b600f63dc7c646f415a5d8f356df1f66ff6f2
Committed by
Greg Kroah-Hartman
1 parent
d5926ae7a8
Exists in
master
and in
7 other branches
[PATCH] USB: fix acm trouble with terminals
This patch fixes lost LF when ACM device is used with getty/login/bash, in case of a modem which takes calls. Signed-off-by: Pete Zaitcev <zaitcev@redhat.com> Signed-off-by: Oliver Neukum <oliver@neukum.name> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Showing 2 changed files with 197 additions and 37 deletions Side-by-side Diff
drivers/usb/class/cdc-acm.c
... | ... | @@ -106,6 +106,111 @@ |
106 | 106 | acm_ctrl_msg(acm, USB_CDC_REQ_SEND_BREAK, ms, NULL, 0) |
107 | 107 | |
108 | 108 | /* |
109 | + * Write buffer management. | |
110 | + * All of these assume proper locks taken by the caller. | |
111 | + */ | |
112 | + | |
113 | +static int acm_wb_alloc(struct acm *acm) | |
114 | +{ | |
115 | + int i, wbn; | |
116 | + struct acm_wb *wb; | |
117 | + | |
118 | + wbn = acm->write_current; | |
119 | + i = 0; | |
120 | + for (;;) { | |
121 | + wb = &acm->wb[wbn]; | |
122 | + if (!wb->use) { | |
123 | + wb->use = 1; | |
124 | + return wbn; | |
125 | + } | |
126 | + wbn = (wbn + 1) % ACM_NWB; | |
127 | + if (++i >= ACM_NWB) | |
128 | + return -1; | |
129 | + } | |
130 | +} | |
131 | + | |
132 | +static void acm_wb_free(struct acm *acm, int wbn) | |
133 | +{ | |
134 | + acm->wb[wbn].use = 0; | |
135 | +} | |
136 | + | |
137 | +static int acm_wb_is_avail(struct acm *acm) | |
138 | +{ | |
139 | + int i, n; | |
140 | + | |
141 | + n = 0; | |
142 | + for (i = 0; i < ACM_NWB; i++) { | |
143 | + if (!acm->wb[i].use) | |
144 | + n++; | |
145 | + } | |
146 | + return n; | |
147 | +} | |
148 | + | |
149 | +static inline int acm_wb_is_used(struct acm *acm, int wbn) | |
150 | +{ | |
151 | + return acm->wb[wbn].use; | |
152 | +} | |
153 | + | |
154 | +/* | |
155 | + * Finish write. | |
156 | + */ | |
157 | +static void acm_write_done(struct acm *acm) | |
158 | +{ | |
159 | + unsigned long flags; | |
160 | + int wbn; | |
161 | + | |
162 | + spin_lock_irqsave(&acm->write_lock, flags); | |
163 | + acm->write_ready = 1; | |
164 | + wbn = acm->write_current; | |
165 | + acm_wb_free(acm, wbn); | |
166 | + acm->write_current = (wbn + 1) % ACM_NWB; | |
167 | + spin_unlock_irqrestore(&acm->write_lock, flags); | |
168 | +} | |
169 | + | |
170 | +/* | |
171 | + * Poke write. | |
172 | + */ | |
173 | +static int acm_write_start(struct acm *acm) | |
174 | +{ | |
175 | + unsigned long flags; | |
176 | + int wbn; | |
177 | + struct acm_wb *wb; | |
178 | + int rc; | |
179 | + | |
180 | + spin_lock_irqsave(&acm->write_lock, flags); | |
181 | + if (!acm->dev) { | |
182 | + spin_unlock_irqrestore(&acm->write_lock, flags); | |
183 | + return -ENODEV; | |
184 | + } | |
185 | + | |
186 | + if (!acm->write_ready) { | |
187 | + spin_unlock_irqrestore(&acm->write_lock, flags); | |
188 | + return 0; /* A white lie */ | |
189 | + } | |
190 | + | |
191 | + wbn = acm->write_current; | |
192 | + if (!acm_wb_is_used(acm, wbn)) { | |
193 | + spin_unlock_irqrestore(&acm->write_lock, flags); | |
194 | + return 0; | |
195 | + } | |
196 | + wb = &acm->wb[wbn]; | |
197 | + | |
198 | + acm->write_ready = 0; | |
199 | + spin_unlock_irqrestore(&acm->write_lock, flags); | |
200 | + | |
201 | + acm->writeurb->transfer_buffer = wb->buf; | |
202 | + acm->writeurb->transfer_dma = wb->dmah; | |
203 | + acm->writeurb->transfer_buffer_length = wb->len; | |
204 | + acm->writeurb->dev = acm->dev; | |
205 | + | |
206 | + if ((rc = usb_submit_urb(acm->writeurb, GFP_ATOMIC)) < 0) { | |
207 | + dbg("usb_submit_urb(write bulk) failed: %d", rc); | |
208 | + acm_write_done(acm); | |
209 | + } | |
210 | + return rc; | |
211 | +} | |
212 | + | |
213 | +/* | |
109 | 214 | * Interrupt handlers for various ACM device responses |
110 | 215 | */ |
111 | 216 | |
112 | 217 | |
... | ... | @@ -237,17 +342,13 @@ |
237 | 342 | static void acm_write_bulk(struct urb *urb, struct pt_regs *regs) |
238 | 343 | { |
239 | 344 | struct acm *acm = (struct acm *)urb->context; |
345 | + | |
240 | 346 | dbg("Entering acm_write_bulk with status %d\n", urb->status); |
241 | 347 | |
242 | - if (!ACM_READY(acm)) | |
243 | - goto out; | |
244 | - | |
245 | - if (urb->status) | |
246 | - dbg("nonzero write bulk status received: %d", urb->status); | |
247 | - | |
248 | - schedule_work(&acm->work); | |
249 | -out: | |
250 | - acm->ready_for_write = 1; | |
348 | + acm_write_done(acm); | |
349 | + acm_write_start(acm); | |
350 | + if (ACM_READY(acm)) | |
351 | + schedule_work(&acm->work); | |
251 | 352 | } |
252 | 353 | |
253 | 354 | static void acm_softint(void *private) |
254 | 355 | |
255 | 356 | |
256 | 357 | |
257 | 358 | |
258 | 359 | |
259 | 360 | |
... | ... | @@ -351,32 +452,33 @@ |
351 | 452 | { |
352 | 453 | struct acm *acm = tty->driver_data; |
353 | 454 | int stat; |
455 | + unsigned long flags; | |
456 | + int wbn; | |
457 | + struct acm_wb *wb; | |
458 | + | |
354 | 459 | dbg("Entering acm_tty_write to write %d bytes,\n", count); |
355 | 460 | |
356 | 461 | if (!ACM_READY(acm)) |
357 | 462 | return -EINVAL; |
358 | - if (!acm->ready_for_write) | |
359 | - return 0; | |
360 | 463 | if (!count) |
361 | 464 | return 0; |
362 | 465 | |
363 | - count = (count > acm->writesize) ? acm->writesize : count; | |
466 | + spin_lock_irqsave(&acm->write_lock, flags); | |
467 | + if ((wbn = acm_wb_alloc(acm)) < 0) { | |
468 | + spin_unlock_irqrestore(&acm->write_lock, flags); | |
469 | + acm_write_start(acm); | |
470 | + return 0; | |
471 | + } | |
472 | + wb = &acm->wb[wbn]; | |
364 | 473 | |
474 | + count = (count > acm->writesize) ? acm->writesize : count; | |
365 | 475 | dbg("Get %d bytes...", count); |
366 | - memcpy(acm->write_buffer, buf, count); | |
367 | - dbg(" Successfully copied.\n"); | |
476 | + memcpy(wb->buf, buf, count); | |
477 | + wb->len = count; | |
478 | + spin_unlock_irqrestore(&acm->write_lock, flags); | |
368 | 479 | |
369 | - acm->writeurb->transfer_buffer_length = count; | |
370 | - acm->writeurb->dev = acm->dev; | |
371 | - | |
372 | - acm->ready_for_write = 0; | |
373 | - stat = usb_submit_urb(acm->writeurb, GFP_ATOMIC); | |
374 | - if (stat < 0) { | |
375 | - dbg("usb_submit_urb(write bulk) failed"); | |
376 | - acm->ready_for_write = 1; | |
480 | + if ((stat = acm_write_start(acm)) < 0) | |
377 | 481 | return stat; |
378 | - } | |
379 | - | |
380 | 482 | return count; |
381 | 483 | } |
382 | 484 | |
... | ... | @@ -385,7 +487,11 @@ |
385 | 487 | struct acm *acm = tty->driver_data; |
386 | 488 | if (!ACM_READY(acm)) |
387 | 489 | return -EINVAL; |
388 | - return !acm->ready_for_write ? 0 : acm->writesize; | |
490 | + /* | |
491 | + * Do not let the line discipline to know that we have a reserve, | |
492 | + * or it might get too enthusiastic. | |
493 | + */ | |
494 | + return (acm->write_ready && acm_wb_is_avail(acm)) ? acm->writesize : 0; | |
389 | 495 | } |
390 | 496 | |
391 | 497 | static int acm_tty_chars_in_buffer(struct tty_struct *tty) |
... | ... | @@ -393,7 +499,10 @@ |
393 | 499 | struct acm *acm = tty->driver_data; |
394 | 500 | if (!ACM_READY(acm)) |
395 | 501 | return -EINVAL; |
396 | - return !acm->ready_for_write ? acm->writeurb->transfer_buffer_length : 0; | |
502 | + /* | |
503 | + * This is inaccurate (overcounts), but it works. | |
504 | + */ | |
505 | + return (ACM_NWB - acm_wb_is_avail(acm)) * acm->writesize; | |
397 | 506 | } |
398 | 507 | |
399 | 508 | static void acm_tty_throttle(struct tty_struct *tty) |
... | ... | @@ -526,6 +635,39 @@ |
526 | 635 | * USB probe and disconnect routines. |
527 | 636 | */ |
528 | 637 | |
638 | +/* Little helper: write buffers free */ | |
639 | +static void acm_write_buffers_free(struct acm *acm) | |
640 | +{ | |
641 | + int i; | |
642 | + struct acm_wb *wb; | |
643 | + | |
644 | + for (wb = &acm->wb[0], i = 0; i < ACM_NWB; i++, wb++) { | |
645 | + usb_buffer_free(acm->dev, acm->writesize, wb->buf, wb->dmah); | |
646 | + } | |
647 | +} | |
648 | + | |
649 | +/* Little helper: write buffers allocate */ | |
650 | +static int acm_write_buffers_alloc(struct acm *acm) | |
651 | +{ | |
652 | + int i; | |
653 | + struct acm_wb *wb; | |
654 | + | |
655 | + for (wb = &acm->wb[0], i = 0; i < ACM_NWB; i++, wb++) { | |
656 | + wb->buf = usb_buffer_alloc(acm->dev, acm->writesize, GFP_KERNEL, | |
657 | + &wb->dmah); | |
658 | + if (!wb->buf) { | |
659 | + while (i != 0) { | |
660 | + --i; | |
661 | + --wb; | |
662 | + usb_buffer_free(acm->dev, acm->writesize, | |
663 | + wb->buf, wb->dmah); | |
664 | + } | |
665 | + return -ENOMEM; | |
666 | + } | |
667 | + } | |
668 | + return 0; | |
669 | +} | |
670 | + | |
529 | 671 | static int acm_probe (struct usb_interface *intf, |
530 | 672 | const struct usb_device_id *id) |
531 | 673 | { |
... | ... | @@ -700,7 +842,8 @@ |
700 | 842 | acm->bh.data = (unsigned long) acm; |
701 | 843 | INIT_WORK(&acm->work, acm_softint, acm); |
702 | 844 | spin_lock_init(&acm->throttle_lock); |
703 | - acm->ready_for_write = 1; | |
845 | + spin_lock_init(&acm->write_lock); | |
846 | + acm->write_ready = 1; | |
704 | 847 | |
705 | 848 | buf = usb_buffer_alloc(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma); |
706 | 849 | if (!buf) { |
707 | 850 | |
... | ... | @@ -716,12 +859,10 @@ |
716 | 859 | } |
717 | 860 | acm->read_buffer = buf; |
718 | 861 | |
719 | - buf = usb_buffer_alloc(usb_dev, acm->writesize, GFP_KERNEL, &acm->write_dma); | |
720 | - if (!buf) { | |
862 | + if (acm_write_buffers_alloc(acm) < 0) { | |
721 | 863 | dev_dbg(&intf->dev, "out of memory (write buffer alloc)\n"); |
722 | 864 | goto alloc_fail4; |
723 | 865 | } |
724 | - acm->write_buffer = buf; | |
725 | 866 | |
726 | 867 | acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL); |
727 | 868 | if (!acm->ctrlurb) { |
728 | 869 | |
... | ... | @@ -750,9 +891,9 @@ |
750 | 891 | acm->readurb->transfer_dma = acm->read_dma; |
751 | 892 | |
752 | 893 | usb_fill_bulk_urb(acm->writeurb, usb_dev, usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress), |
753 | - acm->write_buffer, acm->writesize, acm_write_bulk, acm); | |
894 | + NULL, acm->writesize, acm_write_bulk, acm); | |
754 | 895 | acm->writeurb->transfer_flags |= URB_NO_FSBR | URB_NO_TRANSFER_DMA_MAP; |
755 | - acm->writeurb->transfer_dma = acm->write_dma; | |
896 | + /* acm->writeurb->transfer_dma = 0; */ | |
756 | 897 | |
757 | 898 | dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor); |
758 | 899 | |
... | ... | @@ -775,7 +916,7 @@ |
775 | 916 | alloc_fail6: |
776 | 917 | usb_free_urb(acm->ctrlurb); |
777 | 918 | alloc_fail5: |
778 | - usb_buffer_free(usb_dev, acm->writesize, acm->write_buffer, acm->write_dma); | |
919 | + acm_write_buffers_free(acm); | |
779 | 920 | alloc_fail4: |
780 | 921 | usb_buffer_free(usb_dev, readsize, acm->read_buffer, acm->read_dma); |
781 | 922 | alloc_fail3: |
... | ... | @@ -806,7 +947,7 @@ |
806 | 947 | |
807 | 948 | flush_scheduled_work(); /* wait for acm_softint */ |
808 | 949 | |
809 | - usb_buffer_free(usb_dev, acm->writesize, acm->write_buffer, acm->write_dma); | |
950 | + acm_write_buffers_free(acm); | |
810 | 951 | usb_buffer_free(usb_dev, acm->readsize, acm->read_buffer, acm->read_dma); |
811 | 952 | usb_buffer_free(usb_dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); |
812 | 953 |
drivers/usb/class/cdc-acm.h
... | ... | @@ -51,14 +51,34 @@ |
51 | 51 | * Internal driver structures. |
52 | 52 | */ |
53 | 53 | |
54 | +/* | |
55 | + * The only reason to have several buffers is to accomodate assumptions | |
56 | + * in line disciplines. They ask for empty space amount, receive our URB size, | |
57 | + * and proceed to issue several 1-character writes, assuming they will fit. | |
58 | + * The very first write takes a complete URB. Fortunately, this only happens | |
59 | + * when processing onlcr, so we only need 2 buffers. | |
60 | + */ | |
61 | +#define ACM_NWB 2 | |
62 | +struct acm_wb { | |
63 | + unsigned char *buf; | |
64 | + dma_addr_t dmah; | |
65 | + int len; | |
66 | + int use; | |
67 | +}; | |
68 | + | |
54 | 69 | struct acm { |
55 | 70 | struct usb_device *dev; /* the corresponding usb device */ |
56 | 71 | struct usb_interface *control; /* control interface */ |
57 | 72 | struct usb_interface *data; /* data interface */ |
58 | 73 | struct tty_struct *tty; /* the corresponding tty */ |
59 | 74 | struct urb *ctrlurb, *readurb, *writeurb; /* urbs */ |
60 | - u8 *ctrl_buffer, *read_buffer, *write_buffer; /* buffers of urbs */ | |
61 | - dma_addr_t ctrl_dma, read_dma, write_dma; /* dma handles of buffers */ | |
75 | + u8 *ctrl_buffer, *read_buffer; /* buffers of urbs */ | |
76 | + dma_addr_t ctrl_dma, read_dma; /* dma handles of buffers */ | |
77 | + struct acm_wb wb[ACM_NWB]; | |
78 | + int write_current; /* current write buffer */ | |
79 | + int write_used; /* number of non-empty write buffers */ | |
80 | + int write_ready; /* write urb is not running */ | |
81 | + spinlock_t write_lock; | |
62 | 82 | struct usb_cdc_line_coding line; /* bits, stop, parity */ |
63 | 83 | struct work_struct work; /* work queue entry for line discipline waking up */ |
64 | 84 | struct tasklet_struct bh; /* rx processing */ |
... | ... | @@ -71,7 +91,6 @@ |
71 | 91 | unsigned int minor; /* acm minor number */ |
72 | 92 | unsigned char throttle; /* throttled by tty layer */ |
73 | 93 | unsigned char clocal; /* termios CLOCAL */ |
74 | - unsigned char ready_for_write; /* write urb can be used */ | |
75 | 94 | unsigned char resubmit_to_unthrottle; /* throtteling has disabled the read urb */ |
76 | 95 | unsigned int ctrl_caps; /* control capabilities from the class specific header */ |
77 | 96 | }; |