diff options
author | Jeff Dike <jdike@addtoit.com> | 2007-02-10 04:43:52 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-02-11 13:51:21 -0500 |
commit | d79a580936396bbcd2f4fae2c6215f9cf81e3c0d (patch) | |
tree | 72c91c7475e2562c5459c4d6d0499cdd671e0b50 | |
parent | 5cf885d01f30be710a339976c485f92bb8a8946d (diff) |
[PATCH] uml: console locking fixes
Clean up the console driver locking. There are various problems here,
including sleeping under a spinlock and spinlock recursion, some of which are
fixed here. This patch deals with the locking involved with opens and closes.
The problem is that an mconsole request to change a console's configuration
can race with an open. Changing a configuration should only be done when a
console isn't opened. Also, an open must be looking at a stable
configuration. In addition, a get configuration request must observe the same
locking since it must also see a stable configuration. With the old locking,
it was possible for this to hang indefinitely in some cases because open would
block for a long time waiting for a connection from the host while holding the
lock needed by the mconsole request.
As explained in the long comment, this is fixed by adding a spinlock for the
use count and configuration and a mutex for the actual open and close.
Signed-off-by: Jeff Dike <jdike@addtoit.com>
Cc: Paolo 'Blaisorblade' Giarrusso <blaisorblade@yahoo.it>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | arch/um/drivers/line.c | 186 | ||||
-rw-r--r-- | arch/um/drivers/stdio_console.c | 6 | ||||
-rw-r--r-- | arch/um/include/line.h | 14 |
3 files changed, 134 insertions, 72 deletions
diff --git a/arch/um/drivers/line.c b/arch/um/drivers/line.c index 83301e1ef67c..799fca3644e9 100644 --- a/arch/um/drivers/line.c +++ b/arch/um/drivers/line.c | |||
@@ -191,7 +191,6 @@ void line_flush_buffer(struct tty_struct *tty) | |||
191 | /*XXX: copied from line_write, verify if it is correct!*/ | 191 | /*XXX: copied from line_write, verify if it is correct!*/ |
192 | if(tty->stopped) | 192 | if(tty->stopped) |
193 | return; | 193 | return; |
194 | //return 0; | ||
195 | 194 | ||
196 | spin_lock_irqsave(&line->lock, flags); | 195 | spin_lock_irqsave(&line->lock, flags); |
197 | err = flush_buffer(line); | 196 | err = flush_buffer(line); |
@@ -421,42 +420,84 @@ int line_setup_irq(int fd, int input, int output, struct line *line, void *data) | |||
421 | return err; | 420 | return err; |
422 | } | 421 | } |
423 | 422 | ||
423 | /* Normally, a driver like this can rely mostly on the tty layer | ||
424 | * locking, particularly when it comes to the driver structure. | ||
425 | * However, in this case, mconsole requests can come in "from the | ||
426 | * side", and race with opens and closes. | ||
427 | * | ||
428 | * The problem comes from line_setup not wanting to sleep if | ||
429 | * the device is open or being opened. This can happen because the | ||
430 | * first opener of a device is responsible for setting it up on the | ||
431 | * host, and that can sleep. The open of a port device will sleep | ||
432 | * until someone telnets to it. | ||
433 | * | ||
434 | * The obvious solution of putting everything under a mutex fails | ||
435 | * because then trying (and failing) to change the configuration of an | ||
436 | * open(ing) device will block until the open finishes. The right | ||
437 | * thing to happen is for it to fail immediately. | ||
438 | * | ||
439 | * We can put the opening (and closing) of the host device under a | ||
440 | * separate lock, but that has to be taken before the count lock is | ||
441 | * released. Otherwise, you open a window in which another open can | ||
442 | * come through and assume that the host side is opened and working. | ||
443 | * | ||
444 | * So, if the tty count is one, open will take the open mutex | ||
445 | * inside the count lock. Otherwise, it just returns. This will sleep | ||
446 | * if the last close is pending, and will block a setup or get_config, | ||
447 | * but that should not last long. | ||
448 | * | ||
449 | * So, what we end up with is that open and close take the count lock. | ||
450 | * If the first open or last close are happening, then the open mutex | ||
451 | * is taken inside the count lock and the host opening or closing is done. | ||
452 | * | ||
453 | * setup and get_config only take the count lock. setup modifies the | ||
454 | * device configuration only if the open count is zero. Arbitrarily | ||
455 | * long blocking of setup doesn't happen because something would have to be | ||
456 | * waiting for an open to happen. However, a second open with | ||
457 | * tty->count == 1 can't happen, and a close can't happen until the open | ||
458 | * had finished. | ||
459 | * | ||
460 | * We can't maintain our own count here because the tty layer doesn't | ||
461 | * match opens and closes. It will call close if an open failed, and | ||
462 | * a tty hangup will result in excess closes. So, we rely on | ||
463 | * tty->count instead. It is one on both the first open and last close. | ||
464 | */ | ||
465 | |||
424 | int line_open(struct line *lines, struct tty_struct *tty) | 466 | int line_open(struct line *lines, struct tty_struct *tty) |
425 | { | 467 | { |
426 | struct line *line; | 468 | struct line *line = &lines[tty->index]; |
427 | int err = -ENODEV; | 469 | int err = -ENODEV; |
428 | 470 | ||
429 | line = &lines[tty->index]; | 471 | spin_lock(&line->count_lock); |
430 | tty->driver_data = line; | 472 | if(!line->valid) |
473 | goto out_unlock; | ||
474 | |||
475 | err = 0; | ||
476 | if(tty->count > 1) | ||
477 | goto out_unlock; | ||
431 | 478 | ||
432 | /* The IRQ which takes this lock is not yet enabled and won't be run | 479 | mutex_lock(&line->open_mutex); |
433 | * before the end, so we don't need to use spin_lock_irq.*/ | 480 | spin_unlock(&line->count_lock); |
434 | spin_lock(&line->lock); | ||
435 | 481 | ||
436 | tty->driver_data = line; | 482 | tty->driver_data = line; |
437 | line->tty = tty; | 483 | line->tty = tty; |
438 | if(!line->valid) | ||
439 | goto out; | ||
440 | 484 | ||
441 | if(tty->count == 1){ | 485 | enable_chan(line); |
442 | /* Here the device is opened, if necessary, and interrupt | 486 | INIT_DELAYED_WORK(&line->task, line_timer_cb); |
443 | * is registered. | ||
444 | */ | ||
445 | enable_chan(line); | ||
446 | INIT_DELAYED_WORK(&line->task, line_timer_cb); | ||
447 | |||
448 | if(!line->sigio){ | ||
449 | chan_enable_winch(&line->chan_list, tty); | ||
450 | line->sigio = 1; | ||
451 | } | ||
452 | 487 | ||
453 | chan_window_size(&line->chan_list, &tty->winsize.ws_row, | 488 | if(!line->sigio){ |
454 | &tty->winsize.ws_col); | 489 | chan_enable_winch(&line->chan_list, tty); |
490 | line->sigio = 1; | ||
455 | } | 491 | } |
456 | 492 | ||
457 | err = 0; | 493 | chan_window_size(&line->chan_list, &tty->winsize.ws_row, |
458 | out: | 494 | &tty->winsize.ws_col); |
459 | spin_unlock(&line->lock); | 495 | |
496 | mutex_unlock(&line->open_mutex); | ||
497 | return err; | ||
498 | |||
499 | out_unlock: | ||
500 | spin_unlock(&line->count_lock); | ||
460 | return err; | 501 | return err; |
461 | } | 502 | } |
462 | 503 | ||
@@ -466,25 +507,38 @@ void line_close(struct tty_struct *tty, struct file * filp) | |||
466 | { | 507 | { |
467 | struct line *line = tty->driver_data; | 508 | struct line *line = tty->driver_data; |
468 | 509 | ||
469 | /* XXX: I assume this should be called in process context, not with | 510 | /* If line_open fails (and tty->driver_data is never set), |
470 | * interrupts disabled! | 511 | * tty_open will call line_close. So just return in this case. |
471 | */ | 512 | */ |
472 | spin_lock_irq(&line->lock); | 513 | if(line == NULL) |
514 | return; | ||
473 | 515 | ||
474 | /* We ignore the error anyway! */ | 516 | /* We ignore the error anyway! */ |
475 | flush_buffer(line); | 517 | flush_buffer(line); |
476 | 518 | ||
477 | if(tty->count == 1){ | 519 | spin_lock(&line->count_lock); |
478 | line->tty = NULL; | 520 | if(!line->valid) |
479 | tty->driver_data = NULL; | 521 | goto out_unlock; |
522 | |||
523 | if(tty->count > 1) | ||
524 | goto out_unlock; | ||
480 | 525 | ||
481 | if(line->sigio){ | 526 | mutex_lock(&line->open_mutex); |
482 | unregister_winch(tty); | 527 | spin_unlock(&line->count_lock); |
483 | line->sigio = 0; | 528 | |
484 | } | 529 | line->tty = NULL; |
530 | tty->driver_data = NULL; | ||
531 | |||
532 | if(line->sigio){ | ||
533 | unregister_winch(tty); | ||
534 | line->sigio = 0; | ||
485 | } | 535 | } |
486 | 536 | ||
487 | spin_unlock_irq(&line->lock); | 537 | mutex_unlock(&line->open_mutex); |
538 | return; | ||
539 | |||
540 | out_unlock: | ||
541 | spin_unlock(&line->count_lock); | ||
488 | } | 542 | } |
489 | 543 | ||
490 | void close_lines(struct line *lines, int nlines) | 544 | void close_lines(struct line *lines, int nlines) |
@@ -495,6 +549,30 @@ void close_lines(struct line *lines, int nlines) | |||
495 | close_chan(&lines[i].chan_list, 0); | 549 | close_chan(&lines[i].chan_list, 0); |
496 | } | 550 | } |
497 | 551 | ||
552 | static void setup_one_line(struct line *lines, int n, char *init, int init_prio) | ||
553 | { | ||
554 | struct line *line = &lines[n]; | ||
555 | |||
556 | spin_lock(&line->count_lock); | ||
557 | |||
558 | if(line->tty != NULL){ | ||
559 | printk("line_setup - device %d is open\n", n); | ||
560 | goto out; | ||
561 | } | ||
562 | |||
563 | if (line->init_pri <= init_prio){ | ||
564 | line->init_pri = init_prio; | ||
565 | if (!strcmp(init, "none")) | ||
566 | line->valid = 0; | ||
567 | else { | ||
568 | line->init_str = init; | ||
569 | line->valid = 1; | ||
570 | } | ||
571 | } | ||
572 | out: | ||
573 | spin_unlock(&line->count_lock); | ||
574 | } | ||
575 | |||
498 | /* Common setup code for both startup command line and mconsole initialization. | 576 | /* Common setup code for both startup command line and mconsole initialization. |
499 | * @lines contains the array (of size @num) to modify; | 577 | * @lines contains the array (of size @num) to modify; |
500 | * @init is the setup string; | 578 | * @init is the setup string; |
@@ -526,32 +604,11 @@ int line_setup(struct line *lines, unsigned int num, char *init) | |||
526 | n, num - 1); | 604 | n, num - 1); |
527 | return 0; | 605 | return 0; |
528 | } | 606 | } |
529 | else if (n >= 0){ | 607 | else if (n >= 0) |
530 | if (lines[n].tty != NULL) { | 608 | setup_one_line(lines, n, init, INIT_ONE); |
531 | printk("line_setup - device %d is open\n", n); | ||
532 | return 0; | ||
533 | } | ||
534 | if (lines[n].init_pri <= INIT_ONE){ | ||
535 | lines[n].init_pri = INIT_ONE; | ||
536 | if (!strcmp(init, "none")) | ||
537 | lines[n].valid = 0; | ||
538 | else { | ||
539 | lines[n].init_str = init; | ||
540 | lines[n].valid = 1; | ||
541 | } | ||
542 | } | ||
543 | } | ||
544 | else { | 609 | else { |
545 | for(i = 0; i < num; i++){ | 610 | for(i = 0; i < num; i++) |
546 | if(lines[i].init_pri <= INIT_ALL){ | 611 | setup_one_line(lines, i, init, INIT_ALL); |
547 | lines[i].init_pri = INIT_ALL; | ||
548 | if(!strcmp(init, "none")) lines[i].valid = 0; | ||
549 | else { | ||
550 | lines[i].init_str = init; | ||
551 | lines[i].valid = 1; | ||
552 | } | ||
553 | } | ||
554 | } | ||
555 | } | 612 | } |
556 | return n == -1 ? num : n; | 613 | return n == -1 ? num : n; |
557 | } | 614 | } |
@@ -602,13 +659,13 @@ int line_get_config(char *name, struct line *lines, unsigned int num, char *str, | |||
602 | 659 | ||
603 | line = &lines[dev]; | 660 | line = &lines[dev]; |
604 | 661 | ||
605 | spin_lock(&line->lock); | 662 | spin_lock(&line->count_lock); |
606 | if(!line->valid) | 663 | if(!line->valid) |
607 | CONFIG_CHUNK(str, size, n, "none", 1); | 664 | CONFIG_CHUNK(str, size, n, "none", 1); |
608 | else if(line->tty == NULL) | 665 | else if(line->tty == NULL) |
609 | CONFIG_CHUNK(str, size, n, line->init_str, 1); | 666 | CONFIG_CHUNK(str, size, n, line->init_str, 1); |
610 | else n = chan_config_string(&line->chan_list, str, size, error_out); | 667 | else n = chan_config_string(&line->chan_list, str, size, error_out); |
611 | spin_unlock(&line->lock); | 668 | spin_unlock(&line->count_lock); |
612 | 669 | ||
613 | return n; | 670 | return n; |
614 | } | 671 | } |
@@ -688,6 +745,7 @@ void lines_init(struct line *lines, int nlines, struct chan_opts *opts) | |||
688 | for(i = 0; i < nlines; i++){ | 745 | for(i = 0; i < nlines; i++){ |
689 | line = &lines[i]; | 746 | line = &lines[i]; |
690 | INIT_LIST_HEAD(&line->chan_list); | 747 | INIT_LIST_HEAD(&line->chan_list); |
748 | mutex_init(&line->open_mutex); | ||
691 | 749 | ||
692 | if(line->init_str == NULL) | 750 | if(line->init_str == NULL) |
693 | continue; | 751 | continue; |
diff --git a/arch/um/drivers/stdio_console.c b/arch/um/drivers/stdio_console.c index 7a4897e27f42..9b2dd0b8a43b 100644 --- a/arch/um/drivers/stdio_console.c +++ b/arch/um/drivers/stdio_console.c | |||
@@ -83,9 +83,9 @@ static struct lines console_lines = LINES_INIT(MAX_TTYS); | |||
83 | /* The array is initialized by line_init, which is an initcall. The | 83 | /* The array is initialized by line_init, which is an initcall. The |
84 | * individual elements are protected by individual semaphores. | 84 | * individual elements are protected by individual semaphores. |
85 | */ | 85 | */ |
86 | struct line vts[MAX_TTYS] = { LINE_INIT(CONFIG_CON_ZERO_CHAN, &driver), | 86 | static struct line vts[MAX_TTYS] = { LINE_INIT(CONFIG_CON_ZERO_CHAN, &driver), |
87 | [ 1 ... MAX_TTYS - 1 ] = | 87 | [ 1 ... MAX_TTYS - 1 ] = |
88 | LINE_INIT(CONFIG_CON_CHAN, &driver) }; | 88 | LINE_INIT(CONFIG_CON_CHAN, &driver) }; |
89 | 89 | ||
90 | static int con_config(char *str) | 90 | static int con_config(char *str) |
91 | { | 91 | { |
diff --git a/arch/um/include/line.h b/arch/um/include/line.h index 5f232ae89fbb..b79643eeee08 100644 --- a/arch/um/include/line.h +++ b/arch/um/include/line.h | |||
@@ -11,6 +11,7 @@ | |||
11 | #include "linux/tty.h" | 11 | #include "linux/tty.h" |
12 | #include "linux/interrupt.h" | 12 | #include "linux/interrupt.h" |
13 | #include "linux/spinlock.h" | 13 | #include "linux/spinlock.h" |
14 | #include "linux/mutex.h" | ||
14 | #include "chan_user.h" | 15 | #include "chan_user.h" |
15 | #include "mconsole_kern.h" | 16 | #include "mconsole_kern.h" |
16 | 17 | ||
@@ -32,15 +33,17 @@ struct line_driver { | |||
32 | 33 | ||
33 | struct line { | 34 | struct line { |
34 | struct tty_struct *tty; | 35 | struct tty_struct *tty; |
36 | spinlock_t count_lock; | ||
37 | int valid; | ||
38 | |||
39 | struct mutex open_mutex; | ||
35 | char *init_str; | 40 | char *init_str; |
36 | int init_pri; | 41 | int init_pri; |
37 | struct list_head chan_list; | 42 | struct list_head chan_list; |
38 | int valid; | 43 | |
39 | int count; | ||
40 | int throttled; | ||
41 | /*This lock is actually, mostly, local to*/ | 44 | /*This lock is actually, mostly, local to*/ |
42 | spinlock_t lock; | 45 | spinlock_t lock; |
43 | 46 | int throttled; | |
44 | /* Yes, this is a real circular buffer. | 47 | /* Yes, this is a real circular buffer. |
45 | * XXX: And this should become a struct kfifo! | 48 | * XXX: And this should become a struct kfifo! |
46 | * | 49 | * |
@@ -57,7 +60,8 @@ struct line { | |||
57 | }; | 60 | }; |
58 | 61 | ||
59 | #define LINE_INIT(str, d) \ | 62 | #define LINE_INIT(str, d) \ |
60 | { .init_str = str, \ | 63 | { .count_lock = SPIN_LOCK_UNLOCKED, \ |
64 | .init_str = str, \ | ||
61 | .init_pri = INIT_STATIC, \ | 65 | .init_pri = INIT_STATIC, \ |
62 | .valid = 1, \ | 66 | .valid = 1, \ |
63 | .lock = SPIN_LOCK_UNLOCKED, \ | 67 | .lock = SPIN_LOCK_UNLOCKED, \ |