fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / arch / um / drivers / chan_kern.c
index 9e45a08..7d4190e 100644 (file)
@@ -1,4 +1,4 @@
-/* 
+/*
  * Copyright (C) 2000, 2001, 2002 Jeff Dike (jdike@karaya.com)
  * Licensed under the GPL
  */
@@ -8,6 +8,7 @@
 #include <linux/list.h>
 #include <linux/slab.h>
 #include <linux/tty.h>
+#include <linux/string.h>
 #include <linux/tty_flip.h>
 #include <asm/irq.h>
 #include "chan_kern.h"
 #include "irq_user.h"
 #include "sigio.h"
 #include "line.h"
+#include "os.h"
+
+/* XXX: could well be moved to somewhere else, if needed. */
+static int my_printf(const char * fmt, ...)
+       __attribute__ ((format (printf, 1, 2)));
+
+static int my_printf(const char * fmt, ...)
+{
+       /* Yes, can be called on atomic context.*/
+       char *buf = kmalloc(4096, GFP_ATOMIC);
+       va_list args;
+       int r;
+
+       if (!buf) {
+               /* We print directly fmt.
+                * Yes, yes, yes, feel free to complain. */
+               r = strlen(fmt);
+       } else {
+               va_start(args, fmt);
+               r = vsprintf(buf, fmt, args);
+               va_end(args);
+               fmt = buf;
+       }
+
+       if (r)
+               r = os_write_file(1, fmt, r);
+       return r;
+
+}
+
+#ifdef CONFIG_NOCONFIG_CHAN
+/* Despite its name, there's no added trailing newline. */
+static int my_puts(const char * buf)
+{
+       return os_write_file(1, buf, strlen(buf));
+}
 
 static void *not_configged_init(char *str, int device, struct chan_opts *opts)
 {
-       printk(KERN_ERR "Using a channel type which is configured out of "
+       my_puts("Using a channel type which is configured out of "
               "UML\n");
-       return(NULL);
+       return NULL;
 }
 
 static int not_configged_open(int input, int output, int primary, void *data,
                              char **dev_out)
 {
-       printk(KERN_ERR "Using a channel type which is configured out of "
+       my_puts("Using a channel type which is configured out of "
               "UML\n");
-       return(-ENODEV);
+       return -ENODEV;
 }
 
 static void not_configged_close(int fd, void *data)
 {
-       printk(KERN_ERR "Using a channel type which is configured out of "
+       my_puts("Using a channel type which is configured out of "
               "UML\n");
 }
 
 static int not_configged_read(int fd, char *c_out, void *data)
 {
-       printk(KERN_ERR "Using a channel type which is configured out of "
+       my_puts("Using a channel type which is configured out of "
               "UML\n");
-       return(-EIO);
+       return -EIO;
 }
 
 static int not_configged_write(int fd, const char *buf, int len, void *data)
 {
-       printk(KERN_ERR "Using a channel type which is configured out of "
+       my_puts("Using a channel type which is configured out of "
               "UML\n");
-       return(-EIO);
+       return -EIO;
 }
 
-static int not_configged_console_write(int fd, const char *buf, int len,
-                                      void *data)
+static int not_configged_console_write(int fd, const char *buf, int len)
 {
-       printk(KERN_ERR "Using a channel type which is configured out of "
+       my_puts("Using a channel type which is configured out of "
               "UML\n");
-       return(-EIO);
+       return -EIO;
 }
 
 static int not_configged_window_size(int fd, void *data, unsigned short *rows,
                                     unsigned short *cols)
 {
-       printk(KERN_ERR "Using a channel type which is configured out of "
+       my_puts("Using a channel type which is configured out of "
               "UML\n");
-       return(-ENODEV);
+       return -ENODEV;
 }
 
 static void not_configged_free(void *data)
 {
-       printk(KERN_ERR "Using a channel type which is configured out of "
+       my_puts("Using a channel type which is configured out of "
               "UML\n");
 }
 
-static struct chan_ops not_configged_ops = {
+static const struct chan_ops not_configged_ops = {
        .init           = not_configged_init,
        .open           = not_configged_open,
        .close          = not_configged_close,
@@ -85,6 +121,55 @@ static struct chan_ops not_configged_ops = {
        .free           = not_configged_free,
        .winch          = 0,
 };
+#endif /* CONFIG_NOCONFIG_CHAN */
+
+void generic_close(int fd, void *unused)
+{
+       os_close_file(fd);
+}
+
+int generic_read(int fd, char *c_out, void *unused)
+{
+       int n;
+
+       n = os_read_file(fd, c_out, sizeof(*c_out));
+
+       if(n == -EAGAIN)
+               return 0;
+       else if(n == 0)
+               return -EIO;
+       return n;
+}
+
+/* XXX Trivial wrapper around os_write_file */
+
+int generic_write(int fd, const char *buf, int n, void *unused)
+{
+       return os_write_file(fd, buf, n);
+}
+
+int generic_window_size(int fd, void *unused, unsigned short *rows_out,
+                       unsigned short *cols_out)
+{
+       int rows, cols;
+       int ret;
+
+       ret = os_window_size(fd, &rows, &cols);
+       if(ret < 0)
+               return ret;
+
+       ret = ((*rows_out != rows) || (*cols_out != cols));
+
+       *rows_out = rows;
+       *cols_out = cols;
+
+       return ret;
+}
+
+void generic_free(void *data)
+{
+       kfree(data);
+}
 
 static void tty_receive_char(struct tty_struct *tty, char ch)
 {
@@ -101,25 +186,26 @@ static void tty_receive_char(struct tty_struct *tty, char ch)
                }
        }
 
-       if((tty->flip.flag_buf_ptr == NULL) || 
-          (tty->flip.char_buf_ptr == NULL))
-               return;
        tty_insert_flip_char(tty, ch, TTY_NORMAL);
 }
 
-static int open_one_chan(struct chan *chan, int input, int output, int primary)
+static int open_one_chan(struct chan *chan)
 {
        int fd;
 
-       if(chan->opened) return(0);
-       if(chan->ops->open == NULL) fd = 0;
-       else fd = (*chan->ops->open)(input, output, primary, chan->data,
-                                    &chan->dev);
-       if(fd < 0) return(fd);
+       if(chan->opened)
+               return 0;
+
+       if(chan->ops->open == NULL)
+               fd = 0;
+       else fd = (*chan->ops->open)(chan->input, chan->output, chan->primary,
+                                    chan->data, &chan->dev);
+       if(fd < 0)
+               return fd;
        chan->fd = fd;
 
        chan->opened = 1;
-       return(0);
+       return 0;
 }
 
 int open_chan(struct list_head *chans)
@@ -130,14 +216,14 @@ int open_chan(struct list_head *chans)
 
        list_for_each(ele, chans){
                chan = list_entry(ele, struct chan, list);
-               ret = open_one_chan(chan, chan->input, chan->output,
-                                   chan->primary);
-               if(chan->primary) err = ret;
+               ret = open_one_chan(chan);
+               if(chan->primary)
+                       err = ret;
        }
-       return(err);
+       return err;
 }
 
-void chan_enable_winch(struct list_head *chans, void *line)
+void chan_enable_winch(struct list_head *chans, struct tty_struct *tty)
 {
        struct list_head *ele;
        struct chan *chan;
@@ -145,28 +231,72 @@ void chan_enable_winch(struct list_head *chans, void *line)
        list_for_each(ele, chans){
                chan = list_entry(ele, struct chan, list);
                if(chan->primary && chan->output && chan->ops->winch){
-                       register_winch(chan->fd, line);
+                       register_winch(chan->fd, tty);
                        return;
                }
        }
 }
 
-void enable_chan(struct list_head *chans, void *data)
+void enable_chan(struct line *line)
 {
        struct list_head *ele;
        struct chan *chan;
 
-       list_for_each(ele, chans){
+       list_for_each(ele, &line->chan_list){
                chan = list_entry(ele, struct chan, list);
-               if(!chan->opened) continue;
+               if(open_one_chan(chan))
+                       continue;
 
-               line_setup_irq(chan->fd, chan->input, chan->output, data);
+               if(chan->enabled)
+                       continue;
+               line_setup_irq(chan->fd, chan->input, chan->output, line,
+                              chan);
+               chan->enabled = 1;
        }
 }
 
-void close_chan(struct list_head *chans)
+static LIST_HEAD(irqs_to_free);
+
+void free_irqs(void)
+{
+       struct chan *chan;
+
+       while(!list_empty(&irqs_to_free)){
+               chan = list_entry(irqs_to_free.next, struct chan, free_list);
+               list_del(&chan->free_list);
+
+               if(chan->input)
+                       free_irq(chan->line->driver->read_irq, chan);
+               if(chan->output)
+                       free_irq(chan->line->driver->write_irq, chan);
+               chan->enabled = 0;
+       }
+}
+
+static void close_one_chan(struct chan *chan, int delay_free_irq)
+{
+       if(!chan->opened)
+               return;
+
+       if(delay_free_irq){
+               list_add(&chan->free_list, &irqs_to_free);
+       }
+       else {
+               if(chan->input)
+                       free_irq(chan->line->driver->read_irq, chan);
+               if(chan->output)
+                       free_irq(chan->line->driver->write_irq, chan);
+               chan->enabled = 0;
+       }
+       if(chan->ops->close != NULL)
+               (*chan->ops->close)(chan->fd, chan->data);
+
+       chan->opened = 0;
+       chan->fd = -1;
+}
+
+void close_chan(struct list_head *chans, int delay_free_irq)
 {
-       struct list_head *ele;
        struct chan *chan;
 
        /* Close in reverse order as open in case more than one of them
@@ -174,36 +304,56 @@ void close_chan(struct list_head *chans)
         * state.  Then, the first one opened will have the original state,
         * so it must be the last closed.
         */
-        for(ele = chans->prev; ele != chans; ele = ele->prev){
-                chan = list_entry(ele, struct chan, list);
-               if(!chan->opened) continue;
-               if(chan->ops->close != NULL)
-                       (*chan->ops->close)(chan->fd, chan->data);
-               chan->opened = 0;
-               chan->fd = -1;
+       list_for_each_entry_reverse(chan, chans, list) {
+               close_one_chan(chan, delay_free_irq);
        }
 }
 
-int write_chan(struct list_head *chans, const char *buf, int len, 
-              int write_irq)
+void deactivate_chan(struct list_head *chans, int irq)
+{
+       struct list_head *ele;
+
+       struct chan *chan;
+       list_for_each(ele, chans) {
+               chan = list_entry(ele, struct chan, list);
+
+               if(chan->enabled && chan->input)
+                       deactivate_fd(chan->fd, irq);
+       }
+}
+
+void reactivate_chan(struct list_head *chans, int irq)
 {
        struct list_head *ele;
        struct chan *chan;
+
+       list_for_each(ele, chans) {
+               chan = list_entry(ele, struct chan, list);
+
+               if(chan->enabled && chan->input)
+                       reactivate_fd(chan->fd, irq);
+       }
+}
+
+int write_chan(struct list_head *chans, const char *buf, int len,
+              int write_irq)
+{
+       struct list_head *ele;
+       struct chan *chan = NULL;
        int n, ret = 0;
 
-       list_for_each(ele, chans){
+       list_for_each(ele, chans) {
                chan = list_entry(ele, struct chan, list);
-               if(!chan->output || (chan->ops->write == NULL)) continue;
+               if (!chan->output || (chan->ops->write == NULL))
+                       continue;
                n = chan->ops->write(chan->fd, buf, len, chan->data);
-               if(chan->primary){
+               if (chan->primary) {
                        ret = n;
-                       if((ret == -EAGAIN) || ((ret >= 0) && (ret < len))){
+                       if ((ret == -EAGAIN) || ((ret >= 0) && (ret < len)))
                                reactivate_fd(chan->fd, write_irq);
-                               if(ret == -EAGAIN) ret = 0;
-                       }
                }
        }
-       return(ret);
+       return ret;
 }
 
 int console_write_chan(struct list_head *chans, const char *buf, int len)
@@ -216,10 +366,23 @@ int console_write_chan(struct list_head *chans, const char *buf, int len)
                chan = list_entry(ele, struct chan, list);
                if(!chan->output || (chan->ops->console_write == NULL))
                        continue;
-               n = chan->ops->console_write(chan->fd, buf, len, chan->data);
+               n = chan->ops->console_write(chan->fd, buf, len);
                if(chan->primary) ret = n;
        }
-       return(ret);
+       return ret;
+}
+
+int console_open_chan(struct line *line, struct console *co,
+                     const struct chan_opts *opts)
+{
+       int err;
+
+       err = open_chan(&line->chan_list);
+       if(err)
+               return err;
+
+       printk("Console initialized on /dev/%s%d\n",co->name,co->index);
+       return 0;
 }
 
 int chan_window_size(struct list_head *chans, unsigned short *rows_out,
@@ -231,32 +394,36 @@ int chan_window_size(struct list_head *chans, unsigned short *rows_out,
        list_for_each(ele, chans){
                chan = list_entry(ele, struct chan, list);
                if(chan->primary){
-                       if(chan->ops->window_size == NULL) return(0);
-                       return(chan->ops->window_size(chan->fd, chan->data,
-                                                     rows_out, cols_out));
+                       if(chan->ops->window_size == NULL)
+                               return 0;
+                       return chan->ops->window_size(chan->fd, chan->data,
+                                                     rows_out, cols_out);
                }
        }
-       return(0);
+       return 0;
 }
 
-void free_one_chan(struct chan *chan)
+static void free_one_chan(struct chan *chan, int delay_free_irq)
 {
        list_del(&chan->list);
+
+       close_one_chan(chan, delay_free_irq);
+
        if(chan->ops->free != NULL)
                (*chan->ops->free)(chan->data);
-       free_irq_by_fd(chan->fd);
+
        if(chan->primary && chan->output) ignore_sigio_fd(chan->fd);
        kfree(chan);
 }
 
-void free_chan(struct list_head *chans)
+static void free_chan(struct list_head *chans, int delay_free_irq)
 {
        struct list_head *ele, *next;
        struct chan *chan;
 
        list_for_each_safe(ele, next, chans){
                chan = list_entry(ele, struct chan, list);
-               free_one_chan(chan);
+               free_one_chan(chan, delay_free_irq);
        }
 }
 
@@ -265,20 +432,25 @@ static int one_chan_config_string(struct chan *chan, char *str, int size,
 {
        int n = 0;
 
+       if(chan == NULL){
+               CONFIG_CHUNK(str, size, n, "none", 1);
+               return n;
+       }
+
        CONFIG_CHUNK(str, size, n, chan->ops->type, 0);
 
        if(chan->dev == NULL){
                CONFIG_CHUNK(str, size, n, "", 1);
-               return(n);
+               return n;
        }
 
        CONFIG_CHUNK(str, size, n, ":", 0);
        CONFIG_CHUNK(str, size, n, chan->dev, 0);
 
-       return(n);
+       return n;
 }
 
-static int chan_pair_config_string(struct chan *in, struct chan *out, 
+static int chan_pair_config_string(struct chan *in, struct chan *out,
                                   char *str, int size, char **error_out)
 {
        int n;
@@ -289,7 +461,7 @@ static int chan_pair_config_string(struct chan *in, struct chan *out,
 
        if(in == out){
                CONFIG_CHUNK(str, size, n, "", 1);
-               return(n);
+               return n;
        }
 
        CONFIG_CHUNK(str, size, n, ",", 1);
@@ -298,10 +470,10 @@ static int chan_pair_config_string(struct chan *in, struct chan *out,
        size -= n;
        CONFIG_CHUNK(str, size, n, "", 1);
 
-       return(n);
+       return n;
 }
 
-int chan_config_string(struct list_head *chans, char *str, int size, 
+int chan_config_string(struct list_head *chans, char *str, int size,
                       char **error_out)
 {
        struct list_head *ele;
@@ -317,20 +489,16 @@ int chan_config_string(struct list_head *chans, char *str, int size,
                        out = chan;
        }
 
-       return(chan_pair_config_string(in, out, str, size, error_out));
+       return chan_pair_config_string(in, out, str, size, error_out);
 }
 
 struct chan_type {
        char *key;
-       struct chan_ops *ops;
+       const struct chan_ops *ops;
 };
 
-struct chan_type chan_table[] = {
-#ifdef CONFIG_FD_CHAN
+static const struct chan_type chan_table[] = {
        { "fd", &fd_ops },
-#else
-       { "fd", &not_configged_ops },
-#endif
 
 #ifdef CONFIG_NULL_CHAN
        { "null", &null_ops },
@@ -365,18 +533,18 @@ struct chan_type chan_table[] = {
 #endif
 };
 
-static struct chan *parse_chan(char *str, int pri, int device, 
-                              struct chan_opts *opts)
+static struct chan *parse_chan(struct line *line, char *str, int device,
+                              const struct chan_opts *opts)
 {
-       struct chan_type *entry;
-       struct chan_ops *ops;
+       const struct chan_type *entry;
+       const struct chan_ops *ops;
        struct chan *chan;
        void *data;
        int i;
 
        ops = NULL;
        data = NULL;
-       for(i = 0; i < sizeof(chan_table)/sizeof(chan_table[0]); i++){
+       for(i = 0; i < ARRAY_SIZE(chan_table); i++){
                entry = &chan_table[i];
                if(!strncmp(str, entry->key, strlen(entry->key))){
                        ops = entry->ops;
@@ -385,63 +553,76 @@ static struct chan *parse_chan(char *str, int pri, int device,
                }
        }
        if(ops == NULL){
-               printk(KERN_ERR "parse_chan couldn't parse \"%s\"\n", 
+               my_printf("parse_chan couldn't parse \"%s\"\n",
                       str);
-               return(NULL);
+               return NULL;
        }
-       if(ops->init == NULL) return(NULL); 
+       if(ops->init == NULL)
+               return NULL;
        data = (*ops->init)(str, device, opts);
-       if(data == NULL) return(NULL);
+       if(data == NULL)
+               return NULL;
 
-       chan = kmalloc(sizeof(*chan), GFP_KERNEL);
-       if(chan == NULL) return(NULL);
+       chan = kmalloc(sizeof(*chan), GFP_ATOMIC);
+       if(chan == NULL)
+               return NULL;
        *chan = ((struct chan) { .list          = LIST_HEAD_INIT(chan->list),
+                                .free_list     =
+                                       LIST_HEAD_INIT(chan->free_list),
+                                .line          = line,
                                 .primary       = 1,
                                 .input         = 0,
                                 .output        = 0,
                                 .opened        = 0,
+                                .enabled       = 0,
                                 .fd            = -1,
-                                .pri           = pri,
                                 .ops           = ops,
                                 .data          = data });
-       return(chan);
+       return chan;
 }
 
-int parse_chan_pair(char *str, struct list_head *chans, int pri, int device,
-                   struct chan_opts *opts)
+int parse_chan_pair(char *str, struct line *line, int device,
+                   const struct chan_opts *opts)
 {
+       struct list_head *chans = &line->chan_list;
        struct chan *new, *chan;
        char *in, *out;
 
        if(!list_empty(chans)){
                chan = list_entry(chans->next, struct chan, list);
-               if(chan->pri >= pri) return(0);
-               free_chan(chans);
+               free_chan(chans, 0);
                INIT_LIST_HEAD(chans);
        }
 
-       if((out = strchr(str, ',')) != NULL){
+       out = strchr(str, ',');
+       if(out != NULL){
                in = str;
                *out = '\0';
                out++;
-               new = parse_chan(in, pri, device, opts);
-               if(new == NULL) return(-1);
+               new = parse_chan(line, in, device, opts);
+               if(new == NULL)
+                       return -1;
+
                new->input = 1;
                list_add(&new->list, chans);
 
-               new = parse_chan(out, pri, device, opts);
-               if(new == NULL) return(-1);
+               new = parse_chan(line, out, device, opts);
+               if(new == NULL)
+                       return -1;
+
                list_add(&new->list, chans);
                new->output = 1;
        }
        else {
-               new = parse_chan(str, pri, device, opts);
-               if(new == NULL) return(-1);
+               new = parse_chan(line, str, device, opts);
+               if(new == NULL)
+                       return -1;
+
                list_add(&new->list, chans);
                new->input = 1;
                new->output = 1;
        }
-       return(0);
+       return 0;
 }
 
 int chan_out_fd(struct list_head *chans)
@@ -452,13 +633,13 @@ int chan_out_fd(struct list_head *chans)
        list_for_each(ele, chans){
                chan = list_entry(ele, struct chan, list);
                if(chan->primary && chan->output)
-                       return(chan->fd);
+                       return chan->fd;
        }
-       return(-1);
+       return -1;
 }
 
-void chan_interrupt(struct list_head *chans, struct work_struct *task,
-                   struct tty_struct *tty, int irq, void *dev)
+void chan_interrupt(struct list_head *chans, struct delayed_work *task,
+                   struct tty_struct *tty, int irq)
 {
        struct list_head *ele, *next;
        struct chan *chan;
@@ -469,41 +650,26 @@ void chan_interrupt(struct list_head *chans, struct work_struct *task,
                chan = list_entry(ele, struct chan, list);
                if(!chan->input || (chan->ops->read == NULL)) continue;
                do {
-                       if((tty != NULL) && 
-                          (tty->flip.count >= TTY_FLIPBUF_SIZE)){
-                               schedule_work(task);
+                       if (tty && !tty_buffer_request_room(tty, 1)) {
+                               schedule_delayed_work(task, 1);
                                goto out;
                        }
                        err = chan->ops->read(chan->fd, &c, chan->data);
-                       if(err > 0) tty_receive_char(tty, c);
+                       if(err > 0)
+                               tty_receive_char(tty, c);
                } while(err > 0);
+
                if(err == 0) reactivate_fd(chan->fd, irq);
                if(err == -EIO){
                        if(chan->primary){
-                               if(tty != NULL) tty_hangup(tty);
-                               line_disable(dev, irq);
-                               close_chan(chans);
-                               free_chan(chans);
+                               if(tty != NULL)
+                                       tty_hangup(tty);
+                               close_chan(chans, 1);
                                return;
                        }
-                       else {
-                               if(chan->ops->close != NULL)
-                                       chan->ops->close(chan->fd, chan->data);
-                               free_one_chan(chan);
-                       }
+                       else close_one_chan(chan, 1);
                }
        }
  out:
        if(tty) tty_flip_buffer_push(tty);
 }
-
-/*
- * 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:
- */