diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2009-08-04 18:39:43 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-08-04 18:39:43 -0400 |
commit | 9f3eea6a2fbf5a07625713dc35e5f8fb91adb12f (patch) | |
tree | e5358f8cee31ca38d82b97269e904cb8d0b546b1 /drivers/char | |
parent | 2cf812d732442e86c1e2018e23ad82f9bc594a38 (diff) | |
parent | cbe9352fa08f90aa03b4dbf1bbabfc95d196e562 (diff) |
Merge git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty-2.6
* git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty-2.6:
tty-ldisc: be more careful in 'put_ldisc' locking
tty-ldisc: turn ldisc user count into a proper refcount
tty-ldisc: make refcount be atomic_t 'users' count
Diffstat (limited to 'drivers/char')
-rw-r--r-- | drivers/char/tty_ldisc.c | 152 |
1 files changed, 53 insertions, 99 deletions
diff --git a/drivers/char/tty_ldisc.c b/drivers/char/tty_ldisc.c index acd76b767d4c..1733d3439ad2 100644 --- a/drivers/char/tty_ldisc.c +++ b/drivers/char/tty_ldisc.c | |||
@@ -48,6 +48,41 @@ static DECLARE_WAIT_QUEUE_HEAD(tty_ldisc_wait); | |||
48 | /* Line disc dispatch table */ | 48 | /* Line disc dispatch table */ |
49 | static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS]; | 49 | static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS]; |
50 | 50 | ||
51 | static inline struct tty_ldisc *get_ldisc(struct tty_ldisc *ld) | ||
52 | { | ||
53 | if (ld) | ||
54 | atomic_inc(&ld->users); | ||
55 | return ld; | ||
56 | } | ||
57 | |||
58 | static void put_ldisc(struct tty_ldisc *ld) | ||
59 | { | ||
60 | unsigned long flags; | ||
61 | |||
62 | if (WARN_ON_ONCE(!ld)) | ||
63 | return; | ||
64 | |||
65 | /* | ||
66 | * If this is the last user, free the ldisc, and | ||
67 | * release the ldisc ops. | ||
68 | * | ||
69 | * We really want an "atomic_dec_and_lock_irqsave()", | ||
70 | * but we don't have it, so this does it by hand. | ||
71 | */ | ||
72 | local_irq_save(flags); | ||
73 | if (atomic_dec_and_lock(&ld->users, &tty_ldisc_lock)) { | ||
74 | struct tty_ldisc_ops *ldo = ld->ops; | ||
75 | |||
76 | ldo->refcount--; | ||
77 | module_put(ldo->owner); | ||
78 | spin_unlock_irqrestore(&tty_ldisc_lock, flags); | ||
79 | |||
80 | kfree(ld); | ||
81 | return; | ||
82 | } | ||
83 | local_irq_restore(flags); | ||
84 | } | ||
85 | |||
51 | /** | 86 | /** |
52 | * tty_register_ldisc - install a line discipline | 87 | * tty_register_ldisc - install a line discipline |
53 | * @disc: ldisc number | 88 | * @disc: ldisc number |
@@ -142,7 +177,7 @@ static struct tty_ldisc *tty_ldisc_try_get(int disc) | |||
142 | /* lock it */ | 177 | /* lock it */ |
143 | ldops->refcount++; | 178 | ldops->refcount++; |
144 | ld->ops = ldops; | 179 | ld->ops = ldops; |
145 | ld->refcount = 0; | 180 | atomic_set(&ld->users, 1); |
146 | err = 0; | 181 | err = 0; |
147 | } | 182 | } |
148 | } | 183 | } |
@@ -181,35 +216,6 @@ static struct tty_ldisc *tty_ldisc_get(int disc) | |||
181 | return ld; | 216 | return ld; |
182 | } | 217 | } |
183 | 218 | ||
184 | /** | ||
185 | * tty_ldisc_put - drop ldisc reference | ||
186 | * @ld: ldisc | ||
187 | * | ||
188 | * Drop a reference to a line discipline. Manage refcounts and | ||
189 | * module usage counts. Free the ldisc once the recount hits zero. | ||
190 | * | ||
191 | * Locking: | ||
192 | * takes tty_ldisc_lock to guard against ldisc races | ||
193 | */ | ||
194 | |||
195 | static void tty_ldisc_put(struct tty_ldisc *ld) | ||
196 | { | ||
197 | unsigned long flags; | ||
198 | int disc = ld->ops->num; | ||
199 | struct tty_ldisc_ops *ldo; | ||
200 | |||
201 | BUG_ON(disc < N_TTY || disc >= NR_LDISCS); | ||
202 | |||
203 | spin_lock_irqsave(&tty_ldisc_lock, flags); | ||
204 | ldo = tty_ldiscs[disc]; | ||
205 | BUG_ON(ldo->refcount == 0); | ||
206 | ldo->refcount--; | ||
207 | module_put(ldo->owner); | ||
208 | spin_unlock_irqrestore(&tty_ldisc_lock, flags); | ||
209 | WARN_ON(ld->refcount); | ||
210 | kfree(ld); | ||
211 | } | ||
212 | |||
213 | static void *tty_ldiscs_seq_start(struct seq_file *m, loff_t *pos) | 219 | static void *tty_ldiscs_seq_start(struct seq_file *m, loff_t *pos) |
214 | { | 220 | { |
215 | return (*pos < NR_LDISCS) ? pos : NULL; | 221 | return (*pos < NR_LDISCS) ? pos : NULL; |
@@ -234,7 +240,7 @@ static int tty_ldiscs_seq_show(struct seq_file *m, void *v) | |||
234 | if (IS_ERR(ld)) | 240 | if (IS_ERR(ld)) |
235 | return 0; | 241 | return 0; |
236 | seq_printf(m, "%-10s %2d\n", ld->ops->name ? ld->ops->name : "???", i); | 242 | seq_printf(m, "%-10s %2d\n", ld->ops->name ? ld->ops->name : "???", i); |
237 | tty_ldisc_put(ld); | 243 | put_ldisc(ld); |
238 | return 0; | 244 | return 0; |
239 | } | 245 | } |
240 | 246 | ||
@@ -288,20 +294,17 @@ static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld) | |||
288 | * Locking: takes tty_ldisc_lock | 294 | * Locking: takes tty_ldisc_lock |
289 | */ | 295 | */ |
290 | 296 | ||
291 | static int tty_ldisc_try(struct tty_struct *tty) | 297 | static struct tty_ldisc *tty_ldisc_try(struct tty_struct *tty) |
292 | { | 298 | { |
293 | unsigned long flags; | 299 | unsigned long flags; |
294 | struct tty_ldisc *ld; | 300 | struct tty_ldisc *ld; |
295 | int ret = 0; | ||
296 | 301 | ||
297 | spin_lock_irqsave(&tty_ldisc_lock, flags); | 302 | spin_lock_irqsave(&tty_ldisc_lock, flags); |
298 | ld = tty->ldisc; | 303 | ld = NULL; |
299 | if (test_bit(TTY_LDISC, &tty->flags)) { | 304 | if (test_bit(TTY_LDISC, &tty->flags)) |
300 | ld->refcount++; | 305 | ld = get_ldisc(tty->ldisc); |
301 | ret = 1; | ||
302 | } | ||
303 | spin_unlock_irqrestore(&tty_ldisc_lock, flags); | 306 | spin_unlock_irqrestore(&tty_ldisc_lock, flags); |
304 | return ret; | 307 | return ld; |
305 | } | 308 | } |
306 | 309 | ||
307 | /** | 310 | /** |
@@ -322,10 +325,11 @@ static int tty_ldisc_try(struct tty_struct *tty) | |||
322 | 325 | ||
323 | struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty) | 326 | struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty) |
324 | { | 327 | { |
328 | struct tty_ldisc *ld; | ||
329 | |||
325 | /* wait_event is a macro */ | 330 | /* wait_event is a macro */ |
326 | wait_event(tty_ldisc_wait, tty_ldisc_try(tty)); | 331 | wait_event(tty_ldisc_wait, (ld = tty_ldisc_try(tty)) != NULL); |
327 | WARN_ON(tty->ldisc->refcount == 0); | 332 | return ld; |
328 | return tty->ldisc; | ||
329 | } | 333 | } |
330 | EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait); | 334 | EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait); |
331 | 335 | ||
@@ -342,9 +346,7 @@ EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait); | |||
342 | 346 | ||
343 | struct tty_ldisc *tty_ldisc_ref(struct tty_struct *tty) | 347 | struct tty_ldisc *tty_ldisc_ref(struct tty_struct *tty) |
344 | { | 348 | { |
345 | if (tty_ldisc_try(tty)) | 349 | return tty_ldisc_try(tty); |
346 | return tty->ldisc; | ||
347 | return NULL; | ||
348 | } | 350 | } |
349 | EXPORT_SYMBOL_GPL(tty_ldisc_ref); | 351 | EXPORT_SYMBOL_GPL(tty_ldisc_ref); |
350 | 352 | ||
@@ -360,21 +362,15 @@ EXPORT_SYMBOL_GPL(tty_ldisc_ref); | |||
360 | 362 | ||
361 | void tty_ldisc_deref(struct tty_ldisc *ld) | 363 | void tty_ldisc_deref(struct tty_ldisc *ld) |
362 | { | 364 | { |
363 | unsigned long flags; | 365 | put_ldisc(ld); |
364 | |||
365 | BUG_ON(ld == NULL); | ||
366 | |||
367 | spin_lock_irqsave(&tty_ldisc_lock, flags); | ||
368 | if (ld->refcount == 0) | ||
369 | printk(KERN_ERR "tty_ldisc_deref: no references.\n"); | ||
370 | else | ||
371 | ld->refcount--; | ||
372 | if (ld->refcount == 0) | ||
373 | wake_up(&tty_ldisc_wait); | ||
374 | spin_unlock_irqrestore(&tty_ldisc_lock, flags); | ||
375 | } | 366 | } |
376 | EXPORT_SYMBOL_GPL(tty_ldisc_deref); | 367 | EXPORT_SYMBOL_GPL(tty_ldisc_deref); |
377 | 368 | ||
369 | static inline void tty_ldisc_put(struct tty_ldisc *ld) | ||
370 | { | ||
371 | put_ldisc(ld); | ||
372 | } | ||
373 | |||
378 | /** | 374 | /** |
379 | * tty_ldisc_enable - allow ldisc use | 375 | * tty_ldisc_enable - allow ldisc use |
380 | * @tty: terminal to activate ldisc on | 376 | * @tty: terminal to activate ldisc on |
@@ -523,31 +519,6 @@ static int tty_ldisc_halt(struct tty_struct *tty) | |||
523 | } | 519 | } |
524 | 520 | ||
525 | /** | 521 | /** |
526 | * tty_ldisc_wait_idle - wait for the ldisc to become idle | ||
527 | * @tty: tty to wait for | ||
528 | * | ||
529 | * Wait for the line discipline to become idle. The discipline must | ||
530 | * have been halted for this to guarantee it remains idle. | ||
531 | * | ||
532 | * tty_ldisc_lock protects the ref counts currently. | ||
533 | */ | ||
534 | |||
535 | static int tty_ldisc_wait_idle(struct tty_struct *tty) | ||
536 | { | ||
537 | unsigned long flags; | ||
538 | spin_lock_irqsave(&tty_ldisc_lock, flags); | ||
539 | while (tty->ldisc->refcount) { | ||
540 | spin_unlock_irqrestore(&tty_ldisc_lock, flags); | ||
541 | if (wait_event_timeout(tty_ldisc_wait, | ||
542 | tty->ldisc->refcount == 0, 5 * HZ) == 0) | ||
543 | return -EBUSY; | ||
544 | spin_lock_irqsave(&tty_ldisc_lock, flags); | ||
545 | } | ||
546 | spin_unlock_irqrestore(&tty_ldisc_lock, flags); | ||
547 | return 0; | ||
548 | } | ||
549 | |||
550 | /** | ||
551 | * tty_set_ldisc - set line discipline | 522 | * tty_set_ldisc - set line discipline |
552 | * @tty: the terminal to set | 523 | * @tty: the terminal to set |
553 | * @ldisc: the line discipline | 524 | * @ldisc: the line discipline |
@@ -642,14 +613,6 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc) | |||
642 | 613 | ||
643 | flush_scheduled_work(); | 614 | flush_scheduled_work(); |
644 | 615 | ||
645 | /* Let any existing reference holders finish */ | ||
646 | retval = tty_ldisc_wait_idle(tty); | ||
647 | if (retval < 0) { | ||
648 | clear_bit(TTY_LDISC_CHANGING, &tty->flags); | ||
649 | tty_ldisc_put(new_ldisc); | ||
650 | return retval; | ||
651 | } | ||
652 | |||
653 | mutex_lock(&tty->ldisc_mutex); | 616 | mutex_lock(&tty->ldisc_mutex); |
654 | if (test_bit(TTY_HUPPED, &tty->flags)) { | 617 | if (test_bit(TTY_HUPPED, &tty->flags)) { |
655 | /* We were raced by the hangup method. It will have stomped | 618 | /* We were raced by the hangup method. It will have stomped |
@@ -795,7 +758,6 @@ void tty_ldisc_hangup(struct tty_struct *tty) | |||
795 | if (tty->ldisc) { /* Not yet closed */ | 758 | if (tty->ldisc) { /* Not yet closed */ |
796 | /* Switch back to N_TTY */ | 759 | /* Switch back to N_TTY */ |
797 | tty_ldisc_halt(tty); | 760 | tty_ldisc_halt(tty); |
798 | tty_ldisc_wait_idle(tty); | ||
799 | tty_ldisc_reinit(tty); | 761 | tty_ldisc_reinit(tty); |
800 | /* At this point we have a closed ldisc and we want to | 762 | /* At this point we have a closed ldisc and we want to |
801 | reopen it. We could defer this to the next open but | 763 | reopen it. We could defer this to the next open but |
@@ -860,14 +822,6 @@ void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty) | |||
860 | tty_ldisc_halt(tty); | 822 | tty_ldisc_halt(tty); |
861 | flush_scheduled_work(); | 823 | flush_scheduled_work(); |
862 | 824 | ||
863 | /* | ||
864 | * Wait for any short term users (we know they are just driver | ||
865 | * side waiters as the file is closing so user count on the file | ||
866 | * side is zero. | ||
867 | */ | ||
868 | |||
869 | tty_ldisc_wait_idle(tty); | ||
870 | |||
871 | mutex_lock(&tty->ldisc_mutex); | 825 | mutex_lock(&tty->ldisc_mutex); |
872 | /* | 826 | /* |
873 | * Now kill off the ldisc | 827 | * Now kill off the ldisc |