summaryrefslogtreecommitdiff
path: root/drivers/tty/hvc/hvc_console.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/tty/hvc/hvc_console.c')
-rw-r--r--drivers/tty/hvc/hvc_console.c326
1 files changed, 193 insertions, 133 deletions
diff --git a/drivers/tty/hvc/hvc_console.c b/drivers/tty/hvc/hvc_console.c
index a8d399188242..6b58f340f210 100644
--- a/drivers/tty/hvc/hvc_console.c
+++ b/drivers/tty/hvc/hvc_console.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2001 Anton Blanchard <anton@au.ibm.com>, IBM
* Copyright (C) 2001 Paul Mackerras <paulus@au.ibm.com>, IBM
@@ -6,20 +7,6 @@
*
* Additional Author(s):
* Ryan S. Arnold <rsa@us.ibm.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/console.h>
@@ -62,7 +49,7 @@
#define N_OUTBUF 16
#define N_INBUF 16
-#define __ALIGNED__ __attribute__((__aligned__(sizeof(long))))
+#define __ALIGNED__ __attribute__((__aligned__(L1_CACHE_BYTES)))
static struct tty_driver *hvc_driver;
static struct task_struct *hvc_task;
@@ -86,7 +73,7 @@ static LIST_HEAD(hvc_structs);
* Protect the list of hvc_struct instances from inserts and removals during
* list traversal.
*/
-static DEFINE_SPINLOCK(hvc_structs_lock);
+static DEFINE_MUTEX(hvc_structs_mutex);
/*
* This value is used to assign a tty->index value to a hvc_struct based
@@ -96,7 +83,7 @@ static DEFINE_SPINLOCK(hvc_structs_lock);
static int last_hvc = -1;
/*
- * Do not call this function with either the hvc_structs_lock or the hvc_struct
+ * Do not call this function with either the hvc_structs_mutex or the hvc_struct
* lock held. If successful, this function increments the kref reference
* count against the target hvc_struct so it should be released when finished.
*/
@@ -105,24 +92,46 @@ static struct hvc_struct *hvc_get_by_index(int index)
struct hvc_struct *hp;
unsigned long flags;
- spin_lock(&hvc_structs_lock);
+ mutex_lock(&hvc_structs_mutex);
list_for_each_entry(hp, &hvc_structs, next) {
spin_lock_irqsave(&hp->lock, flags);
if (hp->index == index) {
tty_port_get(&hp->port);
spin_unlock_irqrestore(&hp->lock, flags);
- spin_unlock(&hvc_structs_lock);
+ mutex_unlock(&hvc_structs_mutex);
return hp;
}
spin_unlock_irqrestore(&hp->lock, flags);
}
hp = NULL;
+ mutex_unlock(&hvc_structs_mutex);
- spin_unlock(&hvc_structs_lock);
return hp;
}
+static int __hvc_flush(const struct hv_ops *ops, uint32_t vtermno, bool wait)
+{
+ if (wait)
+ might_sleep();
+
+ if (ops->flush)
+ return ops->flush(vtermno, wait);
+ return 0;
+}
+
+static int hvc_console_flush(const struct hv_ops *ops, uint32_t vtermno)
+{
+ return __hvc_flush(ops, vtermno, false);
+}
+
+/*
+ * Wait for the console to flush before writing more to it. This sleeps.
+ */
+static int hvc_flush(struct hvc_struct *hp)
+{
+ return __hvc_flush(hp->ops, hp->vtermno, true);
+}
/*
* Initial console vtermnos for console API usage prior to full console
@@ -169,15 +178,20 @@ static void hvc_console_print(struct console *co, const char *b,
if (r <= 0) {
/* throw away characters on error
* but spin in case of -EAGAIN */
- if (r != -EAGAIN)
+ if (r != -EAGAIN) {
i = 0;
- } else if (r > 0) {
+ } else {
+ hvc_console_flush(cons_ops[index],
+ vtermnos[index]);
+ }
+ } else {
i -= r;
if (i > 0)
memmove(c, c+r, i);
}
}
}
+ hvc_console_flush(cons_ops[index], vtermnos[index]);
}
static struct tty_driver *hvc_console_device(struct console *c, int *index)
@@ -237,21 +251,21 @@ static void hvc_port_destruct(struct tty_port *port)
struct hvc_struct *hp = container_of(port, struct hvc_struct, port);
unsigned long flags;
- spin_lock(&hvc_structs_lock);
+ mutex_lock(&hvc_structs_mutex);
spin_lock_irqsave(&hp->lock, flags);
list_del(&(hp->next));
spin_unlock_irqrestore(&hp->lock, flags);
- spin_unlock(&hvc_structs_lock);
+ mutex_unlock(&hvc_structs_mutex);
kfree(hp);
}
static void hvc_check_console(int index)
{
- /* Already enabled, bail out */
- if (hvc_console.flags & CON_ENABLED)
+ /* Already registered, bail out */
+ if (console_is_registered(&hvc_console))
return;
/* If this index is what the user requested, then register
@@ -278,7 +292,7 @@ int hvc_instantiate(uint32_t vtermno, int index, const struct hv_ops *ops)
if (vtermnos[index] != -1)
return -1;
- /* make sure no no tty has been registered in this index */
+ /* make sure no tty has been registered in this index */
hp = hvc_get_by_index(index);
if (hp) {
tty_port_put(&hp->port);
@@ -288,10 +302,6 @@ int hvc_instantiate(uint32_t vtermno, int index, const struct hv_ops *ops)
vtermnos[index] = vtermno;
cons_ops[index] = ops;
- /* reserve all indices up to and including this index */
- if (last_hvc < index)
- last_hvc = index;
-
/* check if we need to re-register the kernel console */
hvc_check_console(index);
@@ -361,15 +371,14 @@ static int hvc_open(struct tty_struct *tty, struct file * filp)
* tty fields and return the kref reference.
*/
if (rc) {
- tty_port_tty_set(&hp->port, NULL);
- tty->driver_data = NULL;
- tty_port_put(&hp->port);
printk(KERN_ERR "hvc_open: request_irq failed with rc %d.\n", rc);
- } else
+ } else {
/* We are ready... raise DTR/RTS */
if (C_BAUD(tty))
if (hp->ops->dtr_rts)
- hp->ops->dtr_rts(hp, 1);
+ hp->ops->dtr_rts(hp, true);
+ tty_port_set_initialized(&hp->port, true);
+ }
/* Force wakeup of the polling thread */
hvc_kick();
@@ -379,22 +388,12 @@ static int hvc_open(struct tty_struct *tty, struct file * filp)
static void hvc_close(struct tty_struct *tty, struct file * filp)
{
- struct hvc_struct *hp;
+ struct hvc_struct *hp = tty->driver_data;
unsigned long flags;
if (tty_hung_up_p(filp))
return;
- /*
- * No driver_data means that this close was issued after a failed
- * hvc_open by the tty layer's release_dev() function and we can just
- * exit cleanly because the kref reference wasn't made.
- */
- if (!tty->driver_data)
- return;
-
- hp = tty->driver_data;
-
spin_lock_irqsave(&hp->port.lock, flags);
if (--hp->port.count == 0) {
@@ -402,9 +401,12 @@ static void hvc_close(struct tty_struct *tty, struct file * filp)
/* We are done with the tty pointer now. */
tty_port_tty_set(&hp->port, NULL);
+ if (!tty_port_initialized(&hp->port))
+ return;
+
if (C_HUPCL(tty))
if (hp->ops->dtr_rts)
- hp->ops->dtr_rts(hp, 0);
+ hp->ops->dtr_rts(hp, false);
if (hp->ops->notifier_del)
hp->ops->notifier_del(hp, hp->data);
@@ -418,6 +420,7 @@ static void hvc_close(struct tty_struct *tty, struct file * filp)
* waking periodically to check chars_in_buffer().
*/
tty_wait_until_sent(tty, HVC_CLOSE_WAIT);
+ tty_port_set_initialized(&hp->port, false);
} else {
if (hp->port.count < 0)
printk(KERN_ERR "hvc_close %X: oops, count is %d\n",
@@ -493,11 +496,11 @@ static int hvc_push(struct hvc_struct *hp)
return n;
}
-static int hvc_write(struct tty_struct *tty, const unsigned char *buf, int count)
+static ssize_t hvc_write(struct tty_struct *tty, const u8 *buf, size_t count)
{
struct hvc_struct *hp = tty->driver_data;
unsigned long flags;
- int rsize, written = 0;
+ size_t rsize, written = 0;
/* This write was probably executed during a tty close. */
if (!hp)
@@ -507,29 +510,43 @@ static int hvc_write(struct tty_struct *tty, const unsigned char *buf, int count
if (hp->port.count <= 0)
return -EIO;
- spin_lock_irqsave(&hp->lock, flags);
+ while (count > 0) {
+ int ret = 0;
- /* Push pending writes */
- if (hp->n_outbuf > 0)
- hvc_push(hp);
-
- while (count > 0 && (rsize = hp->outbuf_size - hp->n_outbuf) > 0) {
- if (rsize > count)
- rsize = count;
- memcpy(hp->outbuf + hp->n_outbuf, buf, rsize);
- count -= rsize;
- buf += rsize;
- hp->n_outbuf += rsize;
- written += rsize;
- hvc_push(hp);
+ spin_lock_irqsave(&hp->lock, flags);
+
+ rsize = hp->outbuf_size - hp->n_outbuf;
+
+ if (rsize) {
+ if (rsize > count)
+ rsize = count;
+ memcpy(hp->outbuf + hp->n_outbuf, buf, rsize);
+ count -= rsize;
+ buf += rsize;
+ hp->n_outbuf += rsize;
+ written += rsize;
+ }
+
+ if (hp->n_outbuf > 0)
+ ret = hvc_push(hp);
+
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ if (!ret)
+ break;
+
+ if (count) {
+ if (hp->n_outbuf > 0)
+ hvc_flush(hp);
+ cond_resched();
+ }
}
- spin_unlock_irqrestore(&hp->lock, flags);
/*
- * Racy, but harmless, kick thread if there is still pending data.
+ * Kick thread to flush if there's still pending data
+ * or to wakeup the write queue.
*/
- if (hp->n_outbuf)
- hvc_kick();
+ hvc_kick();
return written;
}
@@ -569,7 +586,7 @@ static void hvc_set_winsz(struct work_struct *work)
* how much write room the driver can guarantee will be sent OR BUFFERED. This
* driver MUST honor the return value.
*/
-static int hvc_write_room(struct tty_struct *tty)
+static unsigned int hvc_write_room(struct tty_struct *tty)
{
struct hvc_struct *hp = tty->driver_data;
@@ -579,7 +596,7 @@ static int hvc_write_room(struct tty_struct *tty)
return hp->outbuf_size - hp->n_outbuf;
}
-static int hvc_chars_in_buffer(struct tty_struct *tty)
+static unsigned int hvc_chars_in_buffer(struct tty_struct *tty)
{
struct hvc_struct *hp = tty->driver_data;
@@ -600,13 +617,22 @@ static int hvc_chars_in_buffer(struct tty_struct *tty)
#define MAX_TIMEOUT (2000)
static u32 timeout = MIN_TIMEOUT;
+/*
+ * Maximum number of bytes to get from the console driver if hvc_poll is
+ * called from driver (and can't sleep). Any more than this and we break
+ * and start polling with khvcd. This value was derived from an OpenBMC
+ * console with the OPAL driver that results in about 0.25ms interrupts off
+ * latency.
+ */
+#define HVC_ATOMIC_READ_MAX 128
+
#define HVC_POLL_READ 0x00000001
#define HVC_POLL_WRITE 0x00000002
-int hvc_poll(struct hvc_struct *hp)
+static int __hvc_poll(struct hvc_struct *hp, bool may_sleep)
{
struct tty_struct *tty;
- int i, n, poll_mask = 0;
+ int i, n, count, poll_mask = 0;
char buf[N_INBUF] __ALIGNED__;
unsigned long flags;
int read_total = 0;
@@ -625,6 +651,12 @@ int hvc_poll(struct hvc_struct *hp)
timeout = (written_total) ? 0 : MIN_TIMEOUT;
}
+ if (may_sleep) {
+ spin_unlock_irqrestore(&hp->lock, flags);
+ cond_resched();
+ spin_lock_irqsave(&hp->lock, flags);
+ }
+
/* No tty attached, just skip */
tty = tty_port_tty_get(&hp->port);
if (tty == NULL)
@@ -632,7 +664,7 @@ int hvc_poll(struct hvc_struct *hp)
/* Now check if we can get data (are we throttled ?) */
if (tty_throttled(tty))
- goto throttled;
+ goto out;
/* If we aren't notifier driven and aren't throttled, we always
* request a reschedule
@@ -640,57 +672,73 @@ int hvc_poll(struct hvc_struct *hp)
if (!hp->irq_requested)
poll_mask |= HVC_POLL_READ;
+ read_again:
/* Read data if any */
- for (;;) {
- int count = tty_buffer_request_room(&hp->port, N_INBUF);
+ count = tty_buffer_request_room(&hp->port, N_INBUF);
+
+ /* If flip is full, just reschedule a later read */
+ if (count == 0) {
+ poll_mask |= HVC_POLL_READ;
+ goto out;
+ }
- /* If flip is full, just reschedule a later read */
- if (count == 0) {
+ n = hp->ops->get_chars(hp->vtermno, buf, count);
+ if (n <= 0) {
+ /* Hangup the tty when disconnected from host */
+ if (n == -EPIPE) {
+ spin_unlock_irqrestore(&hp->lock, flags);
+ tty_hangup(tty);
+ spin_lock_irqsave(&hp->lock, flags);
+ } else if ( n == -EAGAIN ) {
+ /*
+ * Some back-ends can only ensure a certain min
+ * num of bytes read, which may be > 'count'.
+ * Let the tty clear the flip buff to make room.
+ */
poll_mask |= HVC_POLL_READ;
- break;
}
+ goto out;
+ }
- n = hp->ops->get_chars(hp->vtermno, buf, count);
- if (n <= 0) {
- /* Hangup the tty when disconnected from host */
- if (n == -EPIPE) {
- spin_unlock_irqrestore(&hp->lock, flags);
- tty_hangup(tty);
- spin_lock_irqsave(&hp->lock, flags);
- } else if ( n == -EAGAIN ) {
- /*
- * Some back-ends can only ensure a certain min
- * num of bytes read, which may be > 'count'.
- * Let the tty clear the flip buff to make room.
- */
- poll_mask |= HVC_POLL_READ;
- }
- break;
- }
- for (i = 0; i < n; ++i) {
+ for (i = 0; i < n; ++i) {
#ifdef CONFIG_MAGIC_SYSRQ
- if (hp->index == hvc_console.index) {
- /* Handle the SysRq Hack */
- /* XXX should support a sequence */
- if (buf[i] == '\x0f') { /* ^O */
- /* if ^O is pressed again, reset
- * sysrq_pressed and flip ^O char */
- sysrq_pressed = !sysrq_pressed;
- if (sysrq_pressed)
- continue;
- } else if (sysrq_pressed) {
- handle_sysrq(buf[i]);
- sysrq_pressed = 0;
+ if (hp->index == hvc_console.index) {
+ /* Handle the SysRq Hack */
+ /* XXX should support a sequence */
+ if (buf[i] == '\x0f') { /* ^O */
+ /* if ^O is pressed again, reset
+ * sysrq_pressed and flip ^O char */
+ sysrq_pressed = !sysrq_pressed;
+ if (sysrq_pressed)
continue;
- }
+ } else if (sysrq_pressed) {
+ handle_sysrq(buf[i]);
+ sysrq_pressed = 0;
+ continue;
}
-#endif /* CONFIG_MAGIC_SYSRQ */
- tty_insert_flip_char(&hp->port, buf[i], 0);
}
+#endif /* CONFIG_MAGIC_SYSRQ */
+ tty_insert_flip_char(&hp->port, buf[i], 0);
+ }
+ read_total += n;
- read_total += n;
+ if (may_sleep) {
+ /* Keep going until the flip is full */
+ spin_unlock_irqrestore(&hp->lock, flags);
+ cond_resched();
+ spin_lock_irqsave(&hp->lock, flags);
+ goto read_again;
+ } else if (read_total < HVC_ATOMIC_READ_MAX) {
+ /* Break and defer if it's a large read in atomic */
+ goto read_again;
}
- throttled:
+
+ /*
+ * Latency break, schedule another poll immediately.
+ */
+ poll_mask |= HVC_POLL_READ;
+
+ out:
/* Wakeup write queue if necessary */
if (hp->do_wakeup) {
hp->do_wakeup = 0;
@@ -710,6 +758,11 @@ int hvc_poll(struct hvc_struct *hp)
return poll_mask;
}
+
+int hvc_poll(struct hvc_struct *hp)
+{
+ return __hvc_poll(hp, false);
+}
EXPORT_SYMBOL_GPL(hvc_poll);
/**
@@ -746,11 +799,12 @@ static int khvcd(void *unused)
try_to_freeze();
wmb();
if (!cpus_are_in_xmon()) {
- spin_lock(&hvc_structs_lock);
+ mutex_lock(&hvc_structs_mutex);
list_for_each_entry(hp, &hvc_structs, next) {
- poll_mask |= hvc_poll(hp);
+ poll_mask |= __hvc_poll(hp, true);
+ cond_resched();
}
- spin_unlock(&hvc_structs_lock);
+ mutex_unlock(&hvc_structs_mutex);
} else
poll_mask |= HVC_POLL_READ;
if (hvc_kicked)
@@ -868,8 +922,7 @@ struct hvc_struct *hvc_alloc(uint32_t vtermno, int data,
return ERR_PTR(err);
}
- hp = kzalloc(ALIGN(sizeof(*hp), sizeof(long)) + outbuf_size,
- GFP_KERNEL);
+ hp = kzalloc(struct_size(hp, outbuf, outbuf_size), GFP_KERNEL);
if (!hp)
return ERR_PTR(-ENOMEM);
@@ -877,14 +930,13 @@ struct hvc_struct *hvc_alloc(uint32_t vtermno, int data,
hp->data = data;
hp->ops = ops;
hp->outbuf_size = outbuf_size;
- hp->outbuf = &((char *)hp)[ALIGN(sizeof(*hp), sizeof(long))];
tty_port_init(&hp->port);
hp->port.ops = &hvc_port_ops;
INIT_WORK(&hp->tty_resize, hvc_set_winsz);
spin_lock_init(&hp->lock);
- spin_lock(&hvc_structs_lock);
+ mutex_lock(&hvc_structs_mutex);
/*
* find index to use:
@@ -895,16 +947,25 @@ struct hvc_struct *hvc_alloc(uint32_t vtermno, int data,
cons_ops[i] == hp->ops)
break;
- /* no matching slot, just use a counter */
- if (i >= MAX_NR_HVC_CONSOLES)
- i = ++last_hvc;
+ if (i >= MAX_NR_HVC_CONSOLES) {
+
+ /* find 'empty' slot for console */
+ for (i = 0; i < MAX_NR_HVC_CONSOLES && vtermnos[i] != -1; i++) {
+ }
+
+ /* no matching slot, just use a counter */
+ if (i == MAX_NR_HVC_CONSOLES)
+ i = ++last_hvc + MAX_NR_HVC_CONSOLES;
+ }
hp->index = i;
- cons_ops[i] = ops;
- vtermnos[i] = vtermno;
+ if (i < MAX_NR_HVC_CONSOLES) {
+ cons_ops[i] = ops;
+ vtermnos[i] = vtermno;
+ }
list_add_tail(&(hp->next), &hvc_structs);
- spin_unlock(&hvc_structs_lock);
+ mutex_unlock(&hvc_structs_mutex);
/* check if we need to re-register the kernel console */
hvc_check_console(i);
@@ -913,7 +974,7 @@ struct hvc_struct *hvc_alloc(uint32_t vtermno, int data,
}
EXPORT_SYMBOL_GPL(hvc_alloc);
-int hvc_remove(struct hvc_struct *hp)
+void hvc_remove(struct hvc_struct *hp)
{
unsigned long flags;
struct tty_struct *tty;
@@ -947,7 +1008,6 @@ int hvc_remove(struct hvc_struct *hp)
tty_vhangup(tty);
tty_kref_put(tty);
}
- return 0;
}
EXPORT_SYMBOL_GPL(hvc_remove);
@@ -958,9 +1018,10 @@ static int hvc_init(void)
int err;
/* We need more than hvc_count adapters due to hotplug additions. */
- drv = alloc_tty_driver(HVC_ALLOC_TTY_ADAPTERS);
- if (!drv) {
- err = -ENOMEM;
+ drv = tty_alloc_driver(HVC_ALLOC_TTY_ADAPTERS, TTY_DRIVER_REAL_RAW |
+ TTY_DRIVER_RESET_TERMIOS);
+ if (IS_ERR(drv)) {
+ err = PTR_ERR(drv);
goto out;
}
@@ -970,7 +1031,6 @@ static int hvc_init(void)
drv->minor_start = HVC_MINOR;
drv->type = TTY_DRIVER_TYPE_SYSTEM;
drv->init_termios = tty_std_termios;
- drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS;
tty_set_operations(drv, &hvc_ops);
/* Always start the kthread because there can be hotplug vty adapters
@@ -1000,7 +1060,7 @@ stop_thread:
kthread_stop(hvc_task);
hvc_task = NULL;
put_tty:
- put_tty_driver(drv);
+ tty_driver_kref_put(drv);
out:
return err;
}