diff options
| author | Takashi Iwai <tiwai@suse.de> | 2011-08-08 14:30:29 +0200 | 
|---|---|---|
| committer | Takashi Iwai <tiwai@suse.de> | 2011-08-08 14:30:29 +0200 | 
| commit | 0a2d31b62dba9b5b92a38c67c9cc42630513662a (patch) | |
| tree | f755d74ec85248de645e10c45ed1a2ed467530f6 /drivers/tty/hvc | |
| parent | 8039290a91c5dc4414093c086987a5d7738fe2fd (diff) | |
| parent | df944f66784e6d4f2f50739263a4947885d8b6ae (diff) | |
Merge branch 'fix/kconfig' into for-linus
Diffstat (limited to 'drivers/tty/hvc')
| -rw-r--r-- | drivers/tty/hvc/Kconfig | 5 | ||||
| -rw-r--r-- | drivers/tty/hvc/Makefile | 3 | ||||
| -rw-r--r-- | drivers/tty/hvc/hvc_console.c | 70 | ||||
| -rw-r--r-- | drivers/tty/hvc/hvc_console.h | 4 | ||||
| -rw-r--r-- | drivers/tty/hvc/hvc_vio.c | 408 | ||||
| -rw-r--r-- | drivers/tty/hvc/hvsi.c | 129 | ||||
| -rw-r--r-- | drivers/tty/hvc/hvsi_lib.c | 426 | 
7 files changed, 897 insertions, 148 deletions
| diff --git a/drivers/tty/hvc/Kconfig b/drivers/tty/hvc/Kconfig index 6f2c9809f1fb..e371753ba921 100644 --- a/drivers/tty/hvc/Kconfig +++ b/drivers/tty/hvc/Kconfig @@ -19,6 +19,11 @@ config HVC_CONSOLE  	  console. This driver allows each pSeries partition to have a console  	  which is accessed via the HMC. +config HVC_OLD_HVSI +	bool "Old driver for pSeries serial port (/dev/hvsi*)" +	depends on HVC_CONSOLE +	default n +  config HVC_ISERIES  	bool "iSeries Hypervisor Virtual Console support"  	depends on PPC_ISERIES diff --git a/drivers/tty/hvc/Makefile b/drivers/tty/hvc/Makefile index 40a25d93fe52..e29205316376 100644 --- a/drivers/tty/hvc/Makefile +++ b/drivers/tty/hvc/Makefile @@ -1,4 +1,5 @@ -obj-$(CONFIG_HVC_CONSOLE)	+= hvc_vio.o hvsi.o +obj-$(CONFIG_HVC_CONSOLE)	+= hvc_vio.o hvsi_lib.o +obj-$(CONFIG_HVC_OLD_HVSI)	+= hvsi.o  obj-$(CONFIG_HVC_ISERIES)	+= hvc_iseries.o  obj-$(CONFIG_HVC_RTAS)		+= hvc_rtas.o  obj-$(CONFIG_HVC_TILE)		+= hvc_tile.o diff --git a/drivers/tty/hvc/hvc_console.c b/drivers/tty/hvc/hvc_console.c index e9cba13ee800..e1aaf4f309b3 100644 --- a/drivers/tty/hvc/hvc_console.c +++ b/drivers/tty/hvc/hvc_console.c @@ -39,6 +39,7 @@  #include <linux/delay.h>  #include <linux/freezer.h>  #include <linux/slab.h> +#include <linux/serial_core.h>  #include <asm/uaccess.h> @@ -163,8 +164,10 @@ static void hvc_console_print(struct console *co, const char *b,  		} else {  			r = cons_ops[index]->put_chars(vtermnos[index], c, i);  			if (r <= 0) { -				/* throw away chars on error */ -				i = 0; +				/* throw away characters on error +				 * but spin in case of -EAGAIN */ +				if (r != -EAGAIN) +					i = 0;  			} else if (r > 0) {  				i -= r;  				if (i > 0) @@ -184,7 +187,7 @@ static struct tty_driver *hvc_console_device(struct console *c, int *index)  }  static int __init hvc_console_setup(struct console *co, char *options) -{ +{	  	if (co->index < 0 || co->index >= MAX_NR_HVC_CONSOLES)  		return -ENODEV; @@ -448,7 +451,7 @@ static int hvc_push(struct hvc_struct *hp)  	n = hp->ops->put_chars(hp->vtermno, hp->outbuf, hp->n_outbuf);  	if (n <= 0) { -		if (n == 0) { +		if (n == 0 || n == -EAGAIN) {  			hp->do_wakeup = 1;  			return 0;  		} @@ -745,6 +748,58 @@ static int khvcd(void *unused)  	return 0;  } +static int hvc_tiocmget(struct tty_struct *tty) +{ +	struct hvc_struct *hp = tty->driver_data; + +	if (!hp || !hp->ops->tiocmget) +		return -EINVAL; +	return hp->ops->tiocmget(hp); +} + +static int hvc_tiocmset(struct tty_struct *tty, +			unsigned int set, unsigned int clear) +{ +	struct hvc_struct *hp = tty->driver_data; + +	if (!hp || !hp->ops->tiocmset) +		return -EINVAL; +	return hp->ops->tiocmset(hp, set, clear); +} + +#ifdef CONFIG_CONSOLE_POLL +int hvc_poll_init(struct tty_driver *driver, int line, char *options) +{ +	return 0; +} + +static int hvc_poll_get_char(struct tty_driver *driver, int line) +{ +	struct tty_struct *tty = driver->ttys[0]; +	struct hvc_struct *hp = tty->driver_data; +	int n; +	char ch; + +	n = hp->ops->get_chars(hp->vtermno, &ch, 1); + +	if (n == 0) +		return NO_POLL_CHAR; + +	return ch; +} + +static void hvc_poll_put_char(struct tty_driver *driver, int line, char ch) +{ +	struct tty_struct *tty = driver->ttys[0]; +	struct hvc_struct *hp = tty->driver_data; +	int n; + +	do { +		n = hp->ops->put_chars(hp->vtermno, &ch, 1); +	} while (n <= 0); +} +#endif +  static const struct tty_operations hvc_ops = {  	.open = hvc_open,  	.close = hvc_close, @@ -753,6 +808,13 @@ static const struct tty_operations hvc_ops = {  	.unthrottle = hvc_unthrottle,  	.write_room = hvc_write_room,  	.chars_in_buffer = hvc_chars_in_buffer, +	.tiocmget = hvc_tiocmget, +	.tiocmset = hvc_tiocmset, +#ifdef CONFIG_CONSOLE_POLL +	.poll_init = hvc_poll_init, +	.poll_get_char = hvc_poll_get_char, +	.poll_put_char = hvc_poll_put_char, +#endif  };  struct hvc_struct *hvc_alloc(uint32_t vtermno, int data, diff --git a/drivers/tty/hvc/hvc_console.h b/drivers/tty/hvc/hvc_console.h index 54381eba4e4a..c335a1492a54 100644 --- a/drivers/tty/hvc/hvc_console.h +++ b/drivers/tty/hvc/hvc_console.h @@ -73,6 +73,10 @@ struct hv_ops {  	int (*notifier_add)(struct hvc_struct *hp, int irq);  	void (*notifier_del)(struct hvc_struct *hp, int irq);  	void (*notifier_hangup)(struct hvc_struct *hp, int irq); + +	/* tiocmget/set implementation */ +	int (*tiocmget)(struct hvc_struct *hp); +	int (*tiocmset)(struct hvc_struct *hp, unsigned int set, unsigned int clear);  };  /* Register a vterm and a slot index for use as a console (console_init) */ diff --git a/drivers/tty/hvc/hvc_vio.c b/drivers/tty/hvc/hvc_vio.c index e6eea1485244..130aace67f31 100644 --- a/drivers/tty/hvc/hvc_vio.c +++ b/drivers/tty/hvc/hvc_vio.c @@ -27,15 +27,27 @@   * 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 + * + * TODO: + * + *   - handle error in sending hvsi protocol packets + *   - retry nego on subsequent sends ?   */ +#undef DEBUG +  #include <linux/types.h>  #include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/console.h>  #include <asm/hvconsole.h>  #include <asm/vio.h>  #include <asm/prom.h>  #include <asm/firmware.h> +#include <asm/hvsi.h> +#include <asm/udbg.h>  #include "hvc_console.h" @@ -43,59 +55,236 @@ static const char hvc_driver_name[] = "hvc_console";  static struct vio_device_id hvc_driver_table[] __devinitdata = {  	{"serial", "hvterm1"}, +#ifndef HVC_OLD_HVSI +	{"serial", "hvterm-protocol"}, +#endif  	{ "", "" }  };  MODULE_DEVICE_TABLE(vio, hvc_driver_table); -static int filtered_get_chars(uint32_t vtermno, char *buf, int count) +typedef enum hv_protocol { +	HV_PROTOCOL_RAW, +	HV_PROTOCOL_HVSI +} hv_protocol_t; + +struct hvterm_priv { +	u32			termno;	/* HV term number */ +	hv_protocol_t		proto;	/* Raw data or HVSI packets */ +	struct hvsi_priv	hvsi;	/* HVSI specific data */ +	spinlock_t		buf_lock; +	char			buf[SIZE_VIO_GET_CHARS]; +	int			left; +	int			offset; +}; +static struct hvterm_priv *hvterm_privs[MAX_NR_HVC_CONSOLES]; +/* For early boot console */ +static struct hvterm_priv hvterm_priv0; + +static int hvterm_raw_get_chars(uint32_t vtermno, char *buf, int count)  { -	unsigned long got; -	int i; +	struct hvterm_priv *pv = hvterm_privs[vtermno]; +	unsigned long i; +	unsigned long flags; +	int got; -	/* -	 * Vio firmware will read up to SIZE_VIO_GET_CHARS at its own discretion -	 * so we play safe and avoid the situation where got > count which could -	 * overload the flip buffer. -	 */ -	if (count < SIZE_VIO_GET_CHARS) -		return -EAGAIN; +	if (WARN_ON(!pv)) +		return 0; -	got = hvc_get_chars(vtermno, buf, count); +	spin_lock_irqsave(&pv->buf_lock, flags); -	/* -	 * Work around a HV bug where it gives us a null -	 * after every \r.  -- paulus -	 */ -	for (i = 1; i < got; ++i) { -		if (buf[i] == 0 && buf[i-1] == '\r') { -			--got; -			if (i < got) -				memmove(&buf[i], &buf[i+1], -					got - i); +	if (pv->left == 0) { +		pv->offset = 0; +		pv->left = hvc_get_chars(pv->termno, pv->buf, count); + +		/* +		 * Work around a HV bug where it gives us a null +		 * after every \r.  -- paulus +		 */ +		for (i = 1; i < pv->left; ++i) { +			if (pv->buf[i] == 0 && pv->buf[i-1] == '\r') { +				--pv->left; +				if (i < pv->left) { +					memmove(&pv->buf[i], &pv->buf[i+1], +						pv->left - i); +				} +			}  		}  	} + +	got = min(count, pv->left); +	memcpy(buf, &pv->buf[pv->offset], got); +	pv->offset += got; +	pv->left -= got; + +	spin_unlock_irqrestore(&pv->buf_lock, flags); +  	return got;  } -static const struct hv_ops hvc_get_put_ops = { -	.get_chars = filtered_get_chars, -	.put_chars = hvc_put_chars, +static int hvterm_raw_put_chars(uint32_t vtermno, const char *buf, int count) +{ +	struct hvterm_priv *pv = hvterm_privs[vtermno]; + +	if (WARN_ON(!pv)) +		return 0; + +	return hvc_put_chars(pv->termno, buf, count); +} + +static const struct hv_ops hvterm_raw_ops = { +	.get_chars = hvterm_raw_get_chars, +	.put_chars = hvterm_raw_put_chars,  	.notifier_add = notifier_add_irq,  	.notifier_del = notifier_del_irq,  	.notifier_hangup = notifier_hangup_irq,  }; +static int hvterm_hvsi_get_chars(uint32_t vtermno, char *buf, int count) +{ +	struct hvterm_priv *pv = hvterm_privs[vtermno]; + +	if (WARN_ON(!pv)) +		return 0; + +	return hvsilib_get_chars(&pv->hvsi, buf, count); +} + +static int hvterm_hvsi_put_chars(uint32_t vtermno, const char *buf, int count) +{ +	struct hvterm_priv *pv = hvterm_privs[vtermno]; + +	if (WARN_ON(!pv)) +		return 0; + +	return hvsilib_put_chars(&pv->hvsi, buf, count); +} + +static int hvterm_hvsi_open(struct hvc_struct *hp, int data) +{ +	struct hvterm_priv *pv = hvterm_privs[hp->vtermno]; +	int rc; + +	pr_devel("HVSI@%x: open !\n", pv->termno); + +	rc = notifier_add_irq(hp, data); +	if (rc) +		return rc; + +	return hvsilib_open(&pv->hvsi, hp); +} + +static void hvterm_hvsi_close(struct hvc_struct *hp, int data) +{ +	struct hvterm_priv *pv = hvterm_privs[hp->vtermno]; + +	pr_devel("HVSI@%x: do close !\n", pv->termno); + +	hvsilib_close(&pv->hvsi, hp); + +	notifier_del_irq(hp, data); +} + +void hvterm_hvsi_hangup(struct hvc_struct *hp, int data) +{ +	struct hvterm_priv *pv = hvterm_privs[hp->vtermno]; + +	pr_devel("HVSI@%x: do hangup !\n", pv->termno); + +	hvsilib_close(&pv->hvsi, hp); + +	notifier_hangup_irq(hp, data); +} + +static int hvterm_hvsi_tiocmget(struct hvc_struct *hp) +{ +	struct hvterm_priv *pv = hvterm_privs[hp->vtermno]; + +	if (!pv) +		return -EINVAL; +	return pv->hvsi.mctrl; +} + +static int hvterm_hvsi_tiocmset(struct hvc_struct *hp, unsigned int set, +				unsigned int clear) +{ +	struct hvterm_priv *pv = hvterm_privs[hp->vtermno]; + +	pr_devel("HVSI@%x: Set modem control, set=%x,clr=%x\n", +		 pv->termno, set, clear); + +	if (set & TIOCM_DTR) +		hvsilib_write_mctrl(&pv->hvsi, 1); +	else if (clear & TIOCM_DTR) +		hvsilib_write_mctrl(&pv->hvsi, 0); + +	return 0; +} + +static const struct hv_ops hvterm_hvsi_ops = { +	.get_chars = hvterm_hvsi_get_chars, +	.put_chars = hvterm_hvsi_put_chars, +	.notifier_add = hvterm_hvsi_open, +	.notifier_del = hvterm_hvsi_close, +	.notifier_hangup = hvterm_hvsi_hangup, +	.tiocmget = hvterm_hvsi_tiocmget, +	.tiocmset = hvterm_hvsi_tiocmset, +}; +  static int __devinit hvc_vio_probe(struct vio_dev *vdev, -				const struct vio_device_id *id) +				   const struct vio_device_id *id)  { +	const struct hv_ops *ops;  	struct hvc_struct *hp; +	struct hvterm_priv *pv; +	hv_protocol_t proto; +	int i, termno = -1;  	/* probed with invalid parameters. */  	if (!vdev || !id)  		return -EPERM; -	hp = hvc_alloc(vdev->unit_address, vdev->irq, &hvc_get_put_ops, -			MAX_VIO_PUT_CHARS); +	if (of_device_is_compatible(vdev->dev.of_node, "hvterm1")) { +		proto = HV_PROTOCOL_RAW; +		ops = &hvterm_raw_ops; +	} else if (of_device_is_compatible(vdev->dev.of_node, "hvterm-protocol")) { +		proto = HV_PROTOCOL_HVSI; +		ops = &hvterm_hvsi_ops; +	} else { +		pr_err("hvc_vio: Unkown protocol for %s\n", vdev->dev.of_node->full_name); +		return -ENXIO; +	} + +	pr_devel("hvc_vio_probe() device %s, using %s protocol\n", +		 vdev->dev.of_node->full_name, +		 proto == HV_PROTOCOL_RAW ? "raw" : "hvsi"); + +	/* Is it our boot one ? */ +	if (hvterm_privs[0] == &hvterm_priv0 && +	    vdev->unit_address == hvterm_priv0.termno) { +		pv = hvterm_privs[0]; +		termno = 0; +		pr_devel("->boot console, using termno 0\n"); +	} +	/* nope, allocate a new one */ +	else { +		for (i = 0; i < MAX_NR_HVC_CONSOLES && termno < 0; i++) +			if (!hvterm_privs[i]) +				termno = i; +		pr_devel("->non-boot console, using termno %d\n", termno); +		if (termno < 0) +			return -ENODEV; +		pv = kzalloc(sizeof(struct hvterm_priv), GFP_KERNEL); +		if (!pv) +			return -ENOMEM; +		pv->termno = vdev->unit_address; +		pv->proto = proto; +		spin_lock_init(&pv->buf_lock); +		hvterm_privs[termno] = pv; +		hvsilib_init(&pv->hvsi, hvc_get_chars, hvc_put_chars, +			     pv->termno, 0); +	} + +	hp = hvc_alloc(termno, vdev->irq, ops, MAX_VIO_PUT_CHARS);  	if (IS_ERR(hp))  		return PTR_ERR(hp);  	dev_set_drvdata(&vdev->dev, hp); @@ -106,8 +295,16 @@ static int __devinit hvc_vio_probe(struct vio_dev *vdev,  static int __devexit hvc_vio_remove(struct vio_dev *vdev)  {  	struct hvc_struct *hp = dev_get_drvdata(&vdev->dev); +	int rc, termno; -	return hvc_remove(hp); +	termno = hp->vtermno; +	rc = hvc_remove(hp); +	if (rc == 0) { +		if (hvterm_privs[termno] != &hvterm_priv0) +			kfree(hvterm_privs[termno]); +		hvterm_privs[termno] = NULL; +	} +	return rc;  }  static struct vio_driver hvc_vio_driver = { @@ -140,34 +337,149 @@ static void __exit hvc_vio_exit(void)  }  module_exit(hvc_vio_exit); -/* the device tree order defines our numbering */ -static int hvc_find_vtys(void) +static void udbg_hvc_putc(char c)  { -	struct device_node *vty; -	int num_found = 0; +	int count = -1; -	for (vty = of_find_node_by_name(NULL, "vty"); vty != NULL; -			vty = of_find_node_by_name(vty, "vty")) { -		const uint32_t *vtermno; +	if (c == '\n') +		udbg_hvc_putc('\r'); -		/* We have statically defined space for only a certain number -		 * of console adapters. -		 */ -		if (num_found >= MAX_NR_HVC_CONSOLES) { -			of_node_put(vty); +	do { +		switch(hvterm_priv0.proto) { +		case HV_PROTOCOL_RAW: +			count = hvterm_raw_put_chars(0, &c, 1); +			break; +		case HV_PROTOCOL_HVSI: +			count = hvterm_hvsi_put_chars(0, &c, 1);  			break;  		} +	} while(count == 0); +} + +static int udbg_hvc_getc_poll(void) +{ +	int rc = 0; +	char c; -		vtermno = of_get_property(vty, "reg", NULL); -		if (!vtermno) -			continue; +	switch(hvterm_priv0.proto) { +	case HV_PROTOCOL_RAW: +		rc = hvterm_raw_get_chars(0, &c, 1); +		break; +	case HV_PROTOCOL_HVSI: +		rc = hvterm_hvsi_get_chars(0, &c, 1); +		break; +	} +	if (!rc) +		return -1; +	return c; +} -		if (of_device_is_compatible(vty, "hvterm1")) { -			hvc_instantiate(*vtermno, num_found, &hvc_get_put_ops); -			++num_found; +static int udbg_hvc_getc(void) +{ +	int ch; +	for (;;) { +		ch = udbg_hvc_getc_poll(); +		if (ch == -1) { +			/* This shouldn't be needed...but... */ +			volatile unsigned long delay; +			for (delay=0; delay < 2000000; delay++) +				; +		} else { +			return ch;  		}  	} +} + +void __init hvc_vio_init_early(void) +{ +	struct device_node *stdout_node; +	const u32 *termno; +	const char *name; +	const struct hv_ops *ops; + +	/* find the boot console from /chosen/stdout */ +	if (!of_chosen) +		return; +	name = of_get_property(of_chosen, "linux,stdout-path", NULL); +	if (name == NULL) +		return; +	stdout_node = of_find_node_by_path(name); +	if (!stdout_node) +		return; +	name = of_get_property(stdout_node, "name", NULL); +	if (!name) { +		printk(KERN_WARNING "stdout node missing 'name' property!\n"); +		goto out; +	} + +	/* Check if it's a virtual terminal */ +	if (strncmp(name, "vty", 3) != 0) +		goto out; +	termno = of_get_property(stdout_node, "reg", NULL); +	if (termno == NULL) +		goto out; +	hvterm_priv0.termno = *termno; +	spin_lock_init(&hvterm_priv0.buf_lock); +	hvterm_privs[0] = &hvterm_priv0; + +	/* Check the protocol */ +	if (of_device_is_compatible(stdout_node, "hvterm1")) { +		hvterm_priv0.proto = HV_PROTOCOL_RAW; +		ops = &hvterm_raw_ops; +	} +	else if (of_device_is_compatible(stdout_node, "hvterm-protocol")) { +		hvterm_priv0.proto = HV_PROTOCOL_HVSI; +		ops = &hvterm_hvsi_ops; +		hvsilib_init(&hvterm_priv0.hvsi, hvc_get_chars, hvc_put_chars, +			     hvterm_priv0.termno, 1); +		/* HVSI, perform the handshake now */ +		hvsilib_establish(&hvterm_priv0.hvsi); +	} else +		goto out; +	udbg_putc = udbg_hvc_putc; +	udbg_getc = udbg_hvc_getc; +	udbg_getc_poll = udbg_hvc_getc_poll; +#ifdef HVC_OLD_HVSI +	/* When using the old HVSI driver don't register the HVC +	 * backend for HVSI, only do udbg +	 */ +	if (hvterm_priv0.proto == HV_PROTOCOL_HVSI) +		goto out; +#endif +	add_preferred_console("hvc", 0, NULL); +	hvc_instantiate(0, 0, ops); +out: +	of_node_put(stdout_node); +} -	return num_found; +/* call this from early_init() for a working debug console on + * vterm capable LPAR machines + */ +#ifdef CONFIG_PPC_EARLY_DEBUG_LPAR +void __init udbg_init_debug_lpar(void) +{ +	hvterm_privs[0] = &hvterm_priv0; +	hvterm_priv0.termno = 0; +	hvterm_priv0.proto = HV_PROTOCOL_RAW; +	spin_lock_init(&hvterm_priv0.buf_lock); +	udbg_putc = udbg_hvc_putc; +	udbg_getc = udbg_hvc_getc; +	udbg_getc_poll = udbg_hvc_getc_poll; +} +#endif /* CONFIG_PPC_EARLY_DEBUG_LPAR */ + +#ifdef CONFIG_PPC_EARLY_DEBUG_LPAR_HVSI +void __init udbg_init_debug_lpar_hvsi(void) +{ +	hvterm_privs[0] = &hvterm_priv0; +	hvterm_priv0.termno = CONFIG_PPC_EARLY_DEBUG_HVSI_VTERMNO; +	hvterm_priv0.proto = HV_PROTOCOL_HVSI; +	spin_lock_init(&hvterm_priv0.buf_lock); +	udbg_putc = udbg_hvc_putc; +	udbg_getc = udbg_hvc_getc; +	udbg_getc_poll = udbg_hvc_getc_poll; +	hvsilib_init(&hvterm_priv0.hvsi, hvc_get_chars, hvc_put_chars, +		     hvterm_priv0.termno, 1); +	hvsilib_establish(&hvterm_priv0.hvsi);  } -console_initcall(hvc_find_vtys); +#endif /* CONFIG_PPC_EARLY_DEBUG_LPAR_HVSI */ diff --git a/drivers/tty/hvc/hvsi.c b/drivers/tty/hvc/hvsi.c index 8a8d6373f164..c94e2f5853d8 100644 --- a/drivers/tty/hvc/hvsi.c +++ b/drivers/tty/hvc/hvsi.c @@ -49,6 +49,7 @@  #include <asm/uaccess.h>  #include <asm/vio.h>  #include <asm/param.h> +#include <asm/hvsi.h>  #define HVSI_MAJOR	229  #define HVSI_MINOR	128 @@ -109,68 +110,6 @@ enum HVSI_PROTOCOL_STATE {  };  #define HVSI_CONSOLE 0x1 -#define VS_DATA_PACKET_HEADER           0xff -#define VS_CONTROL_PACKET_HEADER        0xfe -#define VS_QUERY_PACKET_HEADER          0xfd -#define VS_QUERY_RESPONSE_PACKET_HEADER 0xfc - -/* control verbs */ -#define VSV_SET_MODEM_CTL    1 /* to service processor only */ -#define VSV_MODEM_CTL_UPDATE 2 /* from service processor only */ -#define VSV_CLOSE_PROTOCOL   3 - -/* query verbs */ -#define VSV_SEND_VERSION_NUMBER 1 -#define VSV_SEND_MODEM_CTL_STATUS 2 - -/* yes, these masks are not consecutive. */ -#define HVSI_TSDTR 0x01 -#define HVSI_TSCD  0x20 - -struct hvsi_header { -	uint8_t  type; -	uint8_t  len; -	uint16_t seqno; -} __attribute__((packed)); - -struct hvsi_data { -	uint8_t  type; -	uint8_t  len; -	uint16_t seqno; -	uint8_t  data[HVSI_MAX_OUTGOING_DATA]; -} __attribute__((packed)); - -struct hvsi_control { -	uint8_t  type; -	uint8_t  len; -	uint16_t seqno; -	uint16_t verb; -	/* optional depending on verb: */ -	uint32_t word; -	uint32_t mask; -} __attribute__((packed)); - -struct hvsi_query { -	uint8_t  type; -	uint8_t  len; -	uint16_t seqno; -	uint16_t verb; -} __attribute__((packed)); - -struct hvsi_query_response { -	uint8_t  type; -	uint8_t  len; -	uint16_t seqno; -	uint16_t verb; -	uint16_t query_seqno; -	union { -		uint8_t  version; -		uint32_t mctrl_word; -	} u; -} __attribute__((packed)); - - -  static inline int is_console(struct hvsi_struct *hp)  {  	return hp->flags & HVSI_CONSOLE; @@ -356,18 +295,18 @@ static int hvsi_version_respond(struct hvsi_struct *hp, uint16_t query_seqno)  	struct hvsi_query_response packet __ALIGNED__;  	int wrote; -	packet.type = VS_QUERY_RESPONSE_PACKET_HEADER; -	packet.len = sizeof(struct hvsi_query_response); -	packet.seqno = atomic_inc_return(&hp->seqno); +	packet.hdr.type = VS_QUERY_RESPONSE_PACKET_HEADER; +	packet.hdr.len = sizeof(struct hvsi_query_response); +	packet.hdr.seqno = atomic_inc_return(&hp->seqno);  	packet.verb = VSV_SEND_VERSION_NUMBER;  	packet.u.version = HVSI_VERSION;  	packet.query_seqno = query_seqno+1; -	pr_debug("%s: sending %i bytes\n", __func__, packet.len); -	dbg_dump_hex((uint8_t*)&packet, packet.len); +	pr_debug("%s: sending %i bytes\n", __func__, packet.hdr.len); +	dbg_dump_hex((uint8_t*)&packet, packet.hdr.len); -	wrote = hvc_put_chars(hp->vtermno, (char *)&packet, packet.len); -	if (wrote != packet.len) { +	wrote = hvc_put_chars(hp->vtermno, (char *)&packet, packet.hdr.len); +	if (wrote != packet.hdr.len) {  		printk(KERN_ERR "hvsi%i: couldn't send query response!\n",  			hp->index);  		return -EIO; @@ -382,7 +321,7 @@ static void hvsi_recv_query(struct hvsi_struct *hp, uint8_t *packet)  	switch (hp->state) {  		case HVSI_WAIT_FOR_VER_QUERY: -			hvsi_version_respond(hp, query->seqno); +			hvsi_version_respond(hp, query->hdr.seqno);  			__set_state(hp, HVSI_OPEN);  			break;  		default: @@ -640,16 +579,16 @@ static int hvsi_query(struct hvsi_struct *hp, uint16_t verb)  	struct hvsi_query packet __ALIGNED__;  	int wrote; -	packet.type = VS_QUERY_PACKET_HEADER; -	packet.len = sizeof(struct hvsi_query); -	packet.seqno = atomic_inc_return(&hp->seqno); +	packet.hdr.type = VS_QUERY_PACKET_HEADER; +	packet.hdr.len = sizeof(struct hvsi_query); +	packet.hdr.seqno = atomic_inc_return(&hp->seqno);  	packet.verb = verb; -	pr_debug("%s: sending %i bytes\n", __func__, packet.len); -	dbg_dump_hex((uint8_t*)&packet, packet.len); +	pr_debug("%s: sending %i bytes\n", __func__, packet.hdr.len); +	dbg_dump_hex((uint8_t*)&packet, packet.hdr.len); -	wrote = hvc_put_chars(hp->vtermno, (char *)&packet, packet.len); -	if (wrote != packet.len) { +	wrote = hvc_put_chars(hp->vtermno, (char *)&packet, packet.hdr.len); +	if (wrote != packet.hdr.len) {  		printk(KERN_ERR "hvsi%i: couldn't send query (%i)!\n", hp->index,  			wrote);  		return -EIO; @@ -683,20 +622,20 @@ static int hvsi_set_mctrl(struct hvsi_struct *hp, uint16_t mctrl)  	struct hvsi_control packet __ALIGNED__;  	int wrote; -	packet.type = VS_CONTROL_PACKET_HEADER, -	packet.seqno = atomic_inc_return(&hp->seqno); -	packet.len = sizeof(struct hvsi_control); +	packet.hdr.type = VS_CONTROL_PACKET_HEADER, +	packet.hdr.seqno = atomic_inc_return(&hp->seqno); +	packet.hdr.len = sizeof(struct hvsi_control);  	packet.verb = VSV_SET_MODEM_CTL;  	packet.mask = HVSI_TSDTR;  	if (mctrl & TIOCM_DTR)  		packet.word = HVSI_TSDTR; -	pr_debug("%s: sending %i bytes\n", __func__, packet.len); -	dbg_dump_hex((uint8_t*)&packet, packet.len); +	pr_debug("%s: sending %i bytes\n", __func__, packet.hdr.len); +	dbg_dump_hex((uint8_t*)&packet, packet.hdr.len); -	wrote = hvc_put_chars(hp->vtermno, (char *)&packet, packet.len); -	if (wrote != packet.len) { +	wrote = hvc_put_chars(hp->vtermno, (char *)&packet, packet.hdr.len); +	if (wrote != packet.hdr.len) {  		printk(KERN_ERR "hvsi%i: couldn't set DTR!\n", hp->index);  		return -EIO;  	} @@ -766,13 +705,13 @@ static int hvsi_put_chars(struct hvsi_struct *hp, const char *buf, int count)  	BUG_ON(count > HVSI_MAX_OUTGOING_DATA); -	packet.type = VS_DATA_PACKET_HEADER; -	packet.seqno = atomic_inc_return(&hp->seqno); -	packet.len = count + sizeof(struct hvsi_header); +	packet.hdr.type = VS_DATA_PACKET_HEADER; +	packet.hdr.seqno = atomic_inc_return(&hp->seqno); +	packet.hdr.len = count + sizeof(struct hvsi_header);  	memcpy(&packet.data, buf, count); -	ret = hvc_put_chars(hp->vtermno, (char *)&packet, packet.len); -	if (ret == packet.len) { +	ret = hvc_put_chars(hp->vtermno, (char *)&packet, packet.hdr.len); +	if (ret == packet.hdr.len) {  		/* return the number of chars written, not the packet length */  		return count;  	} @@ -783,15 +722,15 @@ static void hvsi_close_protocol(struct hvsi_struct *hp)  {  	struct hvsi_control packet __ALIGNED__; -	packet.type = VS_CONTROL_PACKET_HEADER; -	packet.seqno = atomic_inc_return(&hp->seqno); -	packet.len = 6; +	packet.hdr.type = VS_CONTROL_PACKET_HEADER; +	packet.hdr.seqno = atomic_inc_return(&hp->seqno); +	packet.hdr.len = 6;  	packet.verb = VSV_CLOSE_PROTOCOL; -	pr_debug("%s: sending %i bytes\n", __func__, packet.len); -	dbg_dump_hex((uint8_t*)&packet, packet.len); +	pr_debug("%s: sending %i bytes\n", __func__, packet.hdr.len); +	dbg_dump_hex((uint8_t*)&packet, packet.hdr.len); -	hvc_put_chars(hp->vtermno, (char *)&packet, packet.len); +	hvc_put_chars(hp->vtermno, (char *)&packet, packet.hdr.len);  }  static int hvsi_open(struct tty_struct *tty, struct file *filp) diff --git a/drivers/tty/hvc/hvsi_lib.c b/drivers/tty/hvc/hvsi_lib.c new file mode 100644 index 000000000000..bd9b09827b24 --- /dev/null +++ b/drivers/tty/hvc/hvsi_lib.c @@ -0,0 +1,426 @@ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/console.h> +#include <asm/hvsi.h> + +#include "hvc_console.h" + +static int hvsi_send_packet(struct hvsi_priv *pv, struct hvsi_header *packet) +{ +	packet->seqno = atomic_inc_return(&pv->seqno); + +	/* Assumes that always succeeds, works in practice */ +	return pv->put_chars(pv->termno, (char *)packet, packet->len); +} + +static void hvsi_start_handshake(struct hvsi_priv *pv) +{ +	struct hvsi_query q; + +	/* Reset state */ +	pv->established = 0; +	atomic_set(&pv->seqno, 0); + +	pr_devel("HVSI@%x: Handshaking started\n", pv->termno); + +	/* Send version query */ +	q.hdr.type = VS_QUERY_PACKET_HEADER; +	q.hdr.len = sizeof(struct hvsi_query); +	q.verb = VSV_SEND_VERSION_NUMBER; +	hvsi_send_packet(pv, &q.hdr); +} + +static int hvsi_send_close(struct hvsi_priv *pv) +{ +	struct hvsi_control ctrl; + +	pv->established = 0; + +	ctrl.hdr.type = VS_CONTROL_PACKET_HEADER; +	ctrl.hdr.len = sizeof(struct hvsi_control); +	ctrl.verb = VSV_CLOSE_PROTOCOL; +	return hvsi_send_packet(pv, &ctrl.hdr); +} + +static void hvsi_cd_change(struct hvsi_priv *pv, int cd) +{ +	if (cd) +		pv->mctrl |= TIOCM_CD; +	else { +		pv->mctrl &= ~TIOCM_CD; + +		/* We copy the existing hvsi driver semantics +		 * here which are to trigger a hangup when +		 * we get a carrier loss. +		 * Closing our connection to the server will +		 * do just that. +		 */ +		if (!pv->is_console && pv->opened) { +			pr_devel("HVSI@%x Carrier lost, hanging up !\n", +				 pv->termno); +			hvsi_send_close(pv); +		} +	} +} + +static void hvsi_got_control(struct hvsi_priv *pv) +{ +	struct hvsi_control *pkt = (struct hvsi_control *)pv->inbuf; + +	switch (pkt->verb) { +	case VSV_CLOSE_PROTOCOL: +		/* We restart the handshaking */ +		hvsi_start_handshake(pv); +		break; +	case VSV_MODEM_CTL_UPDATE: +		/* Transition of carrier detect */ +		hvsi_cd_change(pv, pkt->word & HVSI_TSCD); +		break; +	} +} + +static void hvsi_got_query(struct hvsi_priv *pv) +{ +	struct hvsi_query *pkt = (struct hvsi_query *)pv->inbuf; +	struct hvsi_query_response r; + +	/* We only handle version queries */ +	if (pkt->verb != VSV_SEND_VERSION_NUMBER) +		return; + +	pr_devel("HVSI@%x: Got version query, sending response...\n", +		 pv->termno); + +	/* Send version response */ +	r.hdr.type = VS_QUERY_RESPONSE_PACKET_HEADER; +	r.hdr.len = sizeof(struct hvsi_query_response); +	r.verb = VSV_SEND_VERSION_NUMBER; +	r.u.version = HVSI_VERSION; +	r.query_seqno = pkt->hdr.seqno; +	hvsi_send_packet(pv, &r.hdr); + +	/* Assume protocol is open now */ +	pv->established = 1; +} + +static void hvsi_got_response(struct hvsi_priv *pv) +{ +	struct hvsi_query_response *r = +		(struct hvsi_query_response *)pv->inbuf; + +	switch(r->verb) { +	case VSV_SEND_MODEM_CTL_STATUS: +		hvsi_cd_change(pv, r->u.mctrl_word & HVSI_TSCD); +		pv->mctrl_update = 1; +		break; +	} +} + +static int hvsi_check_packet(struct hvsi_priv *pv) +{ +	u8 len, type; + +	/* Check header validity. If it's invalid, we ditch +	 * the whole buffer and hope we eventually resync +	 */ +	if (pv->inbuf[0] < 0xfc) { +		pv->inbuf_len = pv->inbuf_pktlen = 0; +		return 0; +	} +	type = pv->inbuf[0]; +	len = pv->inbuf[1]; + +	/* Packet incomplete ? */ +	if (pv->inbuf_len < len) +		return 0; + +	pr_devel("HVSI@%x: Got packet type %x len %d bytes:\n", +		 pv->termno, type, len); + +	/* We have a packet, yay ! Handle it */ +	switch(type) { +	case VS_DATA_PACKET_HEADER: +		pv->inbuf_pktlen = len - 4; +		pv->inbuf_cur = 4; +		return 1; +	case VS_CONTROL_PACKET_HEADER: +		hvsi_got_control(pv); +		break; +	case VS_QUERY_PACKET_HEADER: +		hvsi_got_query(pv); +		break; +	case VS_QUERY_RESPONSE_PACKET_HEADER: +		hvsi_got_response(pv); +		break; +	} + +	/* Swallow packet and retry */ +	pv->inbuf_len -= len; +	memmove(pv->inbuf, &pv->inbuf[len], pv->inbuf_len); +	return 1; +} + +static int hvsi_get_packet(struct hvsi_priv *pv) +{ +	/* If we have room in the buffer, ask HV for more */ +	if (pv->inbuf_len < HVSI_INBUF_SIZE) +		pv->inbuf_len += pv->get_chars(pv->termno, +					     &pv->inbuf[pv->inbuf_len], +					     HVSI_INBUF_SIZE - pv->inbuf_len); +	/* +	 * If we have at least 4 bytes in the buffer, check for +	 * a full packet and retry +	 */ +	if (pv->inbuf_len >= 4) +		return hvsi_check_packet(pv); +	return 0; +} + +int hvsilib_get_chars(struct hvsi_priv *pv, char *buf, int count) +{ +	unsigned int tries, read = 0; + +	if (WARN_ON(!pv)) +		return 0; + +	/* If we aren't open, don't do anything in order to avoid races +	 * with connection establishment. The hvc core will call this +	 * before we have returned from notifier_add(), and we need to +	 * avoid multiple users playing with the receive buffer +	 */ +	if (!pv->opened) +		return 0; + +	/* We try twice, once with what data we have and once more +	 * after we try to fetch some more from the hypervisor +	 */ +	for (tries = 1; count && tries < 2; tries++) { +		/* Consume existing data packet */ +		if (pv->inbuf_pktlen) { +			unsigned int l = min(count, (int)pv->inbuf_pktlen); +			memcpy(&buf[read], &pv->inbuf[pv->inbuf_cur], l); +			pv->inbuf_cur += l; +			pv->inbuf_pktlen -= l; +			count -= l; +			read += l; +		} +		if (count == 0) +			break; + +		/* Data packet fully consumed, move down remaning data */ +		if (pv->inbuf_cur) { +			pv->inbuf_len -= pv->inbuf_cur; +			memmove(pv->inbuf, &pv->inbuf[pv->inbuf_cur], +				pv->inbuf_len); +			pv->inbuf_cur = 0; +		} + +		/* Try to get another packet */ +		if (hvsi_get_packet(pv)) +			tries--; +	} +	if (!pv->established) { +		pr_devel("HVSI@%x: returning -EPIPE\n", pv->termno); +		return -EPIPE; +	} +	return read; +} + +int hvsilib_put_chars(struct hvsi_priv *pv, const char *buf, int count) +{ +	struct hvsi_data dp; +	int rc, adjcount = min(count, HVSI_MAX_OUTGOING_DATA); + +	if (WARN_ON(!pv)) +		return 0; + +	dp.hdr.type = VS_DATA_PACKET_HEADER; +	dp.hdr.len = adjcount + sizeof(struct hvsi_header); +	memcpy(dp.data, buf, adjcount); +	rc = hvsi_send_packet(pv, &dp.hdr); +	if (rc <= 0) +		return rc; +	return adjcount; +} + +static void maybe_msleep(unsigned long ms) +{ +	/* During early boot, IRQs are disabled, use mdelay */ +	if (irqs_disabled()) +		mdelay(ms); +	else +		msleep(ms); +} + +int hvsilib_read_mctrl(struct hvsi_priv *pv) +{ +	struct hvsi_query q; +	int rc, timeout; + +	pr_devel("HVSI@%x: Querying modem control status...\n", +		 pv->termno); + +	pv->mctrl_update = 0; +	q.hdr.type = VS_QUERY_PACKET_HEADER; +	q.hdr.len = sizeof(struct hvsi_query); +	q.hdr.seqno = atomic_inc_return(&pv->seqno); +	q.verb = VSV_SEND_MODEM_CTL_STATUS; +	rc = hvsi_send_packet(pv, &q.hdr); +	if (rc <= 0) { +		pr_devel("HVSI@%x: Error %d...\n", pv->termno, rc); +		return rc; +	} + +	/* Try for up to 200ms */ +	for (timeout = 0; timeout < 20; timeout++) { +		if (!pv->established) +			return -ENXIO; +		if (pv->mctrl_update) +			return 0; +		if (!hvsi_get_packet(pv)) +			maybe_msleep(10); +	} +	return -EIO; +} + +int hvsilib_write_mctrl(struct hvsi_priv *pv, int dtr) +{ +	struct hvsi_control ctrl; +	unsigned short mctrl; + +	mctrl = pv->mctrl; +	if (dtr) +		mctrl |= TIOCM_DTR; +	else +		mctrl &= ~TIOCM_DTR; +	if (mctrl == pv->mctrl) +		return 0; +	pv->mctrl = mctrl; + +	pr_devel("HVSI@%x: %s DTR...\n", pv->termno, +		 dtr ? "Setting" : "Clearing"); + +	ctrl.hdr.type = VS_CONTROL_PACKET_HEADER, +	ctrl.hdr.len = sizeof(struct hvsi_control); +	ctrl.verb = VSV_SET_MODEM_CTL; +	ctrl.mask = HVSI_TSDTR; +	ctrl.word = dtr ? HVSI_TSDTR : 0; +	return hvsi_send_packet(pv, &ctrl.hdr); +} + +void hvsilib_establish(struct hvsi_priv *pv) +{ +	int timeout; + +	pr_devel("HVSI@%x: Establishing...\n", pv->termno); + +	/* Try for up to 200ms, there can be a packet to +	 * start the process waiting for us... +	 */ +	for (timeout = 0; timeout < 20; timeout++) { +		if (pv->established) +			goto established; +		if (!hvsi_get_packet(pv)) +			maybe_msleep(10); +	} + +	/* Failed, send a close connection packet just +	 * in case +	 */ +	pr_devel("HVSI@%x:   ... sending close\n", pv->termno); + +	hvsi_send_close(pv); + +	/* Then restart handshake */ + +	pr_devel("HVSI@%x:   ... restarting handshake\n", pv->termno); + +	hvsi_start_handshake(pv); + +	pr_devel("HVSI@%x:   ... waiting handshake\n", pv->termno); + +	/* Try for up to 200s */ +	for (timeout = 0; timeout < 20; timeout++) { +		if (pv->established) +			goto established; +		if (!hvsi_get_packet(pv)) +			maybe_msleep(10); +	} + +	if (!pv->established) { +		pr_devel("HVSI@%x: Timeout handshaking, giving up !\n", +			 pv->termno); +		return; +	} + established: +	/* Query modem control lines */ + +	pr_devel("HVSI@%x:   ... established, reading mctrl\n", pv->termno); + +	hvsilib_read_mctrl(pv); + +	/* Set our own DTR */ + +	pr_devel("HVSI@%x:   ... setting mctrl\n", pv->termno); + +	hvsilib_write_mctrl(pv, 1); + +	/* Set the opened flag so reads are allowed */ +	wmb(); +	pv->opened = 1; +} + +int hvsilib_open(struct hvsi_priv *pv, struct hvc_struct *hp) +{ +	pr_devel("HVSI@%x: open !\n", pv->termno); + +	/* Keep track of the tty data structure */ +	pv->tty = tty_kref_get(hp->tty); + +	hvsilib_establish(pv); + +	return 0; +} + +void hvsilib_close(struct hvsi_priv *pv, struct hvc_struct *hp) +{ +	unsigned long flags; + +	pr_devel("HVSI@%x: close !\n", pv->termno); + +	if (!pv->is_console) { +		pr_devel("HVSI@%x: Not a console, tearing down\n", +			 pv->termno); + +		/* Clear opened, synchronize with khvcd */ +		spin_lock_irqsave(&hp->lock, flags); +		pv->opened = 0; +		spin_unlock_irqrestore(&hp->lock, flags); + +		/* Clear our own DTR */ +		if (!pv->tty || (pv->tty->termios->c_cflag & HUPCL)) +			hvsilib_write_mctrl(pv, 0); + +		/* Tear down the connection */ +		hvsi_send_close(pv); +	} + +	if (pv->tty) +		tty_kref_put(pv->tty); +	pv->tty = NULL; +} + +void hvsilib_init(struct hvsi_priv *pv, +		  int (*get_chars)(uint32_t termno, char *buf, int count), +		  int (*put_chars)(uint32_t termno, const char *buf, +				   int count), +		  int termno, int is_console) +{ +	memset(pv, 0, sizeof(*pv)); +	pv->get_chars = get_chars; +	pv->put_chars = put_chars; +	pv->termno = termno; +	pv->is_console = is_console; +} | 
