Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 611e097d authored by Christian Borntraeger's avatar Christian Borntraeger Committed by Rusty Russell
Browse files

hvc_console: rework setup to replace irq functions with callbacks



This patch tries to change hvc_console to not use request_irq/free_irq if
the backend does not use irqs. This allows virtio_console to use hvc_console
without having a linker reference to request_irq/free_irq.

In addition, together with patch 2/3 it improves the performance for virtio
console input. (an earlier version of this patch was tested by Yajin on lguest)

The irq specific code is moved to hvc_irq.c and selected by the drivers that
use irqs (System p, System i, XEN).

I replaced "int irq" with the opaque "int data". The request_irq and
free_irq calls are replaced with notifier_add and notifier_del. I have also
changed the code a bit to call the notifier_add and notifier_del inside the
spinlock area as the callbacks are found via hp->ops.

Changes since last version:
o remove ifdef
o reintroduce "irq_requested" as "notified"
o cleanups, sparse..

I did not move the timer based polling into a separate polling scheme. I
played with several variants, but it seems we need to sleep/schedule in
a thread even for irq based consoles, as there are throttleing and buffer
size constraints.

I also kept hvc_struct defined in hvc_console.h so that hvc_irq.c can access
the irq_requested element.

Feedback is appreciated. virtio_console is currently the only available console
for kvm on s390. I plan to push this change as soon as all affected parties
agree on it. I would love to get test results from System p, Xen etc.

Signed-off-by: default avatarChristian Borntraeger <borntraeger@de.ibm.com>
Signed-off-by: default avatarRusty Russell <rusty@rustcorp.com.au>
parent 066f4d82
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -578,11 +578,14 @@ config HVC_DRIVER
	  It will automatically be selected if one of the back-end console drivers
	  is selected.

config HVC_IRQ
	bool

config HVC_CONSOLE
	bool "pSeries Hypervisor Virtual Console support"
	depends on PPC_PSERIES
	select HVC_DRIVER
	select HVC_IRQ
	help
	  pSeries machines when partitioned support a hypervisor virtual
	  console. This driver allows each pSeries partition to have a console
@@ -593,6 +596,7 @@ config HVC_ISERIES
	depends on PPC_ISERIES
	default y
	select HVC_DRIVER
	select HVC_IRQ
	help
	  iSeries machines support a hypervisor virtual console.

@@ -614,6 +618,7 @@ config HVC_XEN
	bool "Xen Hypervisor Console support"
	depends on XEN
	select HVC_DRIVER
	select HVC_IRQ
	default y
	help
	  Xen virtual console device driver
+1 −0
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ obj-$(CONFIG_HVC_ISERIES) += hvc_iseries.o
obj-$(CONFIG_HVC_RTAS)		+= hvc_rtas.o
obj-$(CONFIG_HVC_BEAT)		+= hvc_beat.o
obj-$(CONFIG_HVC_DRIVER)	+= hvc_console.o
obj-$(CONFIG_HVC_IRQ)		+= hvc_irq.o
obj-$(CONFIG_HVC_XEN)		+= hvc_xen.o
obj-$(CONFIG_VIRTIO_CONSOLE)	+= virtio_console.o
obj-$(CONFIG_RAW_DRIVER)	+= raw.o
+18 −63
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@
#include <linux/init.h>
#include <linux/kbd_kern.h>
#include <linux/kernel.h>
#include <linux/kref.h>
#include <linux/kthread.h>
#include <linux/list.h>
#include <linux/module.h>
@@ -75,23 +74,6 @@ static int hvc_init(void);
static int sysrq_pressed;
#endif

struct hvc_struct {
	spinlock_t lock;
	int index;
	struct tty_struct *tty;
	unsigned int count;
	int do_wakeup;
	char *outbuf;
	int outbuf_size;
	int n_outbuf;
	uint32_t vtermno;
	struct hv_ops *ops;
	int irq_requested;
	int irq;
	struct list_head next;
	struct kref kref; /* ref count & hvc_struct lifetime */
};

/* dynamic list of hvc_struct instances */
static LIST_HEAD(hvc_structs);

@@ -300,26 +282,12 @@ int hvc_instantiate(uint32_t vtermno, int index, struct hv_ops *ops)
}

/* Wake the sleeping khvcd */
static void hvc_kick(void)
void hvc_kick(void)
{
	hvc_kicked = 1;
	wake_up_process(hvc_task);
}

static int hvc_poll(struct hvc_struct *hp);

