From 725e789f228641fdfafcd65458f0ac78b87acc5a Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Thu, 16 Jun 2011 15:08:12 +0000 Subject: powerpc/hvsi: Move HVSI protocol definitions to a header file This moves various HVSI protocol definitions from the hvsi.c driver to a header file that can be used later on by a udbg implementation Signed-off-by: Benjamin Herrenschmidt --- drivers/tty/hvc/hvsi.c | 63 +------------------------------------------------- 1 file changed, 1 insertion(+), 62 deletions(-) (limited to 'drivers/tty') diff --git a/drivers/tty/hvc/hvsi.c b/drivers/tty/hvc/hvsi.c index 8a8d6373f164..0b35793de1fa 100644 --- a/drivers/tty/hvc/hvsi.c +++ b/drivers/tty/hvc/hvsi.c @@ -49,6 +49,7 @@ #include #include #include +#include #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; -- cgit v1.2.3 From 048bee7718bb3532aa96d0ce8572cced2ea951e6 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Thu, 16 Jun 2011 15:08:24 +0000 Subject: powerpc/pseries: Factor HVSI header struct in packet definitions Embed the struct hvsi_header in the various packet definitions rather than open coding it multiple times. Will help provide stronger type checking. Signed-off-by: Benjamin Herrenschmidt --- arch/powerpc/include/asm/hvsi.h | 16 +++------- drivers/tty/hvc/hvsi.c | 66 ++++++++++++++++++++--------------------- 2 files changed, 37 insertions(+), 45 deletions(-) (limited to 'drivers/tty') diff --git a/arch/powerpc/include/asm/hvsi.h b/arch/powerpc/include/asm/hvsi.h index f13125a0cb3a..ab2ddd76c2e0 100644 --- a/arch/powerpc/include/asm/hvsi.h +++ b/arch/powerpc/include/asm/hvsi.h @@ -29,16 +29,12 @@ struct hvsi_header { } __attribute__((packed)); struct hvsi_data { - uint8_t type; - uint8_t len; - uint16_t seqno; + struct hvsi_header hdr; uint8_t data[HVSI_MAX_OUTGOING_DATA]; } __attribute__((packed)); struct hvsi_control { - uint8_t type; - uint8_t len; - uint16_t seqno; + struct hvsi_header hdr; uint16_t verb; /* optional depending on verb: */ uint32_t word; @@ -46,16 +42,12 @@ struct hvsi_control { } __attribute__((packed)); struct hvsi_query { - uint8_t type; - uint8_t len; - uint16_t seqno; + struct hvsi_header hdr; uint16_t verb; } __attribute__((packed)); struct hvsi_query_response { - uint8_t type; - uint8_t len; - uint16_t seqno; + struct hvsi_header hdr; uint16_t verb; uint16_t query_seqno; union { diff --git a/drivers/tty/hvc/hvsi.c b/drivers/tty/hvc/hvsi.c index 0b35793de1fa..c94e2f5853d8 100644 --- a/drivers/tty/hvc/hvsi.c +++ b/drivers/tty/hvc/hvsi.c @@ -295,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; @@ -321,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: @@ -579,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; @@ -622,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; } @@ -705,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; } @@ -722,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) -- cgit v1.2.3 From 4d2bb3f5003617cb42b89faefd0009c505c3abd5 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Thu, 12 May 2011 13:46:38 +1000 Subject: powerpc/pseries: Re-implement HVSI as part of hvc_vio On pseries machines, consoles are provided by the hypervisor using a low level get_chars/put_chars type interface. However, this is really just a transport to the service processor which implements them either as "raw" console (networked consoles, HMC, ...) or as "hvsi" serial ports. The later is a simple packet protocol on top of the raw character interface that is supposed to convey additional "serial port" style semantics. In practice however, all it does is provide a way to read the CD line and set/clear our DTR line, that's it. We currently implement the "raw" protocol as an hvc console backend (/dev/hvcN) and the "hvsi" protocol using a separate tty driver (/dev/hvsi0). However this is quite impractical. The arbitrary difference between the two type of devices has been a major source of user (and distro) confusion. Additionally, there's an additional mini -hvsi implementation in the pseries platform code for our low level debug console and early boot kernel messages, which means code duplication, though that low level variant is impractical as it's incapable of doing the initial protocol negociation to establish the link to the FSP. This essentially replaces the dedicated hvsi driver and the platform udbg code completely by extending the existing hvc_vio backend used in "raw" mode so that: - It now supports HVSI as well - We add support for hvc backend providing tiocm{get,set} - It also provides a udbg interface for early debug and boot console This is overall less code, though this will only be obvious once we remove the old "hvsi" driver, which is still available for now. When the old driver is enabled, the new code still kicks in for the low level udbg console, replacing the old mini implementation in the platform code, it just doesn't provide the higher level "hvc" interface. In addition to producing generally simler code, this has several benefits over our current situation: - The user/distro only has to deal with /dev/hvcN for the hypervisor console, avoiding all sort of confusion that has plagued us in the past - The tty, kernel and low level debug console all use the same code base which supports the full protocol establishment process, thus the console is now available much earlier than it used to be with the old HVSI driver. The kernel console works much earlier and udbg is available much earlier too. Hackers can enable a hard coded very-early debug console as well that works with HVSI (previously that was only supported for the "raw" mode). I've tried to keep the same semantics as hvsi relative to how I react to things like CD changes, with some subtle differences though: - I clear DTR on close if HUPCL is set - Current hvsi triggers a hangup if it detects a up->down transition on CD (you can still open a console with CD down). My new implementation triggers a hangup if the link to the FSP is severed, and severs it upon detecting a up->down transition on CD. Signed-off-by: Benjamin Herrenschmidt --- arch/powerpc/Kconfig.debug | 15 + arch/powerpc/include/asm/udbg.h | 1 + arch/powerpc/kernel/udbg.c | 3 + arch/powerpc/platforms/pseries/lpar.c | 189 -------- arch/powerpc/platforms/pseries/pseries.h | 3 +- arch/powerpc/platforms/pseries/setup.c | 5 +- drivers/tty/hvc/Kconfig | 5 + drivers/tty/hvc/Makefile | 3 +- drivers/tty/hvc/hvc_console.c | 23 +- drivers/tty/hvc/hvc_console.h | 4 + drivers/tty/hvc/hvc_vio.c | 725 +++++++++++++++++++++++++++++-- 11 files changed, 749 insertions(+), 227 deletions(-) (limited to 'drivers/tty') diff --git a/arch/powerpc/Kconfig.debug b/arch/powerpc/Kconfig.debug index e72dcf6a421d..067cb8480747 100644 --- a/arch/powerpc/Kconfig.debug +++ b/arch/powerpc/Kconfig.debug @@ -167,6 +167,13 @@ config PPC_EARLY_DEBUG_LPAR Select this to enable early debugging for a machine with a HVC console on vterm 0. +config PPC_EARLY_DEBUG_LPAR_HVSI + bool "LPAR HVSI Console" + depends on PPC_PSERIES + help + Select this to enable early debugging for a machine with a HVSI + console on a specified vterm. + config PPC_EARLY_DEBUG_G5 bool "Apple G5" depends on PPC_PMAC64 @@ -253,6 +260,14 @@ config PPC_EARLY_DEBUG_WSP endchoice +config PPC_EARLY_DEBUG_HVSI_VTERMNO + hex "vterm number to use with early debug HVSI" + depends on PPC_EARLY_DEBUG_LPAR_HVSI + default "0x30000000" + help + You probably want 0x30000000 for your first serial port and + 0x30000001 for your second one + config PPC_EARLY_DEBUG_44x_PHYSLOW hex "Low 32 bits of early debug UART physical address" depends on PPC_EARLY_DEBUG_44x diff --git a/arch/powerpc/include/asm/udbg.h b/arch/powerpc/include/asm/udbg.h index 58580e94a2bb..93e05d1b34b2 100644 --- a/arch/powerpc/include/asm/udbg.h +++ b/arch/powerpc/include/asm/udbg.h @@ -40,6 +40,7 @@ extern void udbg_adb_init_early(void); extern void __init udbg_early_init(void); extern void __init udbg_init_debug_lpar(void); +extern void __init udbg_init_debug_lpar_hvsi(void); extern void __init udbg_init_pmac_realmode(void); extern void __init udbg_init_maple_realmode(void); extern void __init udbg_init_pas_realmode(void); diff --git a/arch/powerpc/kernel/udbg.c b/arch/powerpc/kernel/udbg.c index a57e61ea0558..faa82c1f3f68 100644 --- a/arch/powerpc/kernel/udbg.c +++ b/arch/powerpc/kernel/udbg.c @@ -31,6 +31,9 @@ void __init udbg_early_init(void) #if defined(CONFIG_PPC_EARLY_DEBUG_LPAR) /* For LPAR machines that have an HVC console on vterm 0 */ udbg_init_debug_lpar(); +#elif defined(CONFIG_PPC_EARLY_DEBUG_LPAR_HVSI) + /* For LPAR machines that have an HVSI console on vterm 0 */ + udbg_init_debug_lpar_hvsi(); #elif defined(CONFIG_PPC_EARLY_DEBUG_G5) /* For use on Apple G5 machines */ udbg_init_pmac_realmode(); diff --git a/arch/powerpc/platforms/pseries/lpar.c b/arch/powerpc/platforms/pseries/lpar.c index e3a96c4348ab..f7205d344efd 100644 --- a/arch/powerpc/platforms/pseries/lpar.c +++ b/arch/powerpc/platforms/pseries/lpar.c @@ -52,195 +52,6 @@ EXPORT_SYMBOL(plpar_hcall_norets); extern void pSeries_find_serial_port(void); - -static int vtermno; /* virtual terminal# for udbg */ - -#define __ALIGNED__ __attribute__((__aligned__(sizeof(long)))) -static void udbg_hvsi_putc(char c) -{ - /* packet's seqno isn't used anyways */ - uint8_t packet[] __ALIGNED__ = { 0xff, 5, 0, 0, c }; - int rc; - - if (c == '\n') - udbg_hvsi_putc('\r'); - - do { - rc = plpar_put_term_char(vtermno, sizeof(packet), packet); - } while (rc == H_BUSY); -} - -static long hvsi_udbg_buf_len; -static uint8_t hvsi_udbg_buf[256]; - -static int udbg_hvsi_getc_poll(void) -{ - unsigned char ch; - int rc, i; - - if (hvsi_udbg_buf_len == 0) { - rc = plpar_get_term_char(vtermno, &hvsi_udbg_buf_len, hvsi_udbg_buf); - if (rc != H_SUCCESS || hvsi_udbg_buf[0] != 0xff) { - /* bad read or non-data packet */ - hvsi_udbg_buf_len = 0; - } else { - /* remove the packet header */ - for (i = 4; i < hvsi_udbg_buf_len; i++) - hvsi_udbg_buf[i-4] = hvsi_udbg_buf[i]; - hvsi_udbg_buf_len -= 4; - } - } - - if (hvsi_udbg_buf_len <= 0 || hvsi_udbg_buf_len > 256) { - /* no data ready */ - hvsi_udbg_buf_len = 0; - return -1; - } - - ch = hvsi_udbg_buf[0]; - /* shift remaining data down */ - for (i = 1; i < hvsi_udbg_buf_len; i++) { - hvsi_udbg_buf[i-1] = hvsi_udbg_buf[i]; - } - hvsi_udbg_buf_len--; - - return ch; -} - -static int udbg_hvsi_getc(void) -{ - int ch; - for (;;) { - ch = udbg_hvsi_getc_poll(); - if (ch == -1) { - /* This shouldn't be needed...but... */ - volatile unsigned long delay; - for (delay=0; delay < 2000000; delay++) - ; - } else { - return ch; - } - } -} - -static void udbg_putcLP(char c) -{ - char buf[16]; - unsigned long rc; - - if (c == '\n') - udbg_putcLP('\r'); - - buf[0] = c; - do { - rc = plpar_put_term_char(vtermno, 1, buf); - } while(rc == H_BUSY); -} - -/* Buffered chars getc */ -static long inbuflen; -static long inbuf[2]; /* must be 2 longs */ - -static int udbg_getc_pollLP(void) -{ - /* The interface is tricky because it may return up to 16 chars. - * We save them statically for future calls to udbg_getc(). - */ - char ch, *buf = (char *)inbuf; - int i; - long rc; - if (inbuflen == 0) { - /* get some more chars. */ - inbuflen = 0; - rc = plpar_get_term_char(vtermno, &inbuflen, buf); - if (rc != H_SUCCESS) - inbuflen = 0; /* otherwise inbuflen is garbage */ - } - if (inbuflen <= 0 || inbuflen > 16) { - /* Catch error case as well as other oddities (corruption) */ - inbuflen = 0; - return -1; - } - ch = buf[0]; - for (i = 1; i < inbuflen; i++) /* shuffle them down. */ - buf[i-1] = buf[i]; - inbuflen--; - return ch; -} - -static int udbg_getcLP(void) -{ - int ch; - for (;;) { - ch = udbg_getc_pollLP(); - if (ch == -1) { - /* This shouldn't be needed...but... */ - volatile unsigned long delay; - for (delay=0; delay < 2000000; delay++) - ; - } else { - return ch; - } - } -} - -/* call this from early_init() for a working debug console on - * vterm capable LPAR machines - */ -void __init udbg_init_debug_lpar(void) -{ - vtermno = 0; - udbg_putc = udbg_putcLP; - udbg_getc = udbg_getcLP; - udbg_getc_poll = udbg_getc_pollLP; -} - -/* returns 0 if couldn't find or use /chosen/stdout as console */ -void __init find_udbg_vterm(void) -{ - struct device_node *stdout_node; - const u32 *termno; - const char *name; - - /* 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; - vtermno = termno[0]; - - if (of_device_is_compatible(stdout_node, "hvterm1")) { - udbg_putc = udbg_putcLP; - udbg_getc = udbg_getcLP; - udbg_getc_poll = udbg_getc_pollLP; - add_preferred_console("hvc", termno[0] & 0xff, NULL); - } else if (of_device_is_compatible(stdout_node, "hvterm-protocol")) { - vtermno = termno[0]; - udbg_putc = udbg_hvsi_putc; - udbg_getc = udbg_hvsi_getc; - udbg_getc_poll = udbg_hvsi_getc_poll; - add_preferred_console("hvsi", termno[0] & 0xff, NULL); - } -out: - of_node_put(stdout_node); -} - void vpa_init(int cpu) { int hwcpu = get_hard_smp_processor_id(cpu); diff --git a/arch/powerpc/platforms/pseries/pseries.h b/arch/powerpc/platforms/pseries/pseries.h index e9f6d2859c3c..24c7162f11d9 100644 --- a/arch/powerpc/platforms/pseries/pseries.h +++ b/arch/powerpc/platforms/pseries/pseries.h @@ -47,7 +47,8 @@ extern void pSeries_final_fixup(void); /* Poweron flag used for enabling auto ups restart */ extern unsigned long rtas_poweron_auto; -extern void find_udbg_vterm(void); +/* Provided by HVC VIO */ +extern void hvc_vio_init_early(void); /* Dynamic logical Partitioning/Mobility */ extern void dlpar_free_cc_nodes(struct device_node *); diff --git a/arch/powerpc/platforms/pseries/setup.c b/arch/powerpc/platforms/pseries/setup.c index 593acceeff96..d00e52926b71 100644 --- a/arch/powerpc/platforms/pseries/setup.c +++ b/arch/powerpc/platforms/pseries/setup.c @@ -512,9 +512,10 @@ static void __init pSeries_init_early(void) { pr_debug(" -> pSeries_init_early()\n"); +#ifdef CONFIG_HVC_CONSOLE if (firmware_has_feature(FW_FEATURE_LPAR)) - find_udbg_vterm(); - + hvc_vio_init_early(); +#endif if (firmware_has_feature(FW_FEATURE_DABR)) ppc_md.set_dabr = pseries_set_dabr; else if (firmware_has_feature(FW_FEATURE_XDABR)) 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..69a444b71c63 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 +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..f8ff6f50fc35 100644 --- a/drivers/tty/hvc/hvc_console.c +++ b/drivers/tty/hvc/hvc_console.c @@ -184,7 +184,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; @@ -745,6 +745,25 @@ 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); +} + static const struct tty_operations hvc_ops = { .open = hvc_open, .close = hvc_close, @@ -753,6 +772,8 @@ 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, }; 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..d4e0850e8051 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 #include +#include +#include +#include #include #include #include #include +#include +#include #include "hvc_console.h" @@ -43,14 +55,47 @@ 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; + +#define HV_INBUF_SIZE 255 + +struct hvterm_priv { + u32 termno; /* HV term number */ + hv_protocol_t proto; /* Raw data or HVSI packets */ + unsigned int inbuf_len; /* Data in input buffer */ + unsigned char inbuf[HV_INBUF_SIZE]; + unsigned int inbuf_cur; /* Cursor in input buffer */ + unsigned int inbuf_pktlen; /* HVSI packet lenght from cursor */ + atomic_t seqno; /* HVSI packet sequence number */ + unsigned int opened:1; /* HVSI driver opened */ + unsigned int established:1; /* HVSI protocol established */ + unsigned int is_console:1; /* Used as a kernel console device */ + unsigned int mctrl_update:1; /* HVSI modem control updated */ + unsigned short mctrl; /* HVSI modem control */ + struct tty_struct *tty; /* TTY structure */ +}; +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 got, i; + + if (WARN_ON(!pv)) + return 0; /* * Vio firmware will read up to SIZE_VIO_GET_CHARS at its own discretion @@ -60,7 +105,7 @@ static int filtered_get_chars(uint32_t vtermno, char *buf, int count) if (count < SIZE_VIO_GET_CHARS) return -EAGAIN; - got = hvc_get_chars(vtermno, buf, count); + got = hvc_get_chars(pv->termno, buf, count); /* * Work around a HV bug where it gives us a null @@ -70,32 +115,527 @@ static int filtered_get_chars(uint32_t vtermno, char *buf, int count) if (buf[i] == 0 && buf[i-1] == '\r') { --got; if (i < got) - memmove(&buf[i], &buf[i+1], - got - i); + memmove(&buf[i], &buf[i+1], got - i); } } 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_send_packet(struct hvterm_priv *pv, struct hvsi_header *packet) +{ + packet->seqno = atomic_inc_return(&pv->seqno); + + /* Assumes that always succeeds, works in practice */ + return hvc_put_chars(pv->termno, (char *)packet, packet->len); +} + +static void hvterm_hvsi_start_handshake(struct hvterm_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; + hvterm_hvsi_send_packet(pv, &q.hdr); +} + +static int hvterm_hvsi_send_close(struct hvterm_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 hvterm_hvsi_send_packet(pv, &ctrl.hdr); +} + +static void hvterm_cd_change(struct hvterm_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); + hvterm_hvsi_send_close(pv); + } + } +} + +static void hvterm_hvsi_got_control(struct hvterm_priv *pv) +{ + struct hvsi_control *pkt = (struct hvsi_control *)pv->inbuf; + + switch (pkt->verb) { + case VSV_CLOSE_PROTOCOL: + /* We restart the handshaking */ + hvterm_hvsi_start_handshake(pv); + break; + case VSV_MODEM_CTL_UPDATE: + /* Transition of carrier detect */ + hvterm_cd_change(pv, pkt->word & HVSI_TSCD); + break; + } +} + +static void hvterm_hvsi_got_query(struct hvterm_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; + hvterm_hvsi_send_packet(pv, &r.hdr); + + /* Assume protocol is open now */ + pv->established = 1; +} + +static void hvterm_hvsi_got_response(struct hvterm_priv *pv) +{ + struct hvsi_query_response *r = (struct hvsi_query_response *)pv->inbuf; + + switch(r->verb) { + case VSV_SEND_MODEM_CTL_STATUS: + hvterm_cd_change(pv, r->u.mctrl_word & HVSI_TSCD); + pv->mctrl_update = 1; + break; + } +} + +static int hvterm_hvsi_check_packet(struct hvterm_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: + hvterm_hvsi_got_control(pv); + break; + case VS_QUERY_PACKET_HEADER: + hvterm_hvsi_got_query(pv); + break; + case VS_QUERY_RESPONSE_PACKET_HEADER: + hvterm_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 hvterm_hvsi_get_packet(struct hvterm_priv *pv) +{ + /* If we have room in the buffer, ask HV for more */ + if (pv->inbuf_len < HV_INBUF_SIZE) + pv->inbuf_len += hvc_get_chars(pv->termno, + &pv->inbuf[pv->inbuf_len], + HV_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 hvterm_hvsi_check_packet(pv); + return 0; +} + +static int hvterm_hvsi_get_chars(uint32_t vtermno, char *buf, int count) +{ + struct hvterm_priv *pv = hvterm_privs[vtermno]; + unsigned int tries, read = 0; + + if (WARN_ON(!pv)) + return 0; + + /* If we aren't open, dont 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 (hvterm_hvsi_get_packet(pv)) + tries--; + } + if (!pv->established) { + pr_devel("HVSI@%x: returning -EPIPE\n", pv->termno); + return -EPIPE; + } + return read; +} + +static int hvterm_hvsi_put_chars(uint32_t vtermno, const char *buf, int count) +{ + struct hvterm_priv *pv = hvterm_privs[vtermno]; + 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 = hvterm_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); +} + +static int hvterm_hvsi_read_mctrl(struct hvterm_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 = hvterm_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 1s */ + for (timeout = 0; timeout < 1000; timeout++) { + if (!pv->established) + return -ENXIO; + if (pv->mctrl_update) + return 0; + if (!hvterm_hvsi_get_packet(pv)) + maybe_msleep(1); + } + return -EIO; +} + +static int hvterm_hvsi_write_mctrl(struct hvterm_priv *pv, int dtr) +{ + struct hvsi_control ctrl; + + 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; + if (dtr) + pv->mctrl |= TIOCM_DTR; + else + pv->mctrl &= ~TIOCM_DTR; + return hvterm_hvsi_send_packet(pv, &ctrl.hdr); +} + +static void hvterm_hvsi_establish(struct hvterm_priv *pv) +{ + int timeout; + + /* Try for up to 10ms, there can be a packet to + * start the process waiting for us... + */ + for (timeout = 0; timeout < 10; timeout++) { + if (pv->established) + goto established; + if (!hvterm_hvsi_get_packet(pv)) + maybe_msleep(1); + } + + /* Failed, send a close connection packet just + * in case + */ + hvterm_hvsi_send_close(pv); + + /* Then restart handshake */ + hvterm_hvsi_start_handshake(pv); + + /* Try for up to 100ms */ + for (timeout = 0; timeout < 100; timeout++) { + if (pv->established) + goto established; + if (!hvterm_hvsi_get_packet(pv)) + maybe_msleep(1); + } + + if (!pv->established) { + pr_devel("HVSI@%x: Timeout handshaking, giving up !\n", + pv->termno); + return; + } + established: + /* Query modem control lines */ + hvterm_hvsi_read_mctrl(pv); + + /* Set our own DTR */ + hvterm_hvsi_write_mctrl(pv, 1); + + /* Set the opened flag so reads are allowed */ + wmb(); + pv->opened = 1; +} + +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; + + /* Keep track of the tty data structure */ + pv->tty = tty_kref_get(hp->tty); + + hvterm_hvsi_establish(pv); + return 0; +} + +static void hvterm_hvsi_shutdown(struct hvc_struct *hp, struct hvterm_priv *pv) +{ + unsigned long flags; + + 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)) + hvterm_hvsi_write_mctrl(pv, 0); + + /* Tear down the connection */ + hvterm_hvsi_send_close(pv); + } + + if (pv->tty) + tty_kref_put(pv->tty); + pv->tty = NULL; +} + +static void hvterm_hvsi_close(struct hvc_struct *hp, int data) +{ + struct hvterm_priv *pv = hvterm_privs[hp->vtermno]; + + pr_devel("HVSI@%x: close !\n", pv->termno); + + hvterm_hvsi_shutdown(hp, pv); + + 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: hangup !\n", pv->termno); + + hvterm_hvsi_shutdown(hp, pv); + + 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->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) + hvterm_hvsi_write_mctrl(pv, 1); + else if (clear & TIOCM_DTR) + hvterm_hvsi_write_mctrl(pv, 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; + hvterm_privs[termno] = pv; + } + + 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 +646,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 +688,145 @@ 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; + hvterm_priv0.is_console = 1; + 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; + /* HVSI, perform the handshake now */ + hvterm_hvsi_establish(&hvterm_priv0); + } 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); +} + +/* 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; + hvterm_priv0.is_console = 1; + udbg_putc = udbg_hvc_putc; + udbg_getc = udbg_hvc_getc; + udbg_getc_poll = udbg_hvc_getc_poll; +} +#endif /* CONFIG_PPC_EARLY_DEBUG_LPAR */ - return num_found; +#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; + hvterm_priv0.is_console = 1; + udbg_putc = udbg_hvc_putc; + udbg_getc = udbg_hvc_getc; + udbg_getc_poll = udbg_hvc_getc_poll; + hvterm_hvsi_establish(&hvterm_priv0); } -console_initcall(hvc_find_vtys); +#endif /* CONFIG_PPC_EARLY_DEBUG_LPAR_HVSI */ -- cgit v1.2.3 From 17bdc6c0e979ae61879806e4dd93ec3b169d0931 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Fri, 29 Apr 2011 16:44:24 +1000 Subject: powerpc/pseries: Move hvsi support into a library This will allow a different backend to share it Signed-off-by: Benjamin Herrenschmidt --- arch/powerpc/include/asm/hvsi.h | 34 ++++ drivers/tty/hvc/Makefile | 2 +- drivers/tty/hvc/hvc_vio.c | 405 ++------------------------------------ drivers/tty/hvc/hvsi_lib.c | 426 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 482 insertions(+), 385 deletions(-) create mode 100644 drivers/tty/hvc/hvsi_lib.c (limited to 'drivers/tty') diff --git a/arch/powerpc/include/asm/hvsi.h b/arch/powerpc/include/asm/hvsi.h index ab2ddd76c2e0..91e0453b3743 100644 --- a/arch/powerpc/include/asm/hvsi.h +++ b/arch/powerpc/include/asm/hvsi.h @@ -56,5 +56,39 @@ struct hvsi_query_response { } u; } __attribute__((packed)); +/* hvsi lib struct definitions */ +#define HVSI_INBUF_SIZE 255 +struct tty_struct; +struct hvsi_priv { + unsigned int inbuf_len; /* data in input buffer */ + unsigned char inbuf[HVSI_INBUF_SIZE]; + unsigned int inbuf_cur; /* Cursor in input buffer */ + unsigned int inbuf_pktlen; /* packet lenght from cursor */ + atomic_t seqno; /* packet sequence number */ + unsigned int opened:1; /* driver opened */ + unsigned int established:1; /* protocol established */ + unsigned int is_console:1; /* used as a kernel console device */ + unsigned int mctrl_update:1; /* modem control updated */ + unsigned short mctrl; /* modem control */ + struct tty_struct *tty; /* tty structure */ + int (*get_chars)(uint32_t termno, char *buf, int count); + int (*put_chars)(uint32_t termno, const char *buf, int count); + uint32_t termno; +}; + +/* hvsi lib functions */ +struct hvc_struct; +extern void hvsi_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); +extern int hvsi_open(struct hvsi_priv *pv, struct hvc_struct *hp); +extern void hvsi_close(struct hvsi_priv *pv, struct hvc_struct *hp); +extern int hvsi_read_mctrl(struct hvsi_priv *pv); +extern int hvsi_write_mctrl(struct hvsi_priv *pv, int dtr); +extern void hvsi_establish(struct hvsi_priv *pv); +extern int hvsi_get_chars(struct hvsi_priv *pv, char *buf, int count); +extern int hvsi_put_chars(struct hvsi_priv *pv, const char *buf, int count); #endif /* _HVSI_H */ diff --git a/drivers/tty/hvc/Makefile b/drivers/tty/hvc/Makefile index 69a444b71c63..e29205316376 100644 --- a/drivers/tty/hvc/Makefile +++ b/drivers/tty/hvc/Makefile @@ -1,4 +1,4 @@ -obj-$(CONFIG_HVC_CONSOLE) += hvc_vio.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 diff --git a/drivers/tty/hvc/hvc_vio.c b/drivers/tty/hvc/hvc_vio.c index d4e0850e8051..ade73fae816a 100644 --- a/drivers/tty/hvc/hvc_vio.c +++ b/drivers/tty/hvc/hvc_vio.c @@ -67,22 +67,10 @@ typedef enum hv_protocol { HV_PROTOCOL_HVSI } hv_protocol_t; -#define HV_INBUF_SIZE 255 - struct hvterm_priv { - u32 termno; /* HV term number */ - hv_protocol_t proto; /* Raw data or HVSI packets */ - unsigned int inbuf_len; /* Data in input buffer */ - unsigned char inbuf[HV_INBUF_SIZE]; - unsigned int inbuf_cur; /* Cursor in input buffer */ - unsigned int inbuf_pktlen; /* HVSI packet lenght from cursor */ - atomic_t seqno; /* HVSI packet sequence number */ - unsigned int opened:1; /* HVSI driver opened */ - unsigned int established:1; /* HVSI protocol established */ - unsigned int is_console:1; /* Used as a kernel console device */ - unsigned int mctrl_update:1; /* HVSI modem control updated */ - unsigned short mctrl; /* HVSI modem control */ - struct tty_struct *tty; /* TTY structure */ + u32 termno; /* HV term number */ + hv_protocol_t proto; /* Raw data or HVSI packets */ + struct hvsi_priv hvsi; /* HVSI specific data */ }; static struct hvterm_priv *hvterm_privs[MAX_NR_HVC_CONSOLES]; @@ -139,348 +127,24 @@ static const struct hv_ops hvterm_raw_ops = { .notifier_hangup = notifier_hangup_irq, }; -static int hvterm_hvsi_send_packet(struct hvterm_priv *pv, struct hvsi_header *packet) -{ - packet->seqno = atomic_inc_return(&pv->seqno); - - /* Assumes that always succeeds, works in practice */ - return hvc_put_chars(pv->termno, (char *)packet, packet->len); -} - -static void hvterm_hvsi_start_handshake(struct hvterm_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; - hvterm_hvsi_send_packet(pv, &q.hdr); -} - -static int hvterm_hvsi_send_close(struct hvterm_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 hvterm_hvsi_send_packet(pv, &ctrl.hdr); -} - -static void hvterm_cd_change(struct hvterm_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); - hvterm_hvsi_send_close(pv); - } - } -} - -static void hvterm_hvsi_got_control(struct hvterm_priv *pv) -{ - struct hvsi_control *pkt = (struct hvsi_control *)pv->inbuf; - - switch (pkt->verb) { - case VSV_CLOSE_PROTOCOL: - /* We restart the handshaking */ - hvterm_hvsi_start_handshake(pv); - break; - case VSV_MODEM_CTL_UPDATE: - /* Transition of carrier detect */ - hvterm_cd_change(pv, pkt->word & HVSI_TSCD); - break; - } -} - -static void hvterm_hvsi_got_query(struct hvterm_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; - hvterm_hvsi_send_packet(pv, &r.hdr); - - /* Assume protocol is open now */ - pv->established = 1; -} - -static void hvterm_hvsi_got_response(struct hvterm_priv *pv) -{ - struct hvsi_query_response *r = (struct hvsi_query_response *)pv->inbuf; - - switch(r->verb) { - case VSV_SEND_MODEM_CTL_STATUS: - hvterm_cd_change(pv, r->u.mctrl_word & HVSI_TSCD); - pv->mctrl_update = 1; - break; - } -} - -static int hvterm_hvsi_check_packet(struct hvterm_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: - hvterm_hvsi_got_control(pv); - break; - case VS_QUERY_PACKET_HEADER: - hvterm_hvsi_got_query(pv); - break; - case VS_QUERY_RESPONSE_PACKET_HEADER: - hvterm_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 hvterm_hvsi_get_packet(struct hvterm_priv *pv) -{ - /* If we have room in the buffer, ask HV for more */ - if (pv->inbuf_len < HV_INBUF_SIZE) - pv->inbuf_len += hvc_get_chars(pv->termno, - &pv->inbuf[pv->inbuf_len], - HV_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 hvterm_hvsi_check_packet(pv); - return 0; -} - static int hvterm_hvsi_get_chars(uint32_t vtermno, char *buf, int count) { struct hvterm_priv *pv = hvterm_privs[vtermno]; - unsigned int tries, read = 0; if (WARN_ON(!pv)) return 0; - /* If we aren't open, dont 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 (hvterm_hvsi_get_packet(pv)) - tries--; - } - if (!pv->established) { - pr_devel("HVSI@%x: returning -EPIPE\n", pv->termno); - return -EPIPE; - } - return read; + return hvsi_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]; - 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 = hvterm_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); -} - -static int hvterm_hvsi_read_mctrl(struct hvterm_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 = hvterm_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 1s */ - for (timeout = 0; timeout < 1000; timeout++) { - if (!pv->established) - return -ENXIO; - if (pv->mctrl_update) - return 0; - if (!hvterm_hvsi_get_packet(pv)) - maybe_msleep(1); - } - return -EIO; -} - -static int hvterm_hvsi_write_mctrl(struct hvterm_priv *pv, int dtr) -{ - struct hvsi_control ctrl; - - 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; - if (dtr) - pv->mctrl |= TIOCM_DTR; - else - pv->mctrl &= ~TIOCM_DTR; - return hvterm_hvsi_send_packet(pv, &ctrl.hdr); -} - -static void hvterm_hvsi_establish(struct hvterm_priv *pv) -{ - int timeout; - - /* Try for up to 10ms, there can be a packet to - * start the process waiting for us... - */ - for (timeout = 0; timeout < 10; timeout++) { - if (pv->established) - goto established; - if (!hvterm_hvsi_get_packet(pv)) - maybe_msleep(1); - } - - /* Failed, send a close connection packet just - * in case - */ - hvterm_hvsi_send_close(pv); - - /* Then restart handshake */ - hvterm_hvsi_start_handshake(pv); - - /* Try for up to 100ms */ - for (timeout = 0; timeout < 100; timeout++) { - if (pv->established) - goto established; - if (!hvterm_hvsi_get_packet(pv)) - maybe_msleep(1); - } - - if (!pv->established) { - pr_devel("HVSI@%x: Timeout handshaking, giving up !\n", - pv->termno); - return; - } - established: - /* Query modem control lines */ - hvterm_hvsi_read_mctrl(pv); - - /* Set our own DTR */ - hvterm_hvsi_write_mctrl(pv, 1); - - /* Set the opened flag so reads are allowed */ - wmb(); - pv->opened = 1; + return hvsi_put_chars(&pv->hvsi, buf, count); } static int hvterm_hvsi_open(struct hvc_struct *hp, int data) @@ -494,46 +158,16 @@ static int hvterm_hvsi_open(struct hvc_struct *hp, int data) if (rc) return rc; - /* Keep track of the tty data structure */ - pv->tty = tty_kref_get(hp->tty); - - hvterm_hvsi_establish(pv); - return 0; -} - -static void hvterm_hvsi_shutdown(struct hvc_struct *hp, struct hvterm_priv *pv) -{ - unsigned long flags; - - 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)) - hvterm_hvsi_write_mctrl(pv, 0); - - /* Tear down the connection */ - hvterm_hvsi_send_close(pv); - } - - if (pv->tty) - tty_kref_put(pv->tty); - pv->tty = NULL; + return hvsi_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: close !\n", pv->termno); + pr_devel("HVSI@%x: do close !\n", pv->termno); - hvterm_hvsi_shutdown(hp, pv); + hvsi_close(&pv->hvsi, hp); notifier_del_irq(hp, data); } @@ -542,9 +176,9 @@ void hvterm_hvsi_hangup(struct hvc_struct *hp, int data) { struct hvterm_priv *pv = hvterm_privs[hp->vtermno]; - pr_devel("HVSI@%x: hangup !\n", pv->termno); + pr_devel("HVSI@%x: do hangup !\n", pv->termno); - hvterm_hvsi_shutdown(hp, pv); + hvsi_close(&pv->hvsi, hp); notifier_hangup_irq(hp, data); } @@ -555,7 +189,7 @@ static int hvterm_hvsi_tiocmget(struct hvc_struct *hp) if (!pv) return -EINVAL; - return pv->mctrl; + return pv->hvsi.mctrl; } static int hvterm_hvsi_tiocmset(struct hvc_struct *hp, unsigned int set, @@ -567,9 +201,9 @@ static int hvterm_hvsi_tiocmset(struct hvc_struct *hp, unsigned int set, pv->termno, set, clear); if (set & TIOCM_DTR) - hvterm_hvsi_write_mctrl(pv, 1); + hvsi_write_mctrl(&pv->hvsi, 1); else if (clear & TIOCM_DTR) - hvterm_hvsi_write_mctrl(pv, 0); + hvsi_write_mctrl(&pv->hvsi, 0); return 0; } @@ -633,6 +267,8 @@ static int __devinit hvc_vio_probe(struct vio_dev *vdev, pv->termno = vdev->unit_address; pv->proto = proto; hvterm_privs[termno] = pv; + hvsi_init(&pv->hvsi, hvc_get_chars, hvc_put_chars, + pv->termno, 0); } hp = hvc_alloc(termno, vdev->irq, ops, MAX_VIO_PUT_CHARS); @@ -770,7 +406,6 @@ void __init hvc_vio_init_early(void) if (termno == NULL) goto out; hvterm_priv0.termno = *termno; - hvterm_priv0.is_console = 1; hvterm_privs[0] = &hvterm_priv0; /* Check the protocol */ @@ -781,8 +416,10 @@ void __init hvc_vio_init_early(void) else if (of_device_is_compatible(stdout_node, "hvterm-protocol")) { hvterm_priv0.proto = HV_PROTOCOL_HVSI; ops = &hvterm_hvsi_ops; + hvsi_init(&hvterm_priv0.hvsi, hvc_get_chars, hvc_put_chars, + hvterm_priv0.termno, 1); /* HVSI, perform the handshake now */ - hvterm_hvsi_establish(&hvterm_priv0); + hvsi_establish(&hvterm_priv0.hvsi); } else goto out; udbg_putc = udbg_hvc_putc; @@ -810,7 +447,6 @@ void __init udbg_init_debug_lpar(void) hvterm_privs[0] = &hvterm_priv0; hvterm_priv0.termno = 0; hvterm_priv0.proto = HV_PROTOCOL_RAW; - hvterm_priv0.is_console = 1; udbg_putc = udbg_hvc_putc; udbg_getc = udbg_hvc_getc; udbg_getc_poll = udbg_hvc_getc_poll; @@ -823,10 +459,11 @@ 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; - hvterm_priv0.is_console = 1; udbg_putc = udbg_hvc_putc; udbg_getc = udbg_hvc_getc; udbg_getc_poll = udbg_hvc_getc_poll; - hvterm_hvsi_establish(&hvterm_priv0); + hvsi_init(&hvterm_priv0.hvsi, hvc_get_chars, hvc_put_chars, + hvterm_priv0.termno, 1); + hvsi_establish(&hvterm_priv0.hvsi); } #endif /* CONFIG_PPC_EARLY_DEBUG_LPAR_HVSI */ diff --git a/drivers/tty/hvc/hvsi_lib.c b/drivers/tty/hvc/hvsi_lib.c new file mode 100644 index 000000000000..9401fcb556f0 --- /dev/null +++ b/drivers/tty/hvc/hvsi_lib.c @@ -0,0 +1,426 @@ +#include +#include +#include +#include +#include +#include + +#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 hvsi_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 hvsi_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 hvsi_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 hvsi_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 hvsi_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); + + hvsi_read_mctrl(pv); + + /* Set our own DTR */ + + pr_devel("HVSI@%x: ... setting mctrl\n", pv->termno); + + hvsi_write_mctrl(pv, 1); + + /* Set the opened flag so reads are allowed */ + wmb(); + pv->opened = 1; +} + +int hvsi_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); + + hvsi_establish(pv); + + return 0; +} + +void hvsi_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)) + hvsi_write_mctrl(pv, 0); + + /* Tear down the connection */ + hvsi_send_close(pv); + } + + if (pv->tty) + tty_kref_put(pv->tty); + pv->tty = NULL; +} + +void hvsi_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; +} -- cgit v1.2.3 From 87fa35dd881fd61a2a8166892366f2c22c34a1fa Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Fri, 1 Jul 2011 13:10:21 +1000 Subject: powerpc/hvsi: Fix conflict with old HVSI driver A mix of think & mismerge on my side caused a problem where both the new hvsi_lib and the old hvsi driver gets compiled and try to define symbols with the same name. This fixes it by renaming the hvsi_lib exported symbols. Signed-off-by: Benjamin Herrenschmidt --- arch/powerpc/include/asm/hvsi.h | 24 ++++++++++++------------ drivers/tty/hvc/hvc_vio.c | 30 +++++++++++++++--------------- drivers/tty/hvc/hvsi_lib.c | 32 ++++++++++++++++---------------- 3 files changed, 43 insertions(+), 43 deletions(-) (limited to 'drivers/tty') diff --git a/arch/powerpc/include/asm/hvsi.h b/arch/powerpc/include/asm/hvsi.h index 91e0453b3743..d3f64f361814 100644 --- a/arch/powerpc/include/asm/hvsi.h +++ b/arch/powerpc/include/asm/hvsi.h @@ -78,17 +78,17 @@ struct hvsi_priv { /* hvsi lib functions */ struct hvc_struct; -extern void hvsi_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); -extern int hvsi_open(struct hvsi_priv *pv, struct hvc_struct *hp); -extern void hvsi_close(struct hvsi_priv *pv, struct hvc_struct *hp); -extern int hvsi_read_mctrl(struct hvsi_priv *pv); -extern int hvsi_write_mctrl(struct hvsi_priv *pv, int dtr); -extern void hvsi_establish(struct hvsi_priv *pv); -extern int hvsi_get_chars(struct hvsi_priv *pv, char *buf, int count); -extern int hvsi_put_chars(struct hvsi_priv *pv, const char *buf, int count); +extern 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); +extern int hvsilib_open(struct hvsi_priv *pv, struct hvc_struct *hp); +extern void hvsilib_close(struct hvsi_priv *pv, struct hvc_struct *hp); +extern int hvsilib_read_mctrl(struct hvsi_priv *pv); +extern int hvsilib_write_mctrl(struct hvsi_priv *pv, int dtr); +extern void hvsilib_establish(struct hvsi_priv *pv); +extern int hvsilib_get_chars(struct hvsi_priv *pv, char *buf, int count); +extern int hvsilib_put_chars(struct hvsi_priv *pv, const char *buf, int count); #endif /* _HVSI_H */ diff --git a/drivers/tty/hvc/hvc_vio.c b/drivers/tty/hvc/hvc_vio.c index ade73fae816a..710c06ca70f4 100644 --- a/drivers/tty/hvc/hvc_vio.c +++ b/drivers/tty/hvc/hvc_vio.c @@ -134,7 +134,7 @@ static int hvterm_hvsi_get_chars(uint32_t vtermno, char *buf, int count) if (WARN_ON(!pv)) return 0; - return hvsi_get_chars(&pv->hvsi, buf, count); + return hvsilib_get_chars(&pv->hvsi, buf, count); } static int hvterm_hvsi_put_chars(uint32_t vtermno, const char *buf, int count) @@ -144,7 +144,7 @@ static int hvterm_hvsi_put_chars(uint32_t vtermno, const char *buf, int count) if (WARN_ON(!pv)) return 0; - return hvsi_put_chars(&pv->hvsi, buf, count); + return hvsilib_put_chars(&pv->hvsi, buf, count); } static int hvterm_hvsi_open(struct hvc_struct *hp, int data) @@ -158,7 +158,7 @@ static int hvterm_hvsi_open(struct hvc_struct *hp, int data) if (rc) return rc; - return hvsi_open(&pv->hvsi, hp); + return hvsilib_open(&pv->hvsi, hp); } static void hvterm_hvsi_close(struct hvc_struct *hp, int data) @@ -167,7 +167,7 @@ static void hvterm_hvsi_close(struct hvc_struct *hp, int data) pr_devel("HVSI@%x: do close !\n", pv->termno); - hvsi_close(&pv->hvsi, hp); + hvsilib_close(&pv->hvsi, hp); notifier_del_irq(hp, data); } @@ -178,7 +178,7 @@ void hvterm_hvsi_hangup(struct hvc_struct *hp, int data) pr_devel("HVSI@%x: do hangup !\n", pv->termno); - hvsi_close(&pv->hvsi, hp); + hvsilib_close(&pv->hvsi, hp); notifier_hangup_irq(hp, data); } @@ -201,9 +201,9 @@ static int hvterm_hvsi_tiocmset(struct hvc_struct *hp, unsigned int set, pv->termno, set, clear); if (set & TIOCM_DTR) - hvsi_write_mctrl(&pv->hvsi, 1); + hvsilib_write_mctrl(&pv->hvsi, 1); else if (clear & TIOCM_DTR) - hvsi_write_mctrl(&pv->hvsi, 0); + hvsilib_write_mctrl(&pv->hvsi, 0); return 0; } @@ -267,8 +267,8 @@ static int __devinit hvc_vio_probe(struct vio_dev *vdev, pv->termno = vdev->unit_address; pv->proto = proto; hvterm_privs[termno] = pv; - hvsi_init(&pv->hvsi, hvc_get_chars, hvc_put_chars, - pv->termno, 0); + hvsilib_init(&pv->hvsi, hvc_get_chars, hvc_put_chars, + pv->termno, 0); } hp = hvc_alloc(termno, vdev->irq, ops, MAX_VIO_PUT_CHARS); @@ -416,10 +416,10 @@ void __init hvc_vio_init_early(void) else if (of_device_is_compatible(stdout_node, "hvterm-protocol")) { hvterm_priv0.proto = HV_PROTOCOL_HVSI; ops = &hvterm_hvsi_ops; - hvsi_init(&hvterm_priv0.hvsi, hvc_get_chars, hvc_put_chars, - hvterm_priv0.termno, 1); + hvsilib_init(&hvterm_priv0.hvsi, hvc_get_chars, hvc_put_chars, + hvterm_priv0.termno, 1); /* HVSI, perform the handshake now */ - hvsi_establish(&hvterm_priv0.hvsi); + hvsilib_establish(&hvterm_priv0.hvsi); } else goto out; udbg_putc = udbg_hvc_putc; @@ -462,8 +462,8 @@ void __init udbg_init_debug_lpar_hvsi(void) udbg_putc = udbg_hvc_putc; udbg_getc = udbg_hvc_getc; udbg_getc_poll = udbg_hvc_getc_poll; - hvsi_init(&hvterm_priv0.hvsi, hvc_get_chars, hvc_put_chars, - hvterm_priv0.termno, 1); - hvsi_establish(&hvterm_priv0.hvsi); + hvsilib_init(&hvterm_priv0.hvsi, hvc_get_chars, hvc_put_chars, + hvterm_priv0.termno, 1); + hvsilib_establish(&hvterm_priv0.hvsi); } #endif /* CONFIG_PPC_EARLY_DEBUG_LPAR_HVSI */ diff --git a/drivers/tty/hvc/hvsi_lib.c b/drivers/tty/hvc/hvsi_lib.c index 9401fcb556f0..bd9b09827b24 100644 --- a/drivers/tty/hvc/hvsi_lib.c +++ b/drivers/tty/hvc/hvsi_lib.c @@ -178,7 +178,7 @@ static int hvsi_get_packet(struct hvsi_priv *pv) return 0; } -int hvsi_get_chars(struct hvsi_priv *pv, char *buf, int count) +int hvsilib_get_chars(struct hvsi_priv *pv, char *buf, int count) { unsigned int tries, read = 0; @@ -228,7 +228,7 @@ int hvsi_get_chars(struct hvsi_priv *pv, char *buf, int count) return read; } -int hvsi_put_chars(struct hvsi_priv *pv, const char *buf, int count) +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); @@ -254,7 +254,7 @@ static void maybe_msleep(unsigned long ms) msleep(ms); } -int hvsi_read_mctrl(struct hvsi_priv *pv) +int hvsilib_read_mctrl(struct hvsi_priv *pv) { struct hvsi_query q; int rc, timeout; @@ -285,7 +285,7 @@ int hvsi_read_mctrl(struct hvsi_priv *pv) return -EIO; } -int hvsi_write_mctrl(struct hvsi_priv *pv, int dtr) +int hvsilib_write_mctrl(struct hvsi_priv *pv, int dtr) { struct hvsi_control ctrl; unsigned short mctrl; @@ -310,7 +310,7 @@ int hvsi_write_mctrl(struct hvsi_priv *pv, int dtr) return hvsi_send_packet(pv, &ctrl.hdr); } -void hvsi_establish(struct hvsi_priv *pv) +void hvsilib_establish(struct hvsi_priv *pv) { int timeout; @@ -359,32 +359,32 @@ void hvsi_establish(struct hvsi_priv *pv) pr_devel("HVSI@%x: ... established, reading mctrl\n", pv->termno); - hvsi_read_mctrl(pv); + hvsilib_read_mctrl(pv); /* Set our own DTR */ pr_devel("HVSI@%x: ... setting mctrl\n", pv->termno); - hvsi_write_mctrl(pv, 1); + hvsilib_write_mctrl(pv, 1); /* Set the opened flag so reads are allowed */ wmb(); pv->opened = 1; } -int hvsi_open(struct hvsi_priv *pv, struct hvc_struct *hp) +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); - hvsi_establish(pv); + hvsilib_establish(pv); return 0; } -void hvsi_close(struct hvsi_priv *pv, struct hvc_struct *hp) +void hvsilib_close(struct hvsi_priv *pv, struct hvc_struct *hp) { unsigned long flags; @@ -401,7 +401,7 @@ void hvsi_close(struct hvsi_priv *pv, struct hvc_struct *hp) /* Clear our own DTR */ if (!pv->tty || (pv->tty->termios->c_cflag & HUPCL)) - hvsi_write_mctrl(pv, 0); + hvsilib_write_mctrl(pv, 0); /* Tear down the connection */ hvsi_send_close(pv); @@ -412,11 +412,11 @@ void hvsi_close(struct hvsi_priv *pv, struct hvc_struct *hp) pv->tty = NULL; } -void hvsi_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) +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; -- cgit v1.2.3 From 8c2381af0d3ef62a681dac5a141b6dabb27bf2e1 Mon Sep 17 00:00:00 2001 From: Hendrik Brueckner Date: Tue, 5 Jul 2011 21:50:18 +0000 Subject: hvc_console: Improve tty/console put_chars handling Currently, the hvc_console_print() function drops console output if the hvc backend's put_chars() returns 0. This patch changes this behavior to allow a retry through returning -EAGAIN. This change also affects the hvc_push() function. Both functions are changed to handle -EAGAIN and to retry the put_chars() operation. If a hvc backend returns -EAGAIN, the retry handling differs: - hvc_console_print() spins to write the complete console output. - hvc_push() behaves the same way as for returning 0. Now hvc backends can indirectly control the way how console output is handled through the hvc console layer. Signed-off-by: Hendrik Brueckner Acked-by: Anton Blanchard Cc: Signed-off-by: Benjamin Herrenschmidt --- drivers/tty/hvc/hvc_console.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'drivers/tty') diff --git a/drivers/tty/hvc/hvc_console.c b/drivers/tty/hvc/hvc_console.c index f8ff6f50fc35..59000750cc73 100644 --- a/drivers/tty/hvc/hvc_console.c +++ b/drivers/tty/hvc/hvc_console.c @@ -163,8 +163,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) @@ -448,7 +450,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; } -- cgit v1.2.3 From 19df9abdd30a0448e5940c6aa3527096bb69aca7 Mon Sep 17 00:00:00 2001 From: Anton Blanchard Date: Tue, 12 Jul 2011 19:19:57 +0000 Subject: powerpc/pseries: Fix hvterm_raw_get_chars to accept < 16 chars, fixing xmon commit 4d2bb3f50036 (powerpc/pseries: Re-implement HVSI as part of hvc_vio) changed udbg_getc to be based on hvterm_raw_get_chars. Unfortunately hvterm_raw_get_chars returns -EAGAIN if you ask for anything less than 16 characters. As a result xmon no longer accepts input and prints a stream of junk to the screen. The recent change highlights a problem that xmon on pseries VIO has had all along, that it can drop input characters. The issue is the hypervisor call does not take a count argument and can return up to 16 characters. This patch adds a per vterm buffer that we copy input data into and give it out as requested. Signed-off-by: Anton Blanchard Signed-off-by: Benjamin Herrenschmidt --- drivers/tty/hvc/hvc_vio.c | 58 ++++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 21 deletions(-) (limited to 'drivers/tty') diff --git a/drivers/tty/hvc/hvc_vio.c b/drivers/tty/hvc/hvc_vio.c index 710c06ca70f4..ae659a4596b0 100644 --- a/drivers/tty/hvc/hvc_vio.c +++ b/drivers/tty/hvc/hvc_vio.c @@ -71,41 +71,53 @@ 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) { struct hvterm_priv *pv = hvterm_privs[vtermno]; - unsigned long got, i; + unsigned long i; + unsigned long flags; + int got; if (WARN_ON(!pv)) return 0; - /* - * 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; - - got = hvc_get_chars(pv->termno, buf, count); - - /* - * 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); + spin_lock_irqsave(&pv->buf_lock, flags); + + 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; } @@ -266,6 +278,7 @@ static int __devinit hvc_vio_probe(struct vio_dev *vdev, 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); @@ -406,6 +419,7 @@ void __init hvc_vio_init_early(void) if (termno == NULL) goto out; hvterm_priv0.termno = *termno; + spin_lock_init(&hvterm_priv0.buf_lock); hvterm_privs[0] = &hvterm_priv0; /* Check the protocol */ @@ -447,6 +461,7 @@ 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; @@ -459,6 +474,7 @@ 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; -- cgit v1.2.3 From 762e77ae7dd055d0b77e0ad34d87db7416df109e Mon Sep 17 00:00:00 2001 From: Anton Blanchard Date: Tue, 12 Jul 2011 19:44:05 +0000 Subject: hvc_console: Add kdb support Add poll_get_char and poll_put_char for kdb. Enable kdb at boot with: kgdboc=hvc0 or at runtime with: echo hvc0 > /sys/module/kgdboc/parameters/kgdboc Signed-off-by: Anton Blanchard Signed-off-by: Benjamin Herrenschmidt --- drivers/tty/hvc/hvc_console.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) (limited to 'drivers/tty') diff --git a/drivers/tty/hvc/hvc_console.c b/drivers/tty/hvc/hvc_console.c index 59000750cc73..e1aaf4f309b3 100644 --- a/drivers/tty/hvc/hvc_console.c +++ b/drivers/tty/hvc/hvc_console.c @@ -39,6 +39,7 @@ #include #include #include +#include #include @@ -766,6 +767,39 @@ static int hvc_tiocmset(struct tty_struct *tty, 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, @@ -776,6 +810,11 @@ static const struct tty_operations hvc_ops = { .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, -- cgit v1.2.3 From f7723f0eaf53d51ee54374116b25ac33e0be8542 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Wed, 20 Jul 2011 18:01:48 +1000 Subject: powerpc/pseries: Fix hvc_vio.c build due to recent changes For some reason I didn't notice the failure in my test builds, probably lacking caffeine or something... Signed-off-by: Benjamin Herrenschmidt --- drivers/tty/hvc/hvc_vio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/tty') diff --git a/drivers/tty/hvc/hvc_vio.c b/drivers/tty/hvc/hvc_vio.c index ae659a4596b0..130aace67f31 100644 --- a/drivers/tty/hvc/hvc_vio.c +++ b/drivers/tty/hvc/hvc_vio.c @@ -461,7 +461,7 @@ 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) + spin_lock_init(&hvterm_priv0.buf_lock); udbg_putc = udbg_hvc_putc; udbg_getc = udbg_hvc_getc; udbg_getc_poll = udbg_hvc_getc_poll; @@ -474,7 +474,7 @@ 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) + spin_lock_init(&hvterm_priv0.buf_lock); udbg_putc = udbg_hvc_putc; udbg_getc = udbg_hvc_getc; udbg_getc_poll = udbg_hvc_getc_poll; -- cgit v1.2.3