diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2008-12-28 12:33:21 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2008-12-28 12:33:21 -0800 |
commit | 1db2a5c11e495366bff35cf7445d494703f7febe (patch) | |
tree | 3347dd1cab0a2a96a4333524298a62132eb22336 /drivers | |
parent | a39b863342b8aba52390092be95db58f6ed56061 (diff) | |
parent | cef7125def4dd104769f400c941199614da0aca1 (diff) |
Merge branch 'for-linus' of git://git390.osdl.marist.edu/pub/scm/linux-2.6
* 'for-linus' of git://git390.osdl.marist.edu/pub/scm/linux-2.6: (85 commits)
[S390] provide documentation for hvc_iucv kernel parameter.
[S390] convert ctcm printks to dev_xxx and pr_xxx macros.
[S390] convert zfcp printks to pr_xxx macros.
[S390] convert vmlogrdr printks to pr_xxx macros.
[S390] convert zfcp dumper printks to pr_xxx macros.
[S390] convert cpu related printks to pr_xxx macros.
[S390] convert qeth printks to dev_xxx and pr_xxx macros.
[S390] convert sclp printks to pr_xxx macros.
[S390] convert iucv printks to dev_xxx and pr_xxx macros.
[S390] convert ap_bus printks to pr_xxx macros.
[S390] convert dcssblk and extmem printks messages to pr_xxx macros.
[S390] convert monwriter printks to pr_xxx macros.
[S390] convert s390 debug feature printks to pr_xxx macros.
[S390] convert monreader printks to pr_xxx macros.
[S390] convert appldata printks to pr_xxx macros.
[S390] convert setup printks to pr_xxx macros.
[S390] convert hypfs printks to pr_xxx macros.
[S390] convert time printks to pr_xxx macros.
[S390] convert cpacf printks to pr_xxx macros.
[S390] convert cio printks to pr_xxx macros.
...
Diffstat (limited to 'drivers')
70 files changed, 2751 insertions, 1437 deletions
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 43d6ba83a19..8783457b93d 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -622,6 +622,16 @@ config HVC_BEAT help Toshiba's Cell Reference Set Beat Console device driver +config HVC_IUCV + bool "z/VM IUCV Hypervisor console support (VM only)" + depends on S390 + select HVC_DRIVER + select IUCV + default y + help + This driver provides a Hypervisor console (HVC) back-end to access + a Linux (console) terminal via a z/VM IUCV communication path. + config HVC_XEN bool "Xen Hypervisor Console support" depends on XEN diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 438f71317c5..36151bae0d7 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -50,6 +50,7 @@ obj-$(CONFIG_HVC_BEAT) += hvc_beat.o obj-$(CONFIG_HVC_DRIVER) += hvc_console.o obj-$(CONFIG_HVC_IRQ) += hvc_irq.o obj-$(CONFIG_HVC_XEN) += hvc_xen.o +obj-$(CONFIG_HVC_IUCV) += hvc_iucv.o obj-$(CONFIG_VIRTIO_CONSOLE) += virtio_console.o obj-$(CONFIG_RAW_DRIVER) += raw.o obj-$(CONFIG_SGI_SNSC) += snsc.o snsc_event.o diff --git a/drivers/char/hvc_iucv.c b/drivers/char/hvc_iucv.c new file mode 100644 index 00000000000..5ea7d7713fc --- /dev/null +++ b/drivers/char/hvc_iucv.c @@ -0,0 +1,850 @@ +/* + * hvc_iucv.c - z/VM IUCV back-end for the Hypervisor Console (HVC) + * + * This back-end for HVC provides terminal access via + * z/VM IUCV communication paths. + * + * Copyright IBM Corp. 2008. + * + * Author(s): Hendrik Brueckner <brueckner@linux.vnet.ibm.com> + */ +#define KMSG_COMPONENT "hvc_iucv" + +#include <linux/types.h> +#include <asm/ebcdic.h> +#include <linux/mempool.h> +#include <linux/module.h> +#include <linux/tty.h> +#include <net/iucv/iucv.h> + +#include "hvc_console.h" + + +/* HVC backend for z/VM IUCV */ +#define HVC_IUCV_MAGIC 0xc9e4c3e5 +#define MAX_HVC_IUCV_LINES HVC_ALLOC_TTY_ADAPTERS +#define MEMPOOL_MIN_NR (PAGE_SIZE / sizeof(struct iucv_tty_buffer)/4) + +/* IUCV TTY message */ +#define MSG_VERSION 0x02 /* Message version */ +#define MSG_TYPE_ERROR 0x01 /* Error message */ +#define MSG_TYPE_TERMENV 0x02 /* Terminal environment variable */ +#define MSG_TYPE_TERMIOS 0x04 /* Terminal IO struct update */ +#define MSG_TYPE_WINSIZE 0x08 /* Terminal window size update */ +#define MSG_TYPE_DATA 0x10 /* Terminal data */ + +#define MSG_SIZE(s) ((s) + offsetof(struct iucv_tty_msg, data)) +struct iucv_tty_msg { + u8 version; /* Message version */ + u8 type; /* Message type */ +#define MSG_MAX_DATALEN (~(u16)0) + u16 datalen; /* Payload length */ + u8 data[]; /* Payload buffer */ +} __attribute__((packed)); + +enum iucv_state_t { + IUCV_DISCONN = 0, + IUCV_CONNECTED = 1, + IUCV_SEVERED = 2, +}; + +enum tty_state_t { + TTY_CLOSED = 0, + TTY_OPENED = 1, +}; + +struct hvc_iucv_private { + struct hvc_struct *hvc; /* HVC console struct reference */ + u8 srv_name[8]; /* IUCV service name (ebcdic) */ + enum iucv_state_t iucv_state; /* IUCV connection status */ + enum tty_state_t tty_state; /* TTY status */ + struct iucv_path *path; /* IUCV path pointer */ + spinlock_t lock; /* hvc_iucv_private lock */ + struct list_head tty_outqueue; /* outgoing IUCV messages */ + struct list_head tty_inqueue; /* incoming IUCV messages */ +}; + +struct iucv_tty_buffer { + struct list_head list; /* list pointer */ + struct iucv_message msg; /* store an incoming IUCV message */ + size_t offset; /* data buffer offset */ + struct iucv_tty_msg *mbuf; /* buffer to store input/output data */ +}; + +/* IUCV callback handler */ +static int hvc_iucv_path_pending(struct iucv_path *, u8[8], u8[16]); +static void hvc_iucv_path_severed(struct iucv_path *, u8[16]); +static void hvc_iucv_msg_pending(struct iucv_path *, struct iucv_message *); +static void hvc_iucv_msg_complete(struct iucv_path *, struct iucv_message *); + + +/* Kernel module parameters */ +static unsigned long hvc_iucv_devices; + +/* Array of allocated hvc iucv tty lines... */ +static struct hvc_iucv_private *hvc_iucv_table[MAX_HVC_IUCV_LINES]; + +/* Kmem cache and mempool for iucv_tty_buffer elements */ +static struct kmem_cache *hvc_iucv_buffer_cache; +static mempool_t *hvc_iucv_mempool; + +/* IUCV handler callback functions */ +static struct iucv_handler hvc_iucv_handler = { + .path_pending = hvc_iucv_path_pending, + .path_severed = hvc_iucv_path_severed, + .message_complete = hvc_iucv_msg_complete, + .message_pending = hvc_iucv_msg_pending, +}; + + +/** + * hvc_iucv_get_private() - Return a struct hvc_iucv_private instance. + * @num: The HVC virtual terminal number (vtermno) + * + * This function returns the struct hvc_iucv_private instance that corresponds + * to the HVC virtual terminal number specified as parameter @num. + */ +struct hvc_iucv_private *hvc_iucv_get_private(uint32_t num) +{ + if ((num < HVC_IUCV_MAGIC) || (num - HVC_IUCV_MAGIC > hvc_iucv_devices)) + return NULL; + return hvc_iucv_table[num - HVC_IUCV_MAGIC]; +} + +/** + * alloc_tty_buffer() - Returns a new struct iucv_tty_buffer element. + * @size: Size of the internal buffer used to store data. + * @flags: Memory allocation flags passed to mempool. + * + * This function allocates a new struct iucv_tty_buffer element and, optionally, + * allocates an internal data buffer with the specified size @size. + * Note: The total message size arises from the internal buffer size and the + * members of the iucv_tty_msg structure. + * + * The function returns NULL if memory allocation has failed. + */ +static struct iucv_tty_buffer *alloc_tty_buffer(size_t size, gfp_t flags) +{ + struct iucv_tty_buffer *bufp; + + bufp = mempool_alloc(hvc_iucv_mempool, flags); + if (!bufp) + return NULL; + memset(bufp, 0, sizeof(struct iucv_tty_buffer)); + + if (size > 0) { + bufp->msg.length = MSG_SIZE(size); + bufp->mbuf = kmalloc(bufp->msg.length, flags); + if (!bufp->mbuf) { + mempool_free(bufp, hvc_iucv_mempool); + return NULL; + } + bufp->mbuf->version = MSG_VERSION; + bufp->mbuf->type = MSG_TYPE_DATA; + bufp->mbuf->datalen = (u16) size; + } + return bufp; +} + +/** + * destroy_tty_buffer() - destroy struct iucv_tty_buffer element. + * @bufp: Pointer to a struct iucv_tty_buffer element, SHALL NOT be NULL. + * + * The destroy_tty_buffer() function frees the internal data buffer and returns + * the struct iucv_tty_buffer element back to the mempool for freeing. + */ +static void destroy_tty_buffer(struct iucv_tty_buffer *bufp) +{ + kfree(bufp->mbuf); + mempool_free(bufp, hvc_iucv_mempool); +} + +/** + * destroy_tty_buffer_list() - call destroy_tty_buffer() for each list element. + * @list: List head pointer to a list containing struct iucv_tty_buffer + * elements. + * + * Calls destroy_tty_buffer() for each struct iucv_tty_buffer element in the + * list @list. + */ +static void destroy_tty_buffer_list(struct list_head *list) +{ + struct iucv_tty_buffer *ent, *next; + + list_for_each_entry_safe(ent, next, list, list) { + list_del(&ent->list); + destroy_tty_buffer(ent); + } +} + +/** + * hvc_iucv_write() - Receive IUCV message write data to HVC console buffer. + * @priv: Pointer to hvc_iucv_private structure. + * @buf: HVC console buffer for writing received terminal data. + * @count: HVC console buffer size. + * @has_more_data: Pointer to an int variable. + * + * The function picks up pending messages from the input queue and receives + * the message data that is then written to the specified buffer @buf. + * If the buffer size @count is less than the data message size, then the + * message is kept on the input queue and @has_more_data is set to 1. + * If the message data has been entirely written, the message is removed from + * the input queue. + * + * The function returns the number of bytes written to the terminal, zero if + * there are no pending data messages available or if there is no established + * IUCV path. + * If the IUCV path has been severed, then -EPIPE is returned to cause a + * hang up (that is issued by the HVC console layer). + */ +static int hvc_iucv_write(struct hvc_iucv_private *priv, + char *buf, int count, int *has_more_data) +{ + struct iucv_tty_buffer *rb; + int written; + int rc; + + /* Immediately return if there is no IUCV connection */ + if (priv->iucv_state == IUCV_DISCONN) + return 0; + + /* If the IUCV path has been severed, return -EPIPE to inform the + * hvc console layer to hang up the tty device. */ + if (priv->iucv_state == IUCV_SEVERED) + return -EPIPE; + + /* check if there are pending messages */ + if (list_empty(&priv->tty_inqueue)) + return 0; + + /* receive a iucv message and flip data to the tty (ldisc) */ + rb = list_first_entry(&priv->tty_inqueue, struct iucv_tty_buffer, list); + + written = 0; + if (!rb->mbuf) { /* message not yet received ... */ + /* allocate mem to store msg data; if no memory is available + * then leave the buffer on the list and re-try later */ + rb->mbuf = kmalloc(rb->msg.length, GFP_ATOMIC); + if (!rb->mbuf) + return -ENOMEM; + + rc = __iucv_message_receive(priv->path, &rb->msg, 0, + rb->mbuf, rb->msg.length, NULL); + switch (rc) { + case 0: /* Successful */ + break; + case 2: /* No message found */ + case 9: /* Message purged */ + break; + default: + written = -EIO; + } + /* remove buffer if an error has occured or received data + * is not correct */ + if (rc || (rb->mbuf->version != MSG_VERSION) || + (rb->msg.length != MSG_SIZE(rb->mbuf->datalen))) + goto out_remove_buffer; + } + + switch (rb->mbuf->type) { + case MSG_TYPE_DATA: + written = min_t(int, rb->mbuf->datalen - rb->offset, count); + memcpy(buf, rb->mbuf->data + rb->offset, written); + if (written < (rb->mbuf->datalen - rb->offset)) { + rb->offset += written; + *has_more_data = 1; + goto out_written; + } + break; + + case MSG_TYPE_WINSIZE: + if (rb->mbuf->datalen != sizeof(struct winsize)) + break; + hvc_resize(priv->hvc, *((struct winsize *)rb->mbuf->data)); + break; + + case MSG_TYPE_ERROR: /* ignored ... */ + case MSG_TYPE_TERMENV: /* ignored ... */ + case MSG_TYPE_TERMIOS: /* ignored ... */ + break; + } + +out_remove_buffer: + list_del(&rb->list); + destroy_tty_buffer(rb); + *has_more_data = !list_empty(&priv->tty_inqueue); + +out_written: + return written; +} + +/** + * hvc_iucv_get_chars() - HVC get_chars operation. + * @vtermno: HVC virtual terminal number. + * @buf: Pointer to a buffer to store data + * @count: Size of buffer available for writing + * + * The hvc_console thread calls this method to read characters from + * the terminal backend. If an IUCV communication path has been established, + * pending IUCV messages are received and data is copied into buffer @buf + * up to @count bytes. + * + * Locking: The routine gets called under an irqsave() spinlock; and + * the routine locks the struct hvc_iucv_private->lock to call + * helper functions. + */ +static int hvc_iucv_get_chars(uint32_t vtermno, char *buf, int count) +{ + struct hvc_iucv_private *priv = hvc_iucv_get_private(vtermno); + int written; + int has_more_data; + + if (count <= 0) + return 0; + + if (!priv) + return -ENODEV; + + spin_lock(&priv->lock); + has_more_data = 0; + written = hvc_iucv_write(priv, buf, count, &has_more_data); + spin_unlock(&priv->lock); + + /* if there are still messages on the queue... schedule another run */ + if (has_more_data) + hvc_kick(); + + return written; +} + +/** + * hvc_iucv_send() - Send an IUCV message containing terminal data. + * @priv: Pointer to struct hvc_iucv_private instance. + * @buf: Buffer containing data to send. + * @size: Size of buffer and amount of data to send. + * + * If an IUCV communication path is established, the function copies the buffer + * data to a newly allocated struct iucv_tty_buffer element, sends the data and + * puts the element to the outqueue. + * + * If there is no IUCV communication path established, the function returns 0. + * If an existing IUCV communicaton path has been severed, the function returns + * -EPIPE (can be passed to HVC layer to cause a tty hangup). + */ +static int hvc_iucv_send(struct hvc_iucv_private *priv, const char *buf, + int count) +{ + struct iucv_tty_buffer *sb; + int rc; + u16 len; + + if (priv->iucv_state == IUCV_SEVERED) + return -EPIPE; + + if (priv->iucv_state == IUCV_DISCONN) + return 0; + + len = min_t(u16, MSG_MAX_DATALEN, count); + + /* allocate internal buffer to store msg data and also compute total + * message length */ + sb = alloc_tty_buffer(len, GFP_ATOMIC); + if (!sb) + return -ENOMEM; + + sb->mbuf->datalen = len; + memcpy(sb->mbuf->data, buf, len); + + list_add_tail(&sb->list, &priv->tty_outqueue); + + rc = __iucv_message_send(priv->path, &sb->msg, 0, 0, + (void *) sb->mbuf, sb->msg.length); + if (rc) { + list_del(&sb->list); + destroy_tty_buffer(sb); + len = 0; + } + + return len; +} + +/** + * hvc_iucv_put_chars() - HVC put_chars operation. + * @vtermno: HVC virtual terminal number. + * @buf: Pointer to an buffer to read data from + * @count: Size of buffer available for reading + * + * The hvc_console thread calls this method to write characters from + * to the terminal backend. + * The function calls hvc_iucv_send() under the lock of the + * struct hvc_iucv_private instance that corresponds to the tty @vtermno. + * + * Locking: The method gets called under an irqsave() spinlock; and + * locks struct hvc_iucv_private->lock. + */ +static int hvc_iucv_put_chars(uint32_t vtermno, const char *buf, int count) +{ + struct hvc_iucv_private *priv = hvc_iucv_get_private(vtermno); + int sent; + + if (count <= 0) + return 0; + + if (!priv) + return -ENODEV; + + spin_lock(&priv->lock); + sent = hvc_iucv_send(priv, buf, count); + spin_unlock(&priv->lock); + + return sent; +} + +/** + * hvc_iucv_notifier_add() - HVC notifier for opening a TTY for the first time. + * @hp: Pointer to the HVC device (struct hvc_struct) + * @id: Additional data (originally passed to hvc_alloc): the index of an struct + * hvc_iucv_private instance. + * + * The function sets the tty state to TTY_OPEN for the struct hvc_iucv_private + * instance that is derived from @id. Always returns 0. + * + * Locking: struct hvc_iucv_private->lock, spin_lock_bh + */ +static int hvc_iucv_notifier_add(struct hvc_struct *hp, int id) +{ + struct hvc_iucv_private *priv; + + priv = hvc_iucv_get_private(id); + if (!priv) + return 0; + + spin_lock_bh(&priv->lock); + priv->tty_state = TTY_OPENED; + spin_unlock_bh(&priv->lock); + + return 0; +} + +/** + * hvc_iucv_cleanup() - Clean up function if the tty portion is finally closed. + * @priv: Pointer to the struct hvc_iucv_private instance. + * + * The functions severs the established IUCV communication path (if any), and + * destroy struct iucv_tty_buffer elements from the in- and outqueue. Finally, + * the functions resets the states to TTY_CLOSED and IUCV_DISCONN. + */ +static void hvc_iucv_cleanup(struct hvc_iucv_private *priv) +{ + destroy_tty_buffer_list(&priv->tty_outqueue); + destroy_tty_buffer_list(&priv->tty_inqueue); + + priv->tty_state = TTY_CLOSED; + priv->iucv_state = IUCV_DISCONN; +} + +/** + * hvc_iucv_notifier_hangup() - HVC notifier for tty hangups. + * @hp: Pointer to the HVC device (struct hvc_struct) + * @id: Additional data (originally passed to hvc_alloc): the index of an struct + * hvc_iucv_private instance. + * + * This routine notifies the HVC backend that a tty hangup (carrier loss, + * virtual or otherwise) has occured. + * + * The HVC backend for z/VM IUCV ignores virtual hangups (vhangup()), to keep + * an existing IUCV communication path established. + * (Background: vhangup() is called from user space (by getty or login) to + * disable writing to the tty by other applications). + * + * If the tty has been opened (e.g. getty) and an established IUCV path has been + * severed (we caused the tty hangup in that case), then the functions invokes + * hvc_iucv_cleanup() to clean up. + * + * Locking: struct hvc_iucv_private->lock + */ +static void hvc_iucv_notifier_hangup(struct hvc_struct *hp, int id) +{ + struct hvc_iucv_private *priv; + + priv = hvc_iucv_get_private(id); + if (!priv) + return; + + spin_lock_bh(&priv->lock); + /* NOTE: If the hangup was scheduled by ourself |