diff options
Diffstat (limited to 'arch/um/drivers/line.c')
-rw-r--r-- | arch/um/drivers/line.c | 681 |
1 files changed, 681 insertions, 0 deletions
diff --git a/arch/um/drivers/line.c b/arch/um/drivers/line.c new file mode 100644 index 0000000..6924f27 --- /dev/null +++ b/arch/um/drivers/line.c @@ -0,0 +1,681 @@ +/* + * Copyright (C) 2001, 2002 Jeff Dike (jdike@karaya.com) + * Licensed under the GPL + */ + +#include "linux/sched.h" +#include "linux/slab.h" +#include "linux/list.h" +#include "linux/kd.h" +#include "linux/interrupt.h" +#include "linux/devfs_fs_kernel.h" +#include "asm/uaccess.h" +#include "chan_kern.h" +#include "irq_user.h" +#include "line.h" +#include "kern.h" +#include "user_util.h" +#include "kern_util.h" +#include "os.h" +#include "irq_kern.h" + +#define LINE_BUFSIZE 4096 + +static irqreturn_t line_interrupt(int irq, void *data, struct pt_regs *unused) +{ + struct tty_struct *tty = data; + struct line *line = tty->driver_data; + + if (line) + chan_interrupt(&line->chan_list, &line->task, tty, irq); + return IRQ_HANDLED; +} + +static void line_timer_cb(void *arg) +{ + struct tty_struct *tty = arg; + struct line *line = tty->driver_data; + + line_interrupt(line->driver->read_irq, arg, NULL); +} + +static int write_room(struct line *dev) +{ + int n; + + if (dev->buffer == NULL) + return (LINE_BUFSIZE - 1); + + n = dev->head - dev->tail; + if (n <= 0) + n = LINE_BUFSIZE + n; + return (n - 1); +} + +static int buffer_data(struct line *line, const char *buf, int len) +{ + int end, room; + + if(line->buffer == NULL){ + line->buffer = kmalloc(LINE_BUFSIZE, GFP_ATOMIC); + if (line->buffer == NULL) { + printk("buffer_data - atomic allocation failed\n"); + return(0); + } + line->head = line->buffer; + line->tail = line->buffer; + } + + room = write_room(line); + len = (len > room) ? room : len; + + end = line->buffer + LINE_BUFSIZE - line->tail; + if(len < end){ + memcpy(line->tail, buf, len); + line->tail += len; + } + else { + memcpy(line->tail, buf, end); + buf += end; + memcpy(line->buffer, buf, len - end); + line->tail = line->buffer + len - end; + } + + return(len); +} + +static int flush_buffer(struct line *line) +{ + int n, count; + + if ((line->buffer == NULL) || (line->head == line->tail)) + return(1); + + if (line->tail < line->head) { + count = line->buffer + LINE_BUFSIZE - line->head; + n = write_chan(&line->chan_list, line->head, count, + line->driver->write_irq); + if (n < 0) + return(n); + if (n == count) + line->head = line->buffer; + else { + line->head += n; + return(0); + } + } + + count = line->tail - line->head; + n = write_chan(&line->chan_list, line->head, count, + line->driver->write_irq); + if(n < 0) return(n); + + line->head += n; + return(line->head == line->tail); +} + +int line_write(struct tty_struct *tty, const unsigned char *buf, int len) +{ + struct line *line = tty->driver_data; + unsigned long flags; + int n, err, ret = 0; + + if(tty->stopped) return 0; + + down(&line->sem); + if(line->head != line->tail){ + local_irq_save(flags); + ret = buffer_data(line, buf, len); + err = flush_buffer(line); + local_irq_restore(flags); + if(err <= 0 && (err != -EAGAIN || !ret)) + ret = err; + } + else { + n = write_chan(&line->chan_list, buf, len, + line->driver->write_irq); + if(n < 0){ + ret = n; + goto out_up; + } + + len -= n; + ret += n; + if(len > 0) + ret += buffer_data(line, buf + n, len); + } + out_up: + up(&line->sem); + return(ret); +} + +void line_put_char(struct tty_struct *tty, unsigned char ch) +{ + line_write(tty, &ch, sizeof(ch)); +} + +void line_set_termios(struct tty_struct *tty, struct termios * old) +{ + /* nothing */ +} + +int line_chars_in_buffer(struct tty_struct *tty) +{ + return 0; +} + +static struct { + int cmd; + char *level; + char *name; +} tty_ioctls[] = { + /* don't print these, they flood the log ... */ + { TCGETS, NULL, "TCGETS" }, + { TCSETS, NULL, "TCSETS" }, + { TCSETSW, NULL, "TCSETSW" }, + { TCFLSH, NULL, "TCFLSH" }, + { TCSBRK, NULL, "TCSBRK" }, + + /* general tty stuff */ + { TCSETSF, KERN_DEBUG, "TCSETSF" }, + { TCGETA, KERN_DEBUG, "TCGETA" }, + { TIOCMGET, KERN_DEBUG, "TIOCMGET" }, + { TCSBRKP, KERN_DEBUG, "TCSBRKP" }, + { TIOCMSET, KERN_DEBUG, "TIOCMSET" }, + + /* linux-specific ones */ + { TIOCLINUX, KERN_INFO, "TIOCLINUX" }, + { KDGKBMODE, KERN_INFO, "KDGKBMODE" }, + { KDGKBTYPE, KERN_INFO, "KDGKBTYPE" }, + { KDSIGACCEPT, KERN_INFO, "KDSIGACCEPT" }, +}; + +int line_ioctl(struct tty_struct *tty, struct file * file, + unsigned int cmd, unsigned long arg) +{ + int ret; + int i; + + ret = 0; + switch(cmd) { +#ifdef TIOCGETP + case TIOCGETP: + case TIOCSETP: + case TIOCSETN: +#endif +#ifdef TIOCGETC + case TIOCGETC: + case TIOCSETC: +#endif +#ifdef TIOCGLTC + case TIOCGLTC: + case TIOCSLTC: +#endif + case TCGETS: + case TCSETSF: + case TCSETSW: + case TCSETS: + case TCGETA: + case TCSETAF: + case TCSETAW: + case TCSETA: + case TCXONC: + case TCFLSH: + case TIOCOUTQ: + case TIOCINQ: + case TIOCGLCKTRMIOS: + case TIOCSLCKTRMIOS: + case TIOCPKT: + case TIOCGSOFTCAR: + case TIOCSSOFTCAR: + return -ENOIOCTLCMD; +#if 0 + case TCwhatever: + /* do something */ + break; +#endif + default: + for (i = 0; i < ARRAY_SIZE(tty_ioctls); i++) + if (cmd == tty_ioctls[i].cmd) + break; + if (i < ARRAY_SIZE(tty_ioctls)) { + if (NULL != tty_ioctls[i].level) + printk("%s%s: %s: ioctl %s called\n", + tty_ioctls[i].level, __FUNCTION__, + tty->name, tty_ioctls[i].name); + } else { + printk(KERN_ERR "%s: %s: unknown ioctl: 0x%x\n", + __FUNCTION__, tty->name, cmd); + } + ret = -ENOIOCTLCMD; + break; + } + return(ret); +} + +static irqreturn_t line_write_interrupt(int irq, void *data, + struct pt_regs *unused) +{ + struct tty_struct *tty = data; + struct line *line = tty->driver_data; + int err; + + err = flush_buffer(line); + if(err == 0) + return(IRQ_NONE); + else if(err < 0){ + line->head = line->buffer; + line->tail = line->buffer; + } + + if(tty == NULL) + return(IRQ_NONE); + + if(test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags) && + (tty->ldisc.write_wakeup != NULL)) + (tty->ldisc.write_wakeup)(tty); + + /* BLOCKING mode + * In blocking mode, everything sleeps on tty->write_wait. + * Sleeping in the console driver would break non-blocking + * writes. + */ + + if(waitqueue_active(&tty->write_wait)) + wake_up_interruptible(&tty->write_wait); + return(IRQ_HANDLED); +} + +int line_setup_irq(int fd, int input, int output, struct tty_struct *tty) +{ + struct line *line = tty->driver_data; + struct line_driver *driver = line->driver; + int err = 0, flags = SA_INTERRUPT | SA_SHIRQ | SA_SAMPLE_RANDOM; + + if(input) err = um_request_irq(driver->read_irq, fd, IRQ_READ, + line_interrupt, flags, + driver->read_irq_name, tty); + if(err) return(err); + if(output) err = um_request_irq(driver->write_irq, fd, IRQ_WRITE, + line_write_interrupt, flags, + driver->write_irq_name, tty); + line->have_irq = 1; + return(err); +} + +void line_disable(struct tty_struct *tty, int current_irq) +{ + struct line *line = tty->driver_data; + + if(!line->have_irq) + return; + + if(line->driver->read_irq == current_irq) + free_irq_later(line->driver->read_irq, tty); + else { + free_irq_by_irq_and_dev(line->driver->read_irq, tty); + free_irq(line->driver->read_irq, tty); + } + + if(line->driver->write_irq == current_irq) + free_irq_later(line->driver->write_irq, tty); + else { + free_irq_by_irq_and_dev(line->driver->write_irq, tty); + free_irq(line->driver->write_irq, tty); + } + + line->have_irq = 0; +} + +int line_open(struct line *lines, struct tty_struct *tty, + struct chan_opts *opts) +{ + struct line *line; + int err = 0; + + line = &lines[tty->index]; + tty->driver_data = line; + + down(&line->sem); + if (tty->count == 1) { + if (!line->valid) { + err = -ENODEV; + goto out; + } + if (list_empty(&line->chan_list)) { + err = parse_chan_pair(line->init_str, &line->chan_list, + line->init_pri, tty->index, opts); + if(err) goto out; + err = open_chan(&line->chan_list); + if(err) goto out; + } + enable_chan(&line->chan_list, tty); + INIT_WORK(&line->task, line_timer_cb, tty); + } + + if(!line->sigio){ + chan_enable_winch(&line->chan_list, tty); + line->sigio = 1; + } + chan_window_size(&line->chan_list, &tty->winsize.ws_row, + &tty->winsize.ws_col); + line->count++; + +out: + up(&line->sem); + return(err); +} + +void line_close(struct tty_struct *tty, struct file * filp) +{ + struct line *line = tty->driver_data; + + down(&line->sem); + line->count--; + if (tty->count == 1) { + line_disable(tty, -1); + tty->driver_data = NULL; + } + up(&line->sem); +} + +void close_lines(struct line *lines, int nlines) +{ + int i; + + for(i = 0; i < nlines; i++) + close_chan(&lines[i].chan_list); +} + +int line_setup(struct line *lines, int num, char *init, int all_allowed) +{ + int i, n; + char *end; + + if(*init == '=') n = -1; + else { + n = simple_strtoul(init, &end, 0); + if(*end != '='){ + printk(KERN_ERR "line_setup failed to parse \"%s\"\n", + init); + return(0); + } + init = end; + } + init++; + if((n >= 0) && (n >= num)){ + printk("line_setup - %d out of range ((0 ... %d) allowed)\n", + n, num - 1); + return(0); + } + else if (n >= 0){ + if (lines[n].count > 0) { + printk("line_setup - device %d is open\n", n); + return(0); + } + if (lines[n].init_pri <= INIT_ONE){ + lines[n].init_pri = INIT_ONE; + if (!strcmp(init, "none")) + lines[n].valid = 0; + else { + lines[n].init_str = init; + lines[n].valid = 1; + } + } + } + else if(!all_allowed){ + printk("line_setup - can't configure all devices from " + "mconsole\n"); + return(0); + } + else { + for(i = 0; i < num; i++){ + if(lines[i].init_pri <= INIT_ALL){ + lines[i].init_pri = INIT_ALL; + if(!strcmp(init, "none")) lines[i].valid = 0; + else { + lines[i].init_str = init; + lines[i].valid = 1; + } + } + } + } + return(1); +} + +int line_config(struct line *lines, int num, char *str) +{ + char *new = uml_strdup(str); + + if(new == NULL){ + printk("line_config - uml_strdup failed\n"); + return(-ENOMEM); + } + return(!line_setup(lines, num, new, 0)); +} + +int line_get_config(char *name, struct line *lines, int num, char *str, + int size, char **error_out) +{ + struct line *line; + char *end; + int dev, n = 0; + + dev = simple_strtoul(name, &end, 0); + if((*end != '\0') || (end == name)){ + *error_out = "line_get_config failed to parse device number"; + return(0); + } + + if((dev < 0) || (dev >= num)){ + *error_out = "device number of of range"; + return(0); + } + + line = &lines[dev]; + + down(&line->sem); + if(!line->valid) + CONFIG_CHUNK(str, size, n, "none", 1); + else if(line->count == 0) + CONFIG_CHUNK(str, size, n, line->init_str, 1); + else n = chan_config_string(&line->chan_list, str, size, error_out); + up(&line->sem); + + return(n); +} + +int line_remove(struct line *lines, int num, char *str) +{ + char config[sizeof("conxxxx=none\0")]; + + sprintf(config, "%s=none", str); + return(!line_setup(lines, num, config, 0)); +} + +int line_write_room(struct tty_struct *tty) +{ + struct line *dev = tty->driver_data; + int room; + + if (tty->stopped) + return 0; + room = write_room(dev); + if (0 == room) + printk(KERN_DEBUG "%s: %s: no room left in buffer\n", + __FUNCTION__,tty->name); + return room; +} + +struct tty_driver *line_register_devfs(struct lines *set, + struct line_driver *line_driver, + struct tty_operations *ops, struct line *lines, + int nlines) +{ + int i; + struct tty_driver *driver = alloc_tty_driver(nlines); + + if (!driver) + return NULL; + + driver->driver_name = line_driver->name; + driver->name = line_driver->device_name; + driver->devfs_name = line_driver->devfs_name; + driver->major = line_driver->major; + driver->minor_start = line_driver->minor_start; + driver->type = line_driver->type; + driver->subtype = line_driver->subtype; + driver->flags = TTY_DRIVER_REAL_RAW; + driver->init_termios = tty_std_termios; + tty_set_operations(driver, ops); + + if (tty_register_driver(driver)) { + printk("%s: can't register %s driver\n", + __FUNCTION__,line_driver->name); + put_tty_driver(driver); + return NULL; + } + + for(i = 0; i < nlines; i++){ + if(!lines[i].valid) + tty_unregister_device(driver, i); + } + + mconsole_register_dev(&line_driver->mc); + return driver; +} + +void lines_init(struct line *lines, int nlines) +{ + struct line *line; + int i; + + for(i = 0; i < nlines; i++){ + line = &lines[i]; + INIT_LIST_HEAD(&line->chan_list); + sema_init(&line->sem, 1); + if(line->init_str != NULL){ + line->init_str = uml_strdup(line->init_str); + if(line->init_str == NULL) + printk("lines_init - uml_strdup returned " + "NULL\n"); + } + } +} + +struct winch { + struct list_head list; + int fd; + int tty_fd; + int pid; + struct tty_struct *tty; +}; + +irqreturn_t winch_interrupt(int irq, void *data, struct pt_regs *unused) +{ + struct winch *winch = data; + struct tty_struct *tty; + struct line *line; + int err; + char c; + + if(winch->fd != -1){ + err = generic_read(winch->fd, &c, NULL); + if(err < 0){ + if(err != -EAGAIN){ + printk("winch_interrupt : read failed, " + "errno = %d\n", -err); + printk("fd %d is losing SIGWINCH support\n", + winch->tty_fd); + return(IRQ_HANDLED); + } + goto out; + } + } + tty = winch->tty; + if (tty != NULL) { + line = tty->driver_data; + chan_window_size(&line->chan_list, + &tty->winsize.ws_row, + &tty->winsize.ws_col); + kill_pg(tty->pgrp, SIGWINCH, 1); + } + out: + if(winch->fd != -1) + reactivate_fd(winch->fd, WINCH_IRQ); + return(IRQ_HANDLED); +} + +DECLARE_MUTEX(winch_handler_sem); +LIST_HEAD(winch_handlers); + +void register_winch_irq(int fd, int tty_fd, int pid, struct tty_struct *tty) +{ + struct winch *winch; + + down(&winch_handler_sem); + winch = kmalloc(sizeof(*winch), GFP_KERNEL); + if (winch == NULL) { + printk("register_winch_irq - kmalloc failed\n"); + goto out; + } + *winch = ((struct winch) { .list = LIST_HEAD_INIT(winch->list), + .fd = fd, + .tty_fd = tty_fd, + .pid = pid, + .tty = tty }); + list_add(&winch->list, &winch_handlers); + if(um_request_irq(WINCH_IRQ, fd, IRQ_READ, winch_interrupt, + SA_INTERRUPT | SA_SHIRQ | SA_SAMPLE_RANDOM, + "winch", winch) < 0) + printk("register_winch_irq - failed to register IRQ\n"); + out: + up(&winch_handler_sem); +} + +static void winch_cleanup(void) +{ + struct list_head *ele; + struct winch *winch; + + list_for_each(ele, &winch_handlers){ + winch = list_entry(ele, struct winch, list); + if(winch->fd != -1){ + deactivate_fd(winch->fd, WINCH_IRQ); + os_close_file(winch->fd); + } + if(winch->pid != -1) + os_kill_process(winch->pid, 1); + } +} +__uml_exitcall(winch_cleanup); + +char *add_xterm_umid(char *base) +{ + char *umid, *title; + int len; + + umid = get_umid(1); + if(umid == NULL) return(base); + + len = strlen(base) + strlen(" ()") + strlen(umid) + 1; + title = kmalloc(len, GFP_KERNEL); + if(title == NULL){ + printk("Failed to allocate buffer for xterm title\n"); + return(base); + } + + snprintf(title, len, "%s (%s)", base, umid); + return(title); +} + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-file-style: "linux" + * End: + */ |