/*
 * NOTE: This API isn't used if the console adapter doesn't support interrupts.
 * In this case the console is poll driven.
 */
static irqreturn_t hvc_handle_interrupt(int irq, void *dev_instance)
{
	/* if hvc_poll request a repoll, then kick the hvcd thread */
	if (hvc_poll(dev_instance))
		hvc_kick();
	return IRQ_HANDLED;
}

static void hvc_unthrottle(struct tty_struct *tty)
{
	hvc_kick();
@@ -333,7 +301,6 @@ static int hvc_open(struct tty_struct *tty, struct file * filp)
{
	struct hvc_struct *hp;
	unsigned long flags;
	int irq = 0;
	int rc = 0;

	/* Auto increments kref reference if found. */
@@ -352,18 +319,15 @@ static int hvc_open(struct tty_struct *tty, struct file * filp)
	tty->low_latency = 1; /* Makes flushes to ldisc synchronous. */

	hp->tty = tty;
	/* Save for request_irq outside of spin_lock. */
	irq = hp->irq;
	if (irq)
		hp->irq_requested = 1;

	if (hp->ops->notifier_add)
		rc = hp->ops->notifier_add(hp, hp->data);

	spin_unlock_irqrestore(&hp->lock, flags);
	/* check error, fallback to non-irq */
	if (irq)
		rc = request_irq(irq, hvc_handle_interrupt, IRQF_DISABLED, "hvc_console", hp);


	/*
	 * If the request_irq() fails and we return an error.  The tty layer
	 * If the notifier fails we return an error.  The tty layer
	 * will call hvc_close() after a failed open but we don't want to clean
	 * up there so we'll clean up here and clear out the previously set
	 * tty fields and return the kref reference.
@@ -371,7 +335,6 @@ static int hvc_open(struct tty_struct *tty, struct file * filp)
	if (rc) {
		spin_lock_irqsave(&hp->lock, flags);
		hp->tty = NULL;
		hp->irq_requested = 0;
		spin_unlock_irqrestore(&hp->lock, flags);
		tty->driver_data = NULL;
		kref_put(&hp->kref, destroy_hvc_struct);
@@ -386,7 +349,6 @@ 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;
	int irq = 0;
	unsigned long flags;

	if (tty_hung_up_p(filp))
@@ -404,9 +366,8 @@ static void hvc_close(struct tty_struct *tty, struct file * filp)
	spin_lock_irqsave(&hp->lock, flags);

	if (--hp->count == 0) {
		if (hp->irq_requested)
			irq = hp->irq;
		hp->irq_requested = 0;
		if (hp->ops->notifier_del)
			hp->ops->notifier_del(hp, hp->data);

		/* We are done with the tty pointer now. */
		hp->tty = NULL;
@@ -418,10 +379,6 @@ 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);

		if (irq)
			free_irq(irq, hp);

	} else {
		if (hp->count < 0)
			printk(KERN_ERR "hvc_close %X: oops, count is %d\n",
@@ -436,7 +393,6 @@ static void hvc_hangup(struct tty_struct *tty)
{
	struct hvc_struct *hp = tty->driver_data;
	unsigned long flags;
	int irq = 0;
	int temp_open_count;

	if (!hp)
@@ -458,13 +414,12 @@ static void hvc_hangup(struct tty_struct *tty)
	hp->count = 0;
	hp->n_outbuf = 0;
	hp->tty = NULL;
	if (hp->irq_requested)
		/* Saved for use outside of spin_lock. */
		irq = hp->irq;
	hp->irq_requested = 0;

	if (hp->ops->notifier_del)
			hp->ops->notifier_del(hp, hp->data);

	spin_unlock_irqrestore(&hp->lock, flags);
	if (irq)
		free_irq(irq, hp);

	while(temp_open_count) {
		--temp_open_count;
		kref_put(&hp->kref, destroy_hvc_struct);
@@ -575,7 +530,7 @@ static u32 timeout = MIN_TIMEOUT;
#define HVC_POLL_READ	0x00000001
#define HVC_POLL_WRITE	0x00000002

static int hvc_poll(struct hvc_struct *hp)
int hvc_poll(struct hvc_struct *hp)
{
	struct tty_struct *tty;
	int i, n, poll_mask = 0;
@@ -602,10 +557,10 @@ static int hvc_poll(struct hvc_struct *hp)
	if (test_bit(TTY_THROTTLED, &tty->flags))
		goto throttled;

	/* If we aren't interrupt driven and aren't throttled, we always
	/* If we aren't notifier driven and aren't throttled, we always
	 * request a reschedule
	 */
	if (hp->irq == 0)
	if (!hp->irq_requested)
		poll_mask |= HVC_POLL_READ;

	/* Read data if any */
@@ -733,7 +688,7 @@ static const struct tty_operations hvc_ops = {
	.chars_in_buffer = hvc_chars_in_buffer,
};

struct hvc_struct __devinit *hvc_alloc(uint32_t vtermno, int irq,
struct hvc_struct __devinit *hvc_alloc(uint32_t vtermno, int data,
					struct hv_ops *ops, int outbuf_size)
{
	struct hvc_struct *hp;
@@ -754,7 +709,7 @@ struct hvc_struct __devinit *hvc_alloc(uint32_t vtermno, int irq,
	memset(hp, 0x00, sizeof(*hp));

	hp->vtermno = vtermno;
	hp->irq = irq;
	hp->data = data;
	hp->ops = ops;
	hp->outbuf_size = outbuf_size;
	hp->outbuf = &((char *)hp)[ALIGN(sizeof(*hp), sizeof(long))];
+31 −4
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@

#ifndef HVC_CONSOLE_H
#define HVC_CONSOLE_H
#include <linux/kref.h>

/*
 * This is the max number of console adapters that can/will be found as
@@ -42,24 +43,50 @@
 */
#define HVC_ALLOC_TTY_ADAPTERS	8

struct hvc_struct {
	spinlock_t lock;
	int index;
	struct tty_struct *tty;
	unsigned int count;
	int do_wakeup;
	char *outbuf;
	int outbuf_size;
	int n_outbuf;
	uint32_t vtermno;
	struct hv_ops *ops;
	int irq_requested;
	int data;
	struct list_head next;
	struct kref kref; /* ref count & hvc_struct lifetime */
};

/* implemented by a low level driver */
struct hv_ops {
	int (*get_chars)(uint32_t vtermno, char *buf, int count);
	int (*put_chars)(uint32_t vtermno, const char *buf, int count);
};

struct hvc_struct;
	/* Callbacks for notification. Called in open and close */
	int (*notifier_add)(struct hvc_struct *hp, int irq);
	void (*notifier_del)(struct hvc_struct *hp, int irq);
};

/* Register a vterm and a slot index for use as a console (console_init) */
extern int hvc_instantiate(uint32_t vtermno, int index, struct hv_ops *ops);

/* register a vterm for hvc tty operation (module_init or hotplug add) */
extern struct hvc_struct * __devinit hvc_alloc(uint32_t vtermno, int irq,
extern struct hvc_struct * __devinit hvc_alloc(uint32_t vtermno, int data,
				struct hv_ops *ops, int outbuf_size);
/* remove a vterm from hvc tty operation (modele_exit or hotplug remove) */
/* remove a vterm from hvc tty operation (module_exit or hotplug remove) */
extern int __devexit hvc_remove(struct hvc_struct *hp);

/* data available */
int hvc_poll(struct hvc_struct *hp);
void hvc_kick(void);

/* default notifier for irq based notification */
extern int notifier_add_irq(struct hvc_struct *hp, int data);
extern void notifier_del_irq(struct hvc_struct *hp, int data);


#if defined(CONFIG_XMON) && defined(CONFIG_SMP)
#include <asm/xmon.h>

drivers/char/hvc_irq.c

0 → 100644
+44 −0
Original line number Diff line number Diff line
/*
 * Copyright IBM Corp. 2001,2008
 *
 * This file contains the IRQ specific code for hvc_console
 *
 */

#include <linux/interrupt.h>

#include "hvc_console.h"

static irqreturn_t hvc_handle_interrupt(int irq, void *dev_instance)
{
	/* if hvc_poll request a repoll, then kick the hvcd thread */
	if (hvc_poll(dev_instance))
		hvc_kick();
	return IRQ_HANDLED;
}

/*
 * For IRQ based systems these callbacks can be used
 */
int notifier_add_irq(struct hvc_struct *hp, int irq)
{
	int rc;

	if (!irq) {
		hp->irq_requested = 0;
		return 0;
	}
	rc = request_irq(irq, hvc_handle_interrupt, IRQF_DISABLED,
			   "hvc_console", hp);
	if (!rc)
		hp->irq_requested = 1;
	return rc;
}

void notifier_del_irq(struct hvc_struct *hp, int irq)
{
	if (!irq)
		return;
	free_irq(irq, hp);
	hp->irq_requested = 0;
}
Loading