diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2011-07-26 09:59:39 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-07-26 09:59:39 +0400 |
commit | 184475029a724b6b900d88fc3a5f462a6107d5af (patch) | |
tree | 408320b46df221a2424bf94282b1b8e5b7aff7a1 /drivers/tty | |
parent | 3b76eefe0f970c2e19f165d4a1650abc523d10bc (diff) | |
parent | f1f4ee01c0d3dce0e3aa7d04e4332677db7af478 (diff) | |
download | linux-184475029a724b6b900d88fc3a5f462a6107d5af.tar.xz |
Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc
* 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc: (99 commits)
drivers/virt: add missing linux/interrupt.h to fsl_hypervisor.c
powerpc/85xx: fix mpic configuration in CAMP mode
powerpc: Copy back TIF flags on return from softirq stack
powerpc/64: Make server perfmon only built on ppc64 server devices
powerpc/pseries: Fix hvc_vio.c build due to recent changes
powerpc: Exporting boot_cpuid_phys
powerpc: Add CFAR to oops output
hvc_console: Add kdb support
powerpc/pseries: Fix hvterm_raw_get_chars to accept < 16 chars, fixing xmon
powerpc/irq: Quieten irq mapping printks
powerpc: Enable lockup and hung task detectors in pseries and ppc64 defeconfigs
powerpc: Add mpt2sas driver to pseries and ppc64 defconfig
powerpc: Disable IRQs off tracer in ppc64 defconfig
powerpc: Sync pseries and ppc64 defconfigs
powerpc/pseries/hvconsole: Fix dropped console output
hvc_console: Improve tty/console put_chars handling
powerpc/kdump: Fix timeout in crash_kexec_wait_realmode
powerpc/mm: Fix output of total_ram.
powerpc/cpufreq: Add cpufreq driver for Momentum Maple boards
powerpc: Correct annotations of pmu registration functions
...
Fix up trivial Kconfig/Makefile conflicts in arch/powerpc, drivers, and
drivers/cpufreq
Diffstat (limited to 'drivers/tty')
-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; +} |