Commit d4820e44 authored by Heiko Carstens's avatar Heiko Carstens Committed by Martin Schwidefsky

[S390] sclp_vt220: fix scheduling while atomic bug.

The driver incorrectly assumed that putchar will only be called from
schedulable process context and therefore blocked and waited if no
free output buffers where available.
Since putchar may also be called from BH context this may lead to
deadlocks.
To fix this just return the number of characters accepted and let the
upper layer handle the rest.

The console write function will busy wait (sclp_sync_wait) until a
buffer is available again.

Cc: Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
Signed-off-by: default avatarHeiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: default avatarMartin Schwidefsky <schwidefsky@de.ibm.com>
parent c1bb7f31
...@@ -71,9 +71,6 @@ static struct list_head sclp_vt220_outqueue; ...@@ -71,9 +71,6 @@ static struct list_head sclp_vt220_outqueue;
/* Number of requests in outqueue */ /* Number of requests in outqueue */
static int sclp_vt220_outqueue_count; static int sclp_vt220_outqueue_count;
/* Wait queue used to delay write requests while we've run out of buffers */
static wait_queue_head_t sclp_vt220_waitq;
/* Timer used for delaying write requests to merge subsequent messages into /* Timer used for delaying write requests to merge subsequent messages into
* a single buffer */ * a single buffer */
static struct timer_list sclp_vt220_timer; static struct timer_list sclp_vt220_timer;
...@@ -133,7 +130,6 @@ sclp_vt220_process_queue(struct sclp_vt220_request *request) ...@@ -133,7 +130,6 @@ sclp_vt220_process_queue(struct sclp_vt220_request *request)
} while (request && __sclp_vt220_emit(request)); } while (request && __sclp_vt220_emit(request));
if (request == NULL && sclp_vt220_flush_later) if (request == NULL && sclp_vt220_flush_later)
sclp_vt220_emit_current(); sclp_vt220_emit_current();
wake_up(&sclp_vt220_waitq);
/* Check if the tty needs a wake up call */ /* Check if the tty needs a wake up call */
if (sclp_vt220_tty != NULL) { if (sclp_vt220_tty != NULL) {
tty_wakeup(sclp_vt220_tty); tty_wakeup(sclp_vt220_tty);
...@@ -383,7 +379,7 @@ sclp_vt220_timeout(unsigned long data) ...@@ -383,7 +379,7 @@ sclp_vt220_timeout(unsigned long data)
*/ */
static int static int
__sclp_vt220_write(const unsigned char *buf, int count, int do_schedule, __sclp_vt220_write(const unsigned char *buf, int count, int do_schedule,
int convertlf, int may_schedule) int convertlf, int may_fail)
{ {
unsigned long flags; unsigned long flags;
void *page; void *page;
...@@ -395,15 +391,14 @@ __sclp_vt220_write(const unsigned char *buf, int count, int do_schedule, ...@@ -395,15 +391,14 @@ __sclp_vt220_write(const unsigned char *buf, int count, int do_schedule,
overall_written = 0; overall_written = 0;
spin_lock_irqsave(&sclp_vt220_lock, flags); spin_lock_irqsave(&sclp_vt220_lock, flags);
do { do {
/* Create a sclp output buffer if none exists yet */ /* Create an sclp output buffer if none exists yet */
if (sclp_vt220_current_request == NULL) { if (sclp_vt220_current_request == NULL) {
while (list_empty(&sclp_vt220_empty)) { while (list_empty(&sclp_vt220_empty)) {
spin_unlock_irqrestore(&sclp_vt220_lock, flags); spin_unlock_irqrestore(&sclp_vt220_lock, flags);
if (in_interrupt() || !may_schedule) if (may_fail)
sclp_sync_wait(); goto out;
else else
wait_event(sclp_vt220_waitq, sclp_sync_wait();
!list_empty(&sclp_vt220_empty));
spin_lock_irqsave(&sclp_vt220_lock, flags); spin_lock_irqsave(&sclp_vt220_lock, flags);
} }
page = (void *) sclp_vt220_empty.next; page = (void *) sclp_vt220_empty.next;
...@@ -437,6 +432,7 @@ __sclp_vt220_write(const unsigned char *buf, int count, int do_schedule, ...@@ -437,6 +432,7 @@ __sclp_vt220_write(const unsigned char *buf, int count, int do_schedule,
add_timer(&sclp_vt220_timer); add_timer(&sclp_vt220_timer);
} }
spin_unlock_irqrestore(&sclp_vt220_lock, flags); spin_unlock_irqrestore(&sclp_vt220_lock, flags);
out:
return overall_written; return overall_written;
} }
...@@ -520,19 +516,11 @@ sclp_vt220_close(struct tty_struct *tty, struct file *filp) ...@@ -520,19 +516,11 @@ sclp_vt220_close(struct tty_struct *tty, struct file *filp)
* character to the tty device. If the kernel uses this routine, * character to the tty device. If the kernel uses this routine,
* it must call the flush_chars() routine (if defined) when it is * it must call the flush_chars() routine (if defined) when it is
* done stuffing characters into the driver. * done stuffing characters into the driver.
*
* NOTE: include/linux/tty_driver.h specifies that a character should be
* ignored if there is no room in the queue. This driver implements a different
* semantic in that it will block when there is no more room left.
*
* FIXME: putchar can currently be called from BH and other non blocking
* handlers so this semantic isn't a good idea.
*/ */
static int static int
sclp_vt220_put_char(struct tty_struct *tty, unsigned char ch) sclp_vt220_put_char(struct tty_struct *tty, unsigned char ch)
{ {
__sclp_vt220_write(&ch, 1, 0, 0, 1); return __sclp_vt220_write(&ch, 1, 0, 0, 1);
return 1;
} }
/* /*
...@@ -653,7 +641,6 @@ static int __init __sclp_vt220_init(void) ...@@ -653,7 +641,6 @@ static int __init __sclp_vt220_init(void)
spin_lock_init(&sclp_vt220_lock); spin_lock_init(&sclp_vt220_lock);
INIT_LIST_HEAD(&sclp_vt220_empty); INIT_LIST_HEAD(&sclp_vt220_empty);
INIT_LIST_HEAD(&sclp_vt220_outqueue); INIT_LIST_HEAD(&sclp_vt220_outqueue);
init_waitqueue_head(&sclp_vt220_waitq);
init_timer(&sclp_vt220_timer); init_timer(&sclp_vt220_timer);
sclp_vt220_current_request = NULL; sclp_vt220_current_request = NULL;
sclp_vt220_buffered_chars = 0; sclp_vt220_buffered_chars = 0;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment