/* * Copyright (C) 2001 Anton Blanchard , IBM * Copyright (C) 2001 Paul Mackerras , IBM * * 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) any 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 #include #include #include #include #include #include #include #include #include #include #include extern int hvc_count(int *); extern int hvc_get_chars(int index, char *buf, int count); extern int hvc_put_chars(int index, const char *buf, int count); #define HVC_MAJOR 229 #define HVC_MINOR 0 #define MAX_NR_HVC_CONSOLES 4 #define TIMEOUT ((HZ + 99) / 100) struct tty_driver hvc_driver; static int hvc_refcount; static struct tty_struct *hvc_table[MAX_NR_HVC_CONSOLES]; static struct termios *hvc_termios[MAX_NR_HVC_CONSOLES]; static struct termios *hvc_termios_locked[MAX_NR_HVC_CONSOLES]; static int hvc_offset; #ifdef CONFIG_MAGIC_SYSRQ static int sysrq_pressed; #endif #define N_OUTBUF 16 #define __ALIGNED__ __attribute__((__aligned__(8))) struct hvc_struct { spinlock_t lock; int index; struct tty_struct *tty; unsigned int count; int do_wakeup; char outbuf[N_OUTBUF] __ALIGNED__; int n_outbuf; }; struct hvc_struct hvc_struct[MAX_NR_HVC_CONSOLES]; static int hvc_open(struct tty_struct *tty, struct file * filp) { int line = MINOR(tty->device) - tty->driver.minor_start; struct hvc_struct *hp; unsigned long flags; if (line < 0 || line >= MAX_NR_HVC_CONSOLES) return -ENODEV; hp = &hvc_struct[line]; tty->driver_data = hp; spin_lock_irqsave(&hp->lock, flags); hp->tty = tty; hp->count++; spin_unlock_irqrestore(&hp->lock, flags); return 0; } static void hvc_close(struct tty_struct *tty, struct file * filp) { struct hvc_struct *hp = tty->driver_data; unsigned long flags; if (tty_hung_up_p(filp)) return; spin_lock_irqsave(&hp->lock, flags); if (--hp->count == 0) hp->tty = NULL; else if (hp->count < 0) printk(KERN_ERR "hvc_close %lu: oops, count is %d\n", hp - hvc_struct, hp->count); spin_unlock_irqrestore(&hp->lock, flags); } static void hvc_hangup(struct tty_struct *tty) { struct hvc_struct *hp = tty->driver_data; hp->count = 0; hp->tty = NULL; } /* called with hp->lock held */ static void hvc_push(struct hvc_struct *hp) { int n; n = hvc_put_chars(hp->index + hvc_offset, hp->outbuf, hp->n_outbuf); if (n <= 0) { if (n == 0) return; /* throw away output on error; this happens when there is no session connected to the vterm. */ hp->n_outbuf = 0; } else hp->n_outbuf -= n; if (hp->n_outbuf > 0) memmove(hp->outbuf, hp->outbuf + n, hp->n_outbuf); else hp->do_wakeup = 1; } static int hvc_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count) { struct hvc_struct *hp = tty->driver_data; char *p; int todo, written = 0; unsigned long flags; spin_lock_irqsave(&hp->lock, flags); while (count > 0 && (todo = N_OUTBUF - hp->n_outbuf) > 0) { if (todo > count) todo = count; p = hp->outbuf + hp->n_outbuf; if (from_user) { todo -= copy_from_user(p, buf, todo); if (todo == 0) { if (written == 0) written = -EFAULT; break; } } else memcpy(p, buf, todo); count -= todo; buf += todo; hp->n_outbuf += todo; written += todo; hvc_push(hp); } spin_unlock_irqrestore(&hp->lock, flags); return written; } static int hvc_write_room(struct tty_struct *tty) { struct hvc_struct *hp = tty->driver_data; return N_OUTBUF - hp->n_outbuf; } static int hvc_chars_in_buffer(struct tty_struct *tty) { struct hvc_struct *hp = tty->driver_data; return hp->n_outbuf; } static void hvc_poll(int index) { struct hvc_struct *hp = &hvc_struct[index]; struct tty_struct *tty; int i, n; char buf[16] __ALIGNED__; unsigned long flags; spin_lock_irqsave(&hp->lock, flags); if (hp->n_outbuf > 0) hvc_push(hp); tty = hp->tty; if (tty) { for (;;) { if (TTY_FLIPBUF_SIZE - tty->flip.count < sizeof(buf)) break; n = hvc_get_chars(index + hvc_offset, buf, sizeof(buf)); if (n <= 0) break; for (i = 0; i < n; ++i) { #ifdef CONFIG_MAGIC_SYSRQ /* Handle the SysRq Hack */ if (buf[i] == '\x0f') { /* ^O -- should support a sequence */ sysrq_pressed = 1; continue; } else if (sysrq_pressed) { handle_sysrq(buf[i], NULL, NULL, tty); sysrq_pressed = 0; continue; } #endif tty_insert_flip_char(tty, buf[i], 0); } } if (tty->flip.count) tty_schedule_flip(tty); if (hp->do_wakeup) { hp->do_wakeup = 0; if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) (tty->ldisc.write_wakeup)(tty); wake_up_interruptible(&tty->write_wait); } } spin_unlock_irqrestore(&hp->lock, flags); } int khvcd(void *unused) { int i; daemonize(); reparent_to_init(); strcpy(current->comm, "khvcd"); sigfillset(¤t->blocked); for (;;) { for (i = 0; i < MAX_NR_HVC_CONSOLES; ++i) hvc_poll(i); set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(TIMEOUT); } } int __init hvc_init(void) { int i; memset(&hvc_driver, 0, sizeof(struct tty_driver)); hvc_driver.magic = TTY_DRIVER_MAGIC; hvc_driver.driver_name = "hvc"; hvc_driver.name = "hvc/%d"; hvc_driver.major = HVC_MAJOR; hvc_driver.minor_start = HVC_MINOR; hvc_driver.num = hvc_count(&hvc_offset); if (hvc_driver.num > MAX_NR_HVC_CONSOLES) hvc_driver.num = MAX_NR_HVC_CONSOLES; hvc_driver.type = TTY_DRIVER_TYPE_SYSTEM; hvc_driver.init_termios = tty_std_termios; hvc_driver.flags = TTY_DRIVER_REAL_RAW; hvc_driver.refcount = &hvc_refcount; hvc_driver.table = hvc_table; hvc_driver.termios = hvc_termios; hvc_driver.termios_locked = hvc_termios_locked; hvc_driver.open = hvc_open; hvc_driver.close = hvc_close; hvc_driver.write = hvc_write; hvc_driver.hangup = hvc_hangup; hvc_driver.write_room = hvc_write_room; hvc_driver.chars_in_buffer = hvc_chars_in_buffer; for (i = 0; i < hvc_driver.num; i++) { hvc_struct[i].lock = SPIN_LOCK_UNLOCKED; hvc_struct[i].index = i; tty_register_devfs(&hvc_driver, 0, hvc_driver.minor_start + i); } if (tty_register_driver(&hvc_driver)) panic("Couldn't register hvc console driver\n"); if (hvc_driver.num > 0) kernel_thread(khvcd, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL); return 0; } static void __exit hvc_exit(void) { } void hvc_console_print(struct console *co, const char *b, unsigned count) { char c[16] __ALIGNED__; unsigned i, n; int r, donecr = 0; i = n = 0; while (count > 0 || i > 0) { if (count > 0 && i < sizeof(c)) { if (b[n] == '\n' && !donecr) { c[i++] = '\r'; donecr = 1; } else { c[i++] = b[n++]; donecr = 0; --count; } } else { r = hvc_put_chars(co->index + hvc_offset, c, i); if (r < 0) { /* throw away chars on error */ i = 0; } else if (r > 0) { i -= r; if (i > 0) memmove(c, c+r, i); } } } } static kdev_t hvc_console_device(struct console *c) { return MKDEV(HVC_MAJOR, HVC_MINOR + c->index); } int hvc_wait_for_keypress(struct console *co) { char c[16] __ALIGNED__; while (hvc_get_chars(co->index, &c[0], 1) < 1) ; return 0; } static int __init hvc_console_setup(struct console *co, char *options) { if (co->index < 0 || co->index >= MAX_NR_HVC_CONSOLES || co->index >= hvc_count(&hvc_offset)) return -1; return 0; } struct console hvc_con_driver = { name: "hvc", write: hvc_console_print, device: hvc_console_device, setup: hvc_console_setup, flags: CON_PRINTBUFFER, index: -1, }; int __init hvc_console_init(void) { register_console(&hvc_con_driver); return 0; } module_init(hvc_init); module_exit(hvc_exit);