viocons.c
来自「linux 内核源代码」· C语言 代码 · 共 1,168 行 · 第 1/2 页
C
1,168 行
/* -*- 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 <sfr@au1.ibm.com> * * (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);
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?