| /* -*- linux-c -*- | |
| * | |
| * drivers/char/viocons.c | |
| * | |
| * iSeries Virtual Terminal | |
| * | |
| * Authors: Dave Boutcher <boutcher@us.ibm.com> | |
| * Ryan Arnold <ryanarn@us.ibm.com> | |
| * Colin Devilbiss <devilbis@us.ibm.com> | |
| * Stephen Rothwell | |
| * | |
| * (C) Copyright 2000, 2001, 2002, 2003, 2004 IBM Corporation | |
| * | |
| * This program is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU General Public License as | |
| * published by the Free Software Foundation; either version 2 of the | |
| * License, or (at your option) anyu later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, but | |
| * WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * General Public License for more details. | |
| * | |
| * 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 | |
| */ | |
| #include <linux/kernel.h> | |
| #include <linux/proc_fs.h> | |
| #include <linux/errno.h> | |
| #include <linux/vmalloc.h> | |
| #include <linux/mm.h> | |
| #include <linux/console.h> | |
| #include <linux/module.h> | |
| #include <asm/uaccess.h> | |
| #include <linux/init.h> | |
| #include <linux/wait.h> | |
| #include <linux/spinlock.h> | |
| #include <asm/ioctls.h> | |
| #include <linux/kd.h> | |
| #include <linux/tty.h> | |
| #include <linux/tty_flip.h> | |
| #include <linux/sysrq.h> | |
| #include <asm/firmware.h> | |
| #include <asm/iseries/vio.h> | |
| #include <asm/iseries/hv_lp_event.h> | |
| #include <asm/iseries/hv_call_event.h> | |
| #include <asm/iseries/hv_lp_config.h> | |
| #include <asm/iseries/hv_call.h> | |
| #ifdef CONFIG_VT | |
| #error You must turn off CONFIG_VT to use CONFIG_VIOCONS | |
| #endif | |
| #define VIOTTY_MAGIC (0x0DCB) | |
| #define VTTY_PORTS 10 | |
| #define VIOCONS_KERN_WARN KERN_WARNING "viocons: " | |
| #define VIOCONS_KERN_INFO KERN_INFO "viocons: " | |
| static DEFINE_SPINLOCK(consolelock); | |
| static DEFINE_SPINLOCK(consoleloglock); | |
| static int vio_sysrq_pressed; | |
| #define VIOCHAR_NUM_BUF 16 | |
| /* | |
| * Our port information. We store a pointer to one entry in the | |
| * tty_driver_data | |
| */ | |
| static struct port_info { | |
| int magic; | |
| struct tty_struct *tty; | |
| HvLpIndex lp; | |
| u8 vcons; | |
| u64 seq; /* sequence number of last HV send */ | |
| u64 ack; /* last ack from HV */ | |
| /* | |
| * When we get writes faster than we can send it to the partition, | |
| * buffer the data here. Note that used is a bit map of used buffers. | |
| * It had better have enough bits to hold VIOCHAR_NUM_BUF the bitops assume | |
| * it is a multiple of unsigned long | |
| */ | |
| unsigned long used; | |
| u8 *buffer[VIOCHAR_NUM_BUF]; | |
| int bufferBytes[VIOCHAR_NUM_BUF]; | |
| int curbuf; | |
| int bufferOverflow; | |
| int overflowMessage; | |
| } port_info[VTTY_PORTS]; | |
| #define viochar_is_console(pi) ((pi) == &port_info[0]) | |
| #define viochar_port(pi) ((pi) - &port_info[0]) | |
| static void initDataEvent(struct viocharlpevent *viochar, HvLpIndex lp); | |
| static struct tty_driver *viotty_driver; | |
| static void hvlog(char *fmt, ...) | |
| { | |
| int i; | |
| unsigned long flags; | |
| va_list args; | |
| static char buf[256]; | |
| spin_lock_irqsave(&consoleloglock, flags); | |
| va_start(args, fmt); | |
| i = vscnprintf(buf, sizeof(buf) - 1, fmt, args); | |
| va_end(args); | |
| buf[i++] = '\r'; | |
| HvCall_writeLogBuffer(buf, i); | |
| spin_unlock_irqrestore(&consoleloglock, flags); | |
| } | |
| static void hvlogOutput(const char *buf, int count) | |
| { | |
| unsigned long flags; | |
| int begin; | |
| int index; | |
| static const char cr = '\r'; | |
| begin = 0; | |
| spin_lock_irqsave(&consoleloglock, flags); | |
| for (index = 0; index < count; index++) { | |
| if (buf[index] == '\n') { | |
| /* | |
| * Start right after the last '\n' or at the zeroth | |
| * array position and output the number of characters | |
| * including the newline. | |
| */ | |
| HvCall_writeLogBuffer(&buf[begin], index - begin + 1); | |
| begin = index + 1; | |
| HvCall_writeLogBuffer(&cr, 1); | |
| } | |
| } | |
| if ((index - begin) > 0) | |
| HvCall_writeLogBuffer(&buf[begin], index - begin); | |
| spin_unlock_irqrestore(&consoleloglock, flags); | |
| } | |
| /* | |
| * Make sure we're pointing to a valid port_info structure. Shamelessly | |
| * plagerized from serial.c | |
| */ | |
| static inline int viotty_paranoia_check(struct port_info *pi, | |
| char *name, const char *routine) | |
| { | |
| static const char *bad_pi_addr = VIOCONS_KERN_WARN | |
| "warning: bad address for port_info struct (%s) in %s\n"; | |
| static const char *badmagic = VIOCONS_KERN_WARN | |
| "warning: bad magic number for port_info struct (%s) in %s\n"; | |
| if ((pi < &port_info[0]) || (viochar_port(pi) > VTTY_PORTS)) { | |
| printk(bad_pi_addr, name, routine); | |
| return 1; | |
| } | |
| if (pi->magic != VIOTTY_MAGIC) { | |
| printk(badmagic, name, routine); | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Add data to our pending-send buffers. | |
| * | |
| * NOTE: Don't use printk in here because it gets nastily recursive. | |
| * hvlog can be used to log to the hypervisor buffer | |
| */ | |
| static int buffer_add(struct port_info *pi, const char *buf, size_t len) | |
| { | |
| size_t bleft; | |
| size_t curlen; | |
| const char *curbuf; | |
| int nextbuf; | |
| curbuf = buf; | |
| bleft = len; | |
| while (bleft > 0) { | |
| /* | |
| * If there is no space left in the current buffer, we have | |
| * filled everything up, so return. If we filled the previous | |
| * buffer we would already have moved to the next one. | |
| */ | |
| if (pi->bufferBytes[pi->curbuf] == VIOCHAR_MAX_DATA) { | |
| hvlog ("\n\rviocons: No overflow buffer available for memcpy().\n"); | |
| pi->bufferOverflow++; | |
| pi->overflowMessage = 1; | |
| break; | |
| } | |
| /* | |
| * Turn on the "used" bit for this buffer. If it's already on, | |
| * that's fine. | |
| */ | |
| set_bit(pi->curbuf, &pi->used); | |
| /* | |
| * See if this buffer has been allocated. If not, allocate it. | |
| */ | |
| if (pi->buffer[pi->curbuf] == NULL) { | |
| pi->buffer[pi->curbuf] = | |
| kmalloc(VIOCHAR_MAX_DATA, GFP_ATOMIC); | |
| if (pi->buffer[pi->curbuf] == NULL) { | |
| hvlog("\n\rviocons: kmalloc failed allocating spaces for buffer %d.", | |
| pi->curbuf); | |
| break; | |
| } | |
| } | |
| /* Figure out how much we can copy into this buffer. */ | |
| if (bleft < (VIOCHAR_MAX_DATA - pi->bufferBytes[pi->curbuf])) | |
| curlen = bleft; | |
| else | |
| curlen = VIOCHAR_MAX_DATA - pi->bufferBytes[pi->curbuf]; | |
| /* Copy the data into the buffer. */ | |
| memcpy(pi->buffer[pi->curbuf] + pi->bufferBytes[pi->curbuf], | |
| curbuf, curlen); | |
| pi->bufferBytes[pi->curbuf] += curlen; | |
| curbuf += curlen; | |
| bleft -= curlen; | |
| /* | |
| * Now see if we've filled this buffer. If not then | |
| * we'll try to use it again later. If we've filled it | |
| * up then we'll advance the curbuf to the next in the | |
| * circular queue. | |
| */ | |
| if (pi->bufferBytes[pi->curbuf] == VIOCHAR_MAX_DATA) { | |
| nextbuf = (pi->curbuf + 1) % VIOCHAR_NUM_BUF; | |
| /* | |
| * Move to the next buffer if it hasn't been used yet | |
| */ | |
| if (test_bit(nextbuf, &pi->used) == 0) | |
| pi->curbuf = nextbuf; | |
| } | |
| } | |
| return len - bleft; | |
| } | |
| /* | |
| * Send pending data | |
| * | |
| * NOTE: Don't use printk in here because it gets nastily recursive. | |
| * hvlog can be used to log to the hypervisor buffer | |
| */ | |
| static void send_buffers(struct port_info *pi) | |
| { | |
| HvLpEvent_Rc hvrc; | |
| int nextbuf; | |
| struct viocharlpevent *viochar; | |
| unsigned long flags; | |
| spin_lock_irqsave(&consolelock, flags); | |
| viochar = (struct viocharlpevent *) | |
| vio_get_event_buffer(viomajorsubtype_chario); | |
| /* Make sure we got a buffer */ | |
| if (viochar == NULL) { | |
| hvlog("\n\rviocons: Can't get viochar buffer in sendBuffers()."); | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| return; | |
| } | |
| if (pi->used == 0) { | |
| hvlog("\n\rviocons: in sendbuffers(), but no buffers used.\n"); | |
| vio_free_event_buffer(viomajorsubtype_chario, viochar); | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| return; | |
| } | |
| /* | |
| * curbuf points to the buffer we're filling. We want to | |
| * start sending AFTER this one. | |
| */ | |
| nextbuf = (pi->curbuf + 1) % VIOCHAR_NUM_BUF; | |
| /* | |
| * Loop until we find a buffer with the used bit on | |
| */ | |
| while (test_bit(nextbuf, &pi->used) == 0) | |
| nextbuf = (nextbuf + 1) % VIOCHAR_NUM_BUF; | |
| initDataEvent(viochar, pi->lp); | |
| /* | |
| * While we have buffers with data, and our send window | |
| * is open, send them | |
| */ | |
| while ((test_bit(nextbuf, &pi->used)) && | |
| ((pi->seq - pi->ack) < VIOCHAR_WINDOW)) { | |
| viochar->len = pi->bufferBytes[nextbuf]; | |
| viochar->event.xCorrelationToken = pi->seq++; | |
| viochar->event.xSizeMinus1 = | |
| offsetof(struct viocharlpevent, data) + viochar->len; | |
| memcpy(viochar->data, pi->buffer[nextbuf], viochar->len); | |
| hvrc = HvCallEvent_signalLpEvent(&viochar->event); | |
| if (hvrc) { | |
| /* | |
| * MUST unlock the spinlock before doing a printk | |
| */ | |
| vio_free_event_buffer(viomajorsubtype_chario, viochar); | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| printk(VIOCONS_KERN_WARN | |
| "error sending event! return code %d\n", | |
| (int)hvrc); | |
| return; | |
| } | |
| /* | |
| * clear the used bit, zero the number of bytes in | |
| * this buffer, and move to the next buffer | |
| */ | |
| clear_bit(nextbuf, &pi->used); | |
| pi->bufferBytes[nextbuf] = 0; | |
| nextbuf = (nextbuf + 1) % VIOCHAR_NUM_BUF; | |
| } | |
| /* | |
| * If we have emptied all the buffers, start at 0 again. | |
| * this will re-use any allocated buffers | |
| */ | |
| if (pi->used == 0) { | |
| pi->curbuf = 0; | |
| if (pi->overflowMessage) | |
| pi->overflowMessage = 0; | |
| if (pi->tty) { | |
| tty_wakeup(pi->tty); | |
| } | |
| } | |
| vio_free_event_buffer(viomajorsubtype_chario, viochar); | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| } | |
| /* | |
| * Our internal writer. Gets called both from the console device and | |
| * the tty device. the tty pointer will be NULL if called from the console. | |
| * Return total number of bytes "written". | |
| * | |
| * NOTE: Don't use printk in here because it gets nastily recursive. hvlog | |
| * can be used to log to the hypervisor buffer | |
| */ | |
| static int internal_write(struct port_info *pi, const char *buf, size_t len) | |
| { | |
| HvLpEvent_Rc hvrc; | |
| size_t bleft; | |
| size_t curlen; | |
| const char *curbuf; | |
| unsigned long flags; | |
| struct viocharlpevent *viochar; | |
| /* | |
| * Write to the hvlog of inbound data are now done prior to | |
| * calling internal_write() since internal_write() is only called in | |
| * the event that an lp event path is active, which isn't the case for | |
| * logging attempts prior to console initialization. | |
| * | |
| * If there is already data queued for this port, send it prior to | |
| * attempting to send any new data. | |
| */ | |
| if (pi->used) | |
| send_buffers(pi); | |
| spin_lock_irqsave(&consolelock, flags); | |
| viochar = vio_get_event_buffer(viomajorsubtype_chario); | |
| if (viochar == NULL) { | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| hvlog("\n\rviocons: Can't get vio buffer in internal_write()."); | |
| return -EAGAIN; | |
| } | |
| initDataEvent(viochar, pi->lp); | |
| curbuf = buf; | |
| bleft = len; | |
| while ((bleft > 0) && (pi->used == 0) && | |
| ((pi->seq - pi->ack) < VIOCHAR_WINDOW)) { | |
| if (bleft > VIOCHAR_MAX_DATA) | |
| curlen = VIOCHAR_MAX_DATA; | |
| else | |
| curlen = bleft; | |
| viochar->event.xCorrelationToken = pi->seq++; | |
| memcpy(viochar->data, curbuf, curlen); | |
| viochar->len = curlen; | |
| viochar->event.xSizeMinus1 = | |
| offsetof(struct viocharlpevent, data) + curlen; | |
| hvrc = HvCallEvent_signalLpEvent(&viochar->event); | |
| if (hvrc) { | |
| hvlog("viocons: error sending event! %d\n", (int)hvrc); | |
| goto out; | |
| } | |
| curbuf += curlen; | |
| bleft -= curlen; | |
| } | |
| /* If we didn't send it all, buffer as much of it as we can. */ | |
| if (bleft > 0) | |
| bleft -= buffer_add(pi, curbuf, bleft); | |
| out: | |
| vio_free_event_buffer(viomajorsubtype_chario, viochar); | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| return len - bleft; | |
| } | |
| static struct port_info *get_port_data(struct tty_struct *tty) | |
| { | |
| unsigned long flags; | |
| struct port_info *pi; | |
| spin_lock_irqsave(&consolelock, flags); | |
| if (tty) { | |
| pi = (struct port_info *)tty->driver_data; | |
| if (!pi || viotty_paranoia_check(pi, tty->name, | |
| "get_port_data")) { | |
| pi = NULL; | |
| } | |
| } else | |
| /* | |
| * If this is the console device, use the lp from | |
| * the first port entry | |
| */ | |
| pi = &port_info[0]; | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| return pi; | |
| } | |
| /* | |
| * Initialize the common fields in a charLpEvent | |
| */ | |
| static void initDataEvent(struct viocharlpevent *viochar, HvLpIndex lp) | |
| { | |
| struct HvLpEvent *hev = &viochar->event; | |
| memset(viochar, 0, sizeof(struct viocharlpevent)); | |
| hev->flags = HV_LP_EVENT_VALID | HV_LP_EVENT_DEFERRED_ACK | | |
| HV_LP_EVENT_INT; | |
| hev->xType = HvLpEvent_Type_VirtualIo; | |
| hev->xSubtype = viomajorsubtype_chario | viochardata; | |
| hev->xSourceLp = HvLpConfig_getLpIndex(); | |
| hev->xTargetLp = lp; | |
| hev->xSizeMinus1 = sizeof(struct viocharlpevent); | |
| hev->xSourceInstanceId = viopath_sourceinst(lp); | |
| hev->xTargetInstanceId = viopath_targetinst(lp); | |
| } | |
| /* | |
| * early console device write | |
| */ | |
| static void viocons_write_early(struct console *co, const char *s, unsigned count) | |
| { | |
| hvlogOutput(s, count); | |
| } | |
| /* | |
| * console device write | |
| */ | |
| static void viocons_write(struct console *co, const char *s, unsigned count) | |
| { | |
| int index; | |
| int begin; | |
| struct port_info *pi; | |
| static const char cr = '\r'; | |
| /* | |
| * Check port data first because the target LP might be valid but | |
| * simply not active, in which case we want to hvlog the output. | |
| */ | |
| pi = get_port_data(NULL); | |
| if (pi == NULL) { | |
| hvlog("\n\rviocons_write: unable to get port data."); | |
| return; | |
| } | |
| hvlogOutput(s, count); | |
| if (!viopath_isactive(pi->lp)) | |
| return; | |
| /* | |
| * Any newline character found will cause a | |
| * carriage return character to be emitted as well. | |
| */ | |
| begin = 0; | |
| for (index = 0; index < count; index++) { | |
| if (s[index] == '\n') { | |
| /* | |
| * Newline found. Print everything up to and | |
| * including the newline | |
| */ | |
| internal_write(pi, &s[begin], index - begin + 1); | |
| begin = index + 1; | |
| /* Emit a carriage return as well */ | |
| internal_write(pi, &cr, 1); | |
| } | |
| } | |
| /* If any characters left to write, write them now */ | |
| if ((index - begin) > 0) | |
| internal_write(pi, &s[begin], index - begin); | |
| } | |
| /* | |
| * Work out the device associate with this console | |
| */ | |
| static struct tty_driver *viocons_device(struct console *c, int *index) | |
| { | |
| *index = c->index; | |
| return viotty_driver; | |
| } | |
| /* | |
| * console device I/O methods | |
| */ | |
| static struct console viocons_early = { | |
| .name = "viocons", | |
| .write = viocons_write_early, | |
| .flags = CON_PRINTBUFFER, | |
| .index = -1, | |
| }; | |
| static struct console viocons = { | |
| .name = "viocons", | |
| .write = viocons_write, | |
| .device = viocons_device, | |
| .flags = CON_PRINTBUFFER, | |
| .index = -1, | |
| }; | |
| /* | |
| * TTY Open method | |
| */ | |
| static int viotty_open(struct tty_struct *tty, struct file *filp) | |
| { | |
| int port; | |
| unsigned long flags; | |
| struct port_info *pi; | |
| port = tty->index; | |
| if ((port < 0) || (port >= VTTY_PORTS)) | |
| return -ENODEV; | |
| spin_lock_irqsave(&consolelock, flags); | |
| pi = &port_info[port]; | |
| /* If some other TTY is already connected here, reject the open */ | |
| if ((pi->tty) && (pi->tty != tty)) { | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| printk(VIOCONS_KERN_WARN | |
| "attempt to open device twice from different ttys\n"); | |
| return -EBUSY; | |
| } | |
| tty->driver_data = pi; | |
| pi->tty = tty; | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| return 0; | |
| } | |
| /* | |
| * TTY Close method | |
| */ | |
| static void viotty_close(struct tty_struct *tty, struct file *filp) | |
| { | |
| unsigned long flags; | |
| struct port_info *pi; | |
| spin_lock_irqsave(&consolelock, flags); | |
| pi = (struct port_info *)tty->driver_data; | |
| if (!pi || viotty_paranoia_check(pi, tty->name, "viotty_close")) { | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| return; | |
| } | |
| if (tty->count == 1) | |
| pi->tty = NULL; | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| } | |
| /* | |
| * TTY Write method | |
| */ | |
| static int viotty_write(struct tty_struct *tty, const unsigned char *buf, | |
| int count) | |
| { | |
| struct port_info *pi; | |
| pi = get_port_data(tty); | |
| if (pi == NULL) { | |
| hvlog("\n\rviotty_write: no port data."); | |
| return -ENODEV; | |
| } | |
| if (viochar_is_console(pi)) | |
| hvlogOutput(buf, count); | |
| /* | |
| * If the path to this LP is closed, don't bother doing anything more. | |
| * just dump the data on the floor and return count. For some reason | |
| * some user level programs will attempt to probe available tty's and | |
| * they'll attempt a viotty_write on an invalid port which maps to an | |
| * invalid target lp. If this is the case then ignore the | |
| * viotty_write call and, since the viopath isn't active to this | |
| * partition, return count. | |
| */ | |
| if (!viopath_isactive(pi->lp)) | |
| return count; | |
| return internal_write(pi, buf, count); | |
| } | |
| /* | |
| * TTY put_char method | |
| */ | |
| static int viotty_put_char(struct tty_struct *tty, unsigned char ch) | |
| { | |
| struct port_info *pi; | |
| pi = get_port_data(tty); | |
| if (pi == NULL) | |
| return 0; | |
| /* This will append '\r' as well if the char is '\n' */ | |
| if (viochar_is_console(pi)) | |
| hvlogOutput(&ch, 1); | |
| if (viopath_isactive(pi->lp)) | |
| internal_write(pi, &ch, 1); | |
| return 1; | |
| } | |
| /* | |
| * TTY write_room method | |
| */ | |
| static int viotty_write_room(struct tty_struct *tty) | |
| { | |
| int i; | |
| int room = 0; | |
| struct port_info *pi; | |
| unsigned long flags; | |
| spin_lock_irqsave(&consolelock, flags); | |
| pi = (struct port_info *)tty->driver_data; | |
| if (!pi || viotty_paranoia_check(pi, tty->name, "viotty_write_room")) { | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| return 0; | |
| } | |
| /* If no buffers are used, return the max size. */ | |
| if (pi->used == 0) { | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| return VIOCHAR_MAX_DATA * VIOCHAR_NUM_BUF; | |
| } | |
| /* | |
| * We retain the spinlock because we want to get an accurate | |
| * count and it can change on us between each operation if we | |
| * don't hold the spinlock. | |
| */ | |
| for (i = 0; ((i < VIOCHAR_NUM_BUF) && (room < VIOCHAR_MAX_DATA)); i++) | |
| room += (VIOCHAR_MAX_DATA - pi->bufferBytes[i]); | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| if (room > VIOCHAR_MAX_DATA) | |
| room = VIOCHAR_MAX_DATA; | |
| return room; | |
| } | |
| /* | |
| * TTY chars_in_buffer method | |
| */ | |
| static int viotty_chars_in_buffer(struct tty_struct *tty) | |
| { | |
| return 0; | |
| } | |
| static int viotty_ioctl(struct tty_struct *tty, struct file *file, | |
| unsigned int cmd, unsigned long arg) | |
| { | |
| switch (cmd) { | |
| /* | |
| * the ioctls below read/set the flags usually shown in the leds | |
| * don't use them - they will go away without warning | |
| */ | |
| case KDGETLED: | |
| case KDGKBLED: | |
| return put_user(0, (char *)arg); | |
| case KDSKBLED: | |
| return 0; | |
| } | |
| /* FIXME: WTF is this being called for ??? */ | |
| lock_kernel(); | |
| ret = n_tty_ioctl(tty, file, cmd, arg); | |
| unlock_kernel(); | |
| return ret; | |
| } | |
| /* | |
| * Handle an open charLpEvent. Could be either interrupt or ack | |
| */ | |
| static void vioHandleOpenEvent(struct HvLpEvent *event) | |
| { | |
| unsigned long flags; | |
| struct viocharlpevent *cevent = (struct viocharlpevent *)event; | |
| u8 port = cevent->virtual_device; | |
| struct port_info *pi; | |
| int reject = 0; | |
| if (hvlpevent_is_ack(event)) { | |
| if (port >= VTTY_PORTS) | |
| return; | |
| spin_lock_irqsave(&consolelock, flags); | |
| /* Got the lock, don't cause console output */ | |
| pi = &port_info[port]; | |
| if (event->xRc == HvLpEvent_Rc_Good) { | |
| pi->seq = pi->ack = 0; | |
| /* | |
| * This line allows connections from the primary | |
| * partition but once one is connected from the | |
| * primary partition nothing short of a reboot | |
| * of linux will allow access from the hosting | |
| * partition again without a required iSeries fix. | |
| */ | |
| pi->lp = event->xTargetLp; | |
| } | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| if (event->xRc != HvLpEvent_Rc_Good) | |
| printk(VIOCONS_KERN_WARN | |
| "handle_open_event: event->xRc == (%d).\n", | |
| event->xRc); | |
| if (event->xCorrelationToken != 0) { | |
| atomic_t *aptr= (atomic_t *)event->xCorrelationToken; | |
| atomic_set(aptr, 1); | |
| } else | |
| printk(VIOCONS_KERN_WARN | |
| "weird...got open ack without atomic\n"); | |
| return; | |
| } | |
| /* This had better require an ack, otherwise complain */ | |
| if (!hvlpevent_need_ack(event)) { | |
| printk(VIOCONS_KERN_WARN "viocharopen without ack bit!\n"); | |
| return; | |
| } | |
| spin_lock_irqsave(&consolelock, flags); | |
| /* Got the lock, don't cause console output */ | |
| /* Make sure this is a good virtual tty */ | |
| if (port >= VTTY_PORTS) { | |
| event->xRc = HvLpEvent_Rc_SubtypeError; | |
| cevent->subtype_result_code = viorc_openRejected; | |
| /* | |
| * Flag state here since we can't printk while holding | |
| * a spinlock. | |
| */ | |
| reject = 1; | |
| } else { | |
| pi = &port_info[port]; | |
| if ((pi->lp != HvLpIndexInvalid) && | |
| (pi->lp != event->xSourceLp)) { | |
| /* | |
| * If this is tty is already connected to a different | |
| * partition, fail. | |
| */ | |
| event->xRc = HvLpEvent_Rc_SubtypeError; | |
| cevent->subtype_result_code = viorc_openRejected; | |
| reject = 2; | |
| } else { | |
| pi->lp = event->xSourceLp; | |
| event->xRc = HvLpEvent_Rc_Good; | |
| cevent->subtype_result_code = viorc_good; | |
| pi->seq = pi->ack = 0; | |
| reject = 0; | |
| } | |
| } | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| if (reject == 1) | |
| printk(VIOCONS_KERN_WARN "open rejected: bad virtual tty.\n"); | |
| else if (reject == 2) | |
| printk(VIOCONS_KERN_WARN | |
| "open rejected: console in exclusive use by another partition.\n"); | |
| /* Return the acknowledgement */ | |
| HvCallEvent_ackLpEvent(event); | |
| } | |
| /* | |
| * Handle a close charLpEvent. This should ONLY be an Interrupt because the | |
| * virtual console should never actually issue a close event to the hypervisor | |
| * because the virtual console never goes away. A close event coming from the | |
| * hypervisor simply means that there are no client consoles connected to the | |
| * virtual console. | |
| * | |
| * Regardless of the number of connections masqueraded on the other side of | |
| * the hypervisor ONLY ONE close event should be called to accompany the ONE | |
| * open event that is called. The close event should ONLY be called when NO | |
| * MORE connections (masqueraded or not) exist on the other side of the | |
| * hypervisor. | |
| */ | |
| static void vioHandleCloseEvent(struct HvLpEvent *event) | |
| { | |
| unsigned long flags; | |
| struct viocharlpevent *cevent = (struct viocharlpevent *)event; | |
| u8 port = cevent->virtual_device; | |
| if (hvlpevent_is_int(event)) { | |
| if (port >= VTTY_PORTS) { | |
| printk(VIOCONS_KERN_WARN | |
| "close message from invalid virtual device.\n"); | |
| return; | |
| } | |
| /* For closes, just mark the console partition invalid */ | |
| spin_lock_irqsave(&consolelock, flags); | |
| /* Got the lock, don't cause console output */ | |
| if (port_info[port].lp == event->xSourceLp) | |
| port_info[port].lp = HvLpIndexInvalid; | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| printk(VIOCONS_KERN_INFO "close from %d\n", event->xSourceLp); | |
| } else | |
| printk(VIOCONS_KERN_WARN | |
| "got unexpected close acknowlegement\n"); | |
| } | |
| /* | |
| * Handle a config charLpEvent. Could be either interrupt or ack | |
| */ | |
| static void vioHandleConfig(struct HvLpEvent *event) | |
| { | |
| struct viocharlpevent *cevent = (struct viocharlpevent *)event; | |
| HvCall_writeLogBuffer(cevent->data, cevent->len); | |
| if (cevent->data[0] == 0x01) | |
| printk(VIOCONS_KERN_INFO "window resized to %d: %d: %d: %d\n", | |
| cevent->data[1], cevent->data[2], | |
| cevent->data[3], cevent->data[4]); | |
| else | |
| printk(VIOCONS_KERN_WARN "unknown config event\n"); | |
| } | |
| /* | |
| * Handle a data charLpEvent. | |
| */ | |
| static void vioHandleData(struct HvLpEvent *event) | |
| { | |
| struct tty_struct *tty; | |
| unsigned long flags; | |
| struct viocharlpevent *cevent = (struct viocharlpevent *)event; | |
| struct port_info *pi; | |
| int index; | |
| int num_pushed; | |
| u8 port = cevent->virtual_device; | |
| if (port >= VTTY_PORTS) { | |
| printk(VIOCONS_KERN_WARN "data on invalid virtual device %d\n", | |
| port); | |
| return; | |
| } | |
| /* | |
| * Hold the spinlock so that we don't take an interrupt that | |
| * changes tty between the time we fetch the port_info | |
| * pointer and the time we paranoia check. | |
| */ | |
| spin_lock_irqsave(&consolelock, flags); | |
| pi = &port_info[port]; | |
| /* | |
| * Change 05/01/2003 - Ryan Arnold: If a partition other than | |
| * the current exclusive partition tries to send us data | |
| * events then just drop them on the floor because we don't | |
| * want his stinking data. He isn't authorized to receive | |
| * data because he wasn't the first one to get the console, | |
| * therefore he shouldn't be allowed to send data either. | |
| * This will work without an iSeries fix. | |
| */ | |
| if (pi->lp != event->xSourceLp) { | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| return; | |
| } | |
| tty = pi->tty; | |
| if (tty == NULL) { | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| printk(VIOCONS_KERN_WARN "no tty for virtual device %d\n", | |
| port); | |
| return; | |
| } | |
| if (tty->magic != TTY_MAGIC) { | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| printk(VIOCONS_KERN_WARN "tty bad magic\n"); | |
| return; | |
| } | |
| /* | |
| * Just to be paranoid, make sure the tty points back to this port | |
| */ | |
| pi = (struct port_info *)tty->driver_data; | |
| if (!pi || viotty_paranoia_check(pi, tty->name, "vioHandleData")) { | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| return; | |
| } | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| /* | |
| * Change 07/21/2003 - Ryan Arnold: functionality added to | |
| * support sysrq utilizing ^O as the sysrq key. The sysrq | |
| * functionality will only work if built into the kernel and | |
| * then only if sysrq is enabled through the proc filesystem. | |
| */ | |
| num_pushed = 0; | |
| for (index = 0; index < cevent->len; index++) { | |
| /* | |
| * Will be optimized away if !CONFIG_MAGIC_SYSRQ: | |
| */ | |
| if (sysrq_on()) { | |
| /* 0x0f is the ascii character for ^O */ | |
| if (cevent->data[index] == '\x0f') { | |
| vio_sysrq_pressed = 1; | |
| /* | |
| * continue because we don't want to add | |
| * the sysrq key into the data string. | |
| */ | |
| continue; | |
| } else if (vio_sysrq_pressed) { | |
| handle_sysrq(cevent->data[index], tty); | |
| vio_sysrq_pressed = 0; | |
| /* | |
| * continue because we don't want to add | |
| * the sysrq sequence into the data string. | |
| */ | |
| continue; | |
| } | |
| } | |
| /* | |
| * The sysrq sequence isn't included in this check if | |
| * sysrq is enabled and compiled into the kernel because | |
| * the sequence will never get inserted into the buffer. | |
| * Don't attempt to copy more data into the buffer than we | |
| * have room for because it would fail without indication. | |
| */ | |
| if(tty_insert_flip_char(tty, cevent->data[index], TTY_NORMAL) == 0) { | |
| printk(VIOCONS_KERN_WARN "input buffer overflow!\n"); | |
| break; | |
| } | |
| num_pushed++; | |
| } | |
| if (num_pushed) | |
| tty_flip_buffer_push(tty); | |
| } | |
| /* | |
| * Handle an ack charLpEvent. | |
| */ | |
| static void vioHandleAck(struct HvLpEvent *event) | |
| { | |
| struct viocharlpevent *cevent = (struct viocharlpevent *)event; | |
| unsigned long flags; | |
| u8 port = cevent->virtual_device; | |
| if (port >= VTTY_PORTS) { | |
| printk(VIOCONS_KERN_WARN "data on invalid virtual device\n"); | |
| return; | |
| } | |
| spin_lock_irqsave(&consolelock, flags); | |
| port_info[port].ack = event->xCorrelationToken; | |
| spin_unlock_irqrestore(&consolelock, flags); | |
| if (port_info[port].used) | |
| send_buffers(&port_info[port]); | |
| } | |
| /* | |
| * Handle charLpEvents and route to the appropriate routine | |
| */ | |
| static void vioHandleCharEvent(struct HvLpEvent *event) | |
| { | |
| int charminor; | |
| if (event == NULL) | |
| return; | |
| charminor = event->xSubtype & VIOMINOR_SUBTYPE_MASK; | |
| switch (charminor) { | |
| case viocharopen: | |
| vioHandleOpenEvent(event); | |
| break; | |
| case viocharclose: | |
| vioHandleCloseEvent(event); | |
| break; | |
| case viochardata: | |
| vioHandleData(event); | |
| break; | |
| case viocharack: | |
| vioHandleAck(event); | |
| break; | |
| case viocharconfig: | |
| vioHandleConfig(event); | |
| break; | |
| default: | |
| if (hvlpevent_is_int(event) && hvlpevent_need_ack(event)) { | |
| event->xRc = HvLpEvent_Rc_InvalidSubtype; | |
| HvCallEvent_ackLpEvent(event); | |
| } | |
| } | |
| } | |
| /* | |
| * Send an open event | |
| */ | |
| static int send_open(HvLpIndex remoteLp, void *sem) | |
| { | |
| return HvCallEvent_signalLpEventFast(remoteLp, | |
| HvLpEvent_Type_VirtualIo, | |
| viomajorsubtype_chario | viocharopen, | |
| HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck, | |
| viopath_sourceinst(remoteLp), | |
| viopath_targetinst(remoteLp), | |
| (u64)(unsigned long)sem, VIOVERSION << 16, | |
| 0, 0, 0, 0); | |
| } | |
| static const struct tty_operations serial_ops = { | |
| .open = viotty_open, | |
| .close = viotty_close, | |
| .write = viotty_write, | |
| .put_char = viotty_put_char, | |
| .write_room = viotty_write_room, | |
| .chars_in_buffer = viotty_chars_in_buffer, | |
| .ioctl = viotty_ioctl, | |
| }; | |
| static int __init viocons_init2(void) | |
| { | |
| atomic_t wait_flag; | |
| int rc; | |
| if (!firmware_has_feature(FW_FEATURE_ISERIES)) | |
| return -ENODEV; | |
| /* +2 for fudge */ | |
| rc = viopath_open(HvLpConfig_getPrimaryLpIndex(), | |
| viomajorsubtype_chario, VIOCHAR_WINDOW + 2); | |
| if (rc) | |
| printk(VIOCONS_KERN_WARN "error opening to primary %d\n", rc); | |
| if (viopath_hostLp == HvLpIndexInvalid) | |
| vio_set_hostlp(); | |
| /* | |
| * And if the primary is not the same as the hosting LP, open to the | |
| * hosting lp | |
| */ | |
| if ((viopath_hostLp != HvLpIndexInvalid) && | |
| (viopath_hostLp != HvLpConfig_getPrimaryLpIndex())) { | |
| printk(VIOCONS_KERN_INFO "open path to hosting (%d)\n", | |
| viopath_hostLp); | |
| rc = viopath_open(viopath_hostLp, viomajorsubtype_chario, | |
| VIOCHAR_WINDOW + 2); /* +2 for fudge */ | |
| if (rc) | |
| printk(VIOCONS_KERN_WARN | |
| "error opening to partition %d: %d\n", | |
| viopath_hostLp, rc); | |
| } | |
| if (vio_setHandler(viomajorsubtype_chario, vioHandleCharEvent) < 0) | |
| printk(VIOCONS_KERN_WARN | |
| "error seting handler for console events!\n"); | |
| /* | |
| * First, try to open the console to the hosting lp. | |
| * Wait on a semaphore for the response. | |
| */ | |
| atomic_set(&wait_flag, 0); | |
| if ((viopath_isactive(viopath_hostLp)) && | |
| (send_open(viopath_hostLp, (void *)&wait_flag) == 0)) { | |
| printk(VIOCONS_KERN_INFO "hosting partition %d\n", | |
| viopath_hostLp); | |
| while (atomic_read(&wait_flag) == 0) | |
| mb(); | |
| atomic_set(&wait_flag, 0); | |
| } | |
| /* | |
| * If we don't have an active console, try the primary | |
| */ | |
| if ((!viopath_isactive(port_info[0].lp)) && | |
| (viopath_isactive(HvLpConfig_getPrimaryLpIndex())) && | |
| (send_open(HvLpConfig_getPrimaryLpIndex(), (void *)&wait_flag) | |
| == 0)) { | |
| printk(VIOCONS_KERN_INFO "opening console to primary partition\n"); | |
| while (atomic_read(&wait_flag) == 0) | |
| mb(); | |
| } | |
| /* Initialize the tty_driver structure */ | |
| viotty_driver = alloc_tty_driver(VTTY_PORTS); | |
| viotty_driver->owner = THIS_MODULE; | |
| viotty_driver->driver_name = "vioconsole"; | |
| viotty_driver->name = "tty"; | |
| viotty_driver->name_base = 1; | |
| viotty_driver->major = TTY_MAJOR; | |
| viotty_driver->minor_start = 1; | |
| viotty_driver->type = TTY_DRIVER_TYPE_CONSOLE; | |
| viotty_driver->subtype = 1; | |
| viotty_driver->init_termios = tty_std_termios; | |
| viotty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS; | |
| tty_set_operations(viotty_driver, &serial_ops); | |
| if (tty_register_driver(viotty_driver)) { | |
| printk(VIOCONS_KERN_WARN "couldn't register console driver\n"); | |
| put_tty_driver(viotty_driver); | |
| viotty_driver = NULL; | |
| } | |
| unregister_console(&viocons_early); | |
| register_console(&viocons); | |
| return 0; | |
| } | |
| static int __init viocons_init(void) | |
| { | |
| int i; | |
| if (!firmware_has_feature(FW_FEATURE_ISERIES)) | |
| return -ENODEV; | |
| printk(VIOCONS_KERN_INFO "registering console\n"); | |
| for (i = 0; i < VTTY_PORTS; i++) { | |
| port_info[i].lp = HvLpIndexInvalid; | |
| port_info[i].magic = VIOTTY_MAGIC; | |
| } | |
| HvCall_setLogBufferFormatAndCodepage(HvCall_LogBuffer_ASCII, 437); | |
| add_preferred_console("viocons", 0, NULL); | |
| register_console(&viocons_early); | |
| return 0; | |
| } | |
| console_initcall(viocons_init); | |
| module_init(viocons_init2); |