When I use OpenSUSE-11.2 on UML (> 2.6.25) I get lots of such errors: Registering fd 1 twice Irqs : 3, 3 Ids : 0x09cb41a0, 0x09cb4120 ------------[ cut here ]------------ WARNING: at kernel/irq/manage.c:896 __free_irq+0x79/0x11a() Trying to free already-free IRQ 3 Modules linked in: 09dadc6c: [<081b2edb>] dump_stack+0x1c/0x20 09dadc84: [<080716da>] warn_slowpath_common+0x49/0x77 09dadc9c: [<08071772>] warn_slowpath_fmt+0x26/0x2a 09dadcb4: [<08094e08>] __free_irq+0x79/0x11a 09dadce4: [<08094ed6>] free_irq+0x2d/0x49 09dadcf4: [<0805b4bc>] close_one_chan+0x70/0x9c 09dadd0c: [<0805b833>] close_chan+0x17/0x22 09dadd1c: [<0805bdda>] enable_chan+0x70/0x7c 09dadd3c: [<0805cbb7>] line_open+0x34/0x9f 09dadd54: [<0805b21e>] con_open+0x13/0x35 09dadd6c: [<0814dc89>] tty_open+0x285/0x384 09dadda0: [<080b754e>] chrdev_open+0xe0/0xf9 09daddc0: [<080b3fb2>] __dentry_open+0xf3/0x1e2 09dadde4: [<080b4142>] nameidata_to_filp+0x35/0x49 09daddfc: [<080bd270>] do_last+0x409/0x50e 09dade28: [<080bea04>] do_filp_open+0x175/0x446 09dadecc: [<080b3d89>] do_sys_open+0x4a/0x128 09dadf04: [<080b3ea2>] sys_open+0x19/0x21 09dadf28: [<0805ab5a>] handle_syscall+0x7a/0x98 09dadf78: [<08068441>] userspace+0x2c9/0x370 09dadfe0: [<08058bb3>] fork_handler+0x53/0x5b 09dadffc: [<00766564>] 0x766564 ---[ end trace 9ebc1094aaf4bded ]--- This patch fixes the issue. Signed-off-by: Richard Weinberger <richard@nod.at> Cc: Jeff Dike <jdike@addtoit.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
		
			
				
	
	
		
			623 lines
		
	
	
	
		
			13 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			623 lines
		
	
	
	
		
			13 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com)
 | 
						|
 * Licensed under the GPL
 | 
						|
 */
 | 
						|
 | 
						|
#include <linux/slab.h>
 | 
						|
#include <linux/tty.h>
 | 
						|
#include <linux/tty_flip.h>
 | 
						|
#include "chan_kern.h"
 | 
						|
#include "os.h"
 | 
						|
 | 
						|
#ifdef CONFIG_NOCONFIG_CHAN
 | 
						|
static void *not_configged_init(char *str, int device,
 | 
						|
				const struct chan_opts *opts)
 | 
						|
{
 | 
						|
	printk(KERN_ERR "Using a channel type which is configured out of "
 | 
						|
	       "UML\n");
 | 
						|
	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 "
 | 
						|
	       "UML\n");
 | 
						|
	return -ENODEV;
 | 
						|
}
 | 
						|
 | 
						|
static void not_configged_close(int fd, void *data)
 | 
						|
{
 | 
						|
	printk(KERN_ERR "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 "
 | 
						|
	       "UML\n");
 | 
						|
	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 "
 | 
						|
	       "UML\n");
 | 
						|
	return -EIO;
 | 
						|
}
 | 
						|
 | 
						|
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 "
 | 
						|
	       "UML\n");
 | 
						|
	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 "
 | 
						|
	       "UML\n");
 | 
						|
	return -ENODEV;
 | 
						|
}
 | 
						|
 | 
						|
static void not_configged_free(void *data)
 | 
						|
{
 | 
						|
	printk(KERN_ERR "Using a channel type which is configured out of "
 | 
						|
	       "UML\n");
 | 
						|
}
 | 
						|
 | 
						|
static const struct chan_ops not_configged_ops = {
 | 
						|
	.init		= not_configged_init,
 | 
						|
	.open		= not_configged_open,
 | 
						|
	.close		= not_configged_close,
 | 
						|
	.read		= not_configged_read,
 | 
						|
	.write		= not_configged_write,
 | 
						|
	.console_write	= not_configged_console_write,
 | 
						|
	.window_size	= not_configged_window_size,
 | 
						|
	.free		= not_configged_free,
 | 
						|
	.winch		= 0,
 | 
						|
};
 | 
						|
#endif /* CONFIG_NOCONFIG_CHAN */
 | 
						|
 | 
						|
static void tty_receive_char(struct tty_struct *tty, char ch)
 | 
						|
{
 | 
						|
	if (tty == NULL)
 | 
						|
		return;
 | 
						|
 | 
						|
	if (I_IXON(tty) && !I_IXOFF(tty) && !tty->raw) {
 | 
						|
		if (ch == STOP_CHAR(tty)) {
 | 
						|
			stop_tty(tty);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		else if (ch == START_CHAR(tty)) {
 | 
						|
			start_tty(tty);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	tty_insert_flip_char(tty, ch, TTY_NORMAL);
 | 
						|
}
 | 
						|
 | 
						|
static int open_one_chan(struct chan *chan)
 | 
						|
{
 | 
						|
	int fd, err;
 | 
						|
 | 
						|
	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;
 | 
						|
 | 
						|
	err = os_set_fd_block(fd, 0);
 | 
						|
	if (err) {
 | 
						|
		(*chan->ops->close)(fd, chan->data);
 | 
						|
		return err;
 | 
						|
	}
 | 
						|
 | 
						|
	chan->fd = fd;
 | 
						|
 | 
						|
	chan->opened = 1;
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int open_chan(struct list_head *chans)
 | 
						|
{
 | 
						|
	struct list_head *ele;
 | 
						|
	struct chan *chan;
 | 
						|
	int ret, err = 0;
 | 
						|
 | 
						|
	list_for_each(ele, chans) {
 | 
						|
		chan = list_entry(ele, struct chan, list);
 | 
						|
		ret = open_one_chan(chan);
 | 
						|
		if (chan->primary)
 | 
						|
			err = ret;
 | 
						|
	}
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
void chan_enable_winch(struct list_head *chans, struct tty_struct *tty)
 | 
						|
{
 | 
						|
	struct list_head *ele;
 | 
						|
	struct chan *chan;
 | 
						|
 | 
						|
	list_for_each(ele, chans) {
 | 
						|
		chan = list_entry(ele, struct chan, list);
 | 
						|
		if (chan->primary && chan->output && chan->ops->winch) {
 | 
						|
			register_winch(chan->fd, tty);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
int enable_chan(struct line *line)
 | 
						|
{
 | 
						|
	struct list_head *ele;
 | 
						|
	struct chan *chan;
 | 
						|
	int err;
 | 
						|
 | 
						|
	list_for_each(ele, &line->chan_list) {
 | 
						|
		chan = list_entry(ele, struct chan, list);
 | 
						|
		err = open_one_chan(chan);
 | 
						|
		if (err) {
 | 
						|
			if (chan->primary)
 | 
						|
				goto out_close;
 | 
						|
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (chan->enabled)
 | 
						|
			continue;
 | 
						|
		err = line_setup_irq(chan->fd, chan->input, chan->output, line,
 | 
						|
				     chan);
 | 
						|
		if (err)
 | 
						|
			goto out_close;
 | 
						|
 | 
						|
		chan->enabled = 1;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
 | 
						|
 out_close:
 | 
						|
	close_chan(&line->chan_list, 0);
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
/* Items are added in IRQ context, when free_irq can't be called, and
 | 
						|
 * removed in process context, when it can.
 | 
						|
 * This handles interrupt sources which disappear, and which need to
 | 
						|
 * be permanently disabled.  This is discovered in IRQ context, but
 | 
						|
 * the freeing of the IRQ must be done later.
 | 
						|
 */
 | 
						|
static DEFINE_SPINLOCK(irqs_to_free_lock);
 | 
						|
static LIST_HEAD(irqs_to_free);
 | 
						|
 | 
						|
void free_irqs(void)
 | 
						|
{
 | 
						|
	struct chan *chan;
 | 
						|
	LIST_HEAD(list);
 | 
						|
	struct list_head *ele;
 | 
						|
	unsigned long flags;
 | 
						|
 | 
						|
	spin_lock_irqsave(&irqs_to_free_lock, flags);
 | 
						|
	list_splice_init(&irqs_to_free, &list);
 | 
						|
	spin_unlock_irqrestore(&irqs_to_free_lock, flags);
 | 
						|
 | 
						|
	list_for_each(ele, &list) {
 | 
						|
		chan = list_entry(ele, struct chan, free_list);
 | 
						|
 | 
						|
		if (chan->input && chan->enabled)
 | 
						|
			free_irq(chan->line->driver->read_irq, chan);
 | 
						|
		if (chan->output && chan->enabled)
 | 
						|
			free_irq(chan->line->driver->write_irq, chan);
 | 
						|
		chan->enabled = 0;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void close_one_chan(struct chan *chan, int delay_free_irq)
 | 
						|
{
 | 
						|
	unsigned long flags;
 | 
						|
 | 
						|
	if (!chan->opened)
 | 
						|
		return;
 | 
						|
 | 
						|
	if (delay_free_irq) {
 | 
						|
		spin_lock_irqsave(&irqs_to_free_lock, flags);
 | 
						|
		list_add(&chan->free_list, &irqs_to_free);
 | 
						|
		spin_unlock_irqrestore(&irqs_to_free_lock, flags);
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		if (chan->input && chan->enabled)
 | 
						|
			free_irq(chan->line->driver->read_irq, chan);
 | 
						|
		if (chan->output && chan->enabled)
 | 
						|
			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 chan *chan;
 | 
						|
 | 
						|
	/* Close in reverse order as open in case more than one of them
 | 
						|
	 * refers to the same device and they save and restore that device's
 | 
						|
	 * state.  Then, the first one opened will have the original state,
 | 
						|
	 * so it must be the last closed.
 | 
						|
	 */
 | 
						|
	list_for_each_entry_reverse(chan, chans, list) {
 | 
						|
		close_one_chan(chan, delay_free_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;
 | 
						|
 | 
						|
	if (len == 0)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	list_for_each(ele, chans) {
 | 
						|
		chan = list_entry(ele, struct chan, list);
 | 
						|
		if (!chan->output || (chan->ops->write == NULL))
 | 
						|
			continue;
 | 
						|
 | 
						|
		n = chan->ops->write(chan->fd, buf, len, chan->data);
 | 
						|
		if (chan->primary) {
 | 
						|
			ret = n;
 | 
						|
			if ((ret == -EAGAIN) || ((ret >= 0) && (ret < len)))
 | 
						|
				reactivate_fd(chan->fd, write_irq);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
int console_write_chan(struct list_head *chans, const char *buf, int len)
 | 
						|
{
 | 
						|
	struct list_head *ele;
 | 
						|
	struct chan *chan;
 | 
						|
	int n, ret = 0;
 | 
						|
 | 
						|
	list_for_each(ele, chans) {
 | 
						|
		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);
 | 
						|
		if (chan->primary)
 | 
						|
			ret = n;
 | 
						|
	}
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
int console_open_chan(struct line *line, struct console *co)
 | 
						|
{
 | 
						|
	int err;
 | 
						|
 | 
						|
	err = open_chan(&line->chan_list);
 | 
						|
	if (err)
 | 
						|
		return err;
 | 
						|
 | 
						|
	printk(KERN_INFO "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,
 | 
						|
		      unsigned short *cols_out)
 | 
						|
{
 | 
						|
	struct list_head *ele;
 | 
						|
	struct chan *chan;
 | 
						|
 | 
						|
	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);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
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);
 | 
						|
 | 
						|
	if (chan->primary && chan->output)
 | 
						|
		ignore_sigio_fd(chan->fd);
 | 
						|
	kfree(chan);
 | 
						|
}
 | 
						|
 | 
						|
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, delay_free_irq);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static int one_chan_config_string(struct chan *chan, char *str, int size,
 | 
						|
				  char **error_out)
 | 
						|
{
 | 
						|
	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;
 | 
						|
	}
 | 
						|
 | 
						|
	CONFIG_CHUNK(str, size, n, ":", 0);
 | 
						|
	CONFIG_CHUNK(str, size, n, chan->dev, 0);
 | 
						|
 | 
						|
	return n;
 | 
						|
}
 | 
						|
 | 
						|
static int chan_pair_config_string(struct chan *in, struct chan *out,
 | 
						|
				   char *str, int size, char **error_out)
 | 
						|
{
 | 
						|
	int n;
 | 
						|
 | 
						|
	n = one_chan_config_string(in, str, size, error_out);
 | 
						|
	str += n;
 | 
						|
	size -= n;
 | 
						|
 | 
						|
	if (in == out) {
 | 
						|
		CONFIG_CHUNK(str, size, n, "", 1);
 | 
						|
		return n;
 | 
						|
	}
 | 
						|
 | 
						|
	CONFIG_CHUNK(str, size, n, ",", 1);
 | 
						|
	n = one_chan_config_string(out, str, size, error_out);
 | 
						|
	str += n;
 | 
						|
	size -= n;
 | 
						|
	CONFIG_CHUNK(str, size, n, "", 1);
 | 
						|
 | 
						|
	return n;
 | 
						|
}
 | 
						|
 | 
						|
int chan_config_string(struct list_head *chans, char *str, int size,
 | 
						|
		       char **error_out)
 | 
						|
{
 | 
						|
	struct list_head *ele;
 | 
						|
	struct chan *chan, *in = NULL, *out = NULL;
 | 
						|
 | 
						|
	list_for_each(ele, chans) {
 | 
						|
		chan = list_entry(ele, struct chan, list);
 | 
						|
		if (!chan->primary)
 | 
						|
			continue;
 | 
						|
		if (chan->input)
 | 
						|
			in = chan;
 | 
						|
		if (chan->output)
 | 
						|
			out = chan;
 | 
						|
	}
 | 
						|
 | 
						|
	return chan_pair_config_string(in, out, str, size, error_out);
 | 
						|
}
 | 
						|
 | 
						|
struct chan_type {
 | 
						|
	char *key;
 | 
						|
	const struct chan_ops *ops;
 | 
						|
};
 | 
						|
 | 
						|
static const struct chan_type chan_table[] = {
 | 
						|
	{ "fd", &fd_ops },
 | 
						|
 | 
						|
#ifdef CONFIG_NULL_CHAN
 | 
						|
	{ "null", &null_ops },
 | 
						|
#else
 | 
						|
	{ "null", ¬_configged_ops },
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef CONFIG_PORT_CHAN
 | 
						|
	{ "port", &port_ops },
 | 
						|
#else
 | 
						|
	{ "port", ¬_configged_ops },
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef CONFIG_PTY_CHAN
 | 
						|
	{ "pty", &pty_ops },
 | 
						|
	{ "pts", &pts_ops },
 | 
						|
#else
 | 
						|
	{ "pty", ¬_configged_ops },
 | 
						|
	{ "pts", ¬_configged_ops },
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef CONFIG_TTY_CHAN
 | 
						|
	{ "tty", &tty_ops },
 | 
						|
#else
 | 
						|
	{ "tty", ¬_configged_ops },
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef CONFIG_XTERM_CHAN
 | 
						|
	{ "xterm", &xterm_ops },
 | 
						|
#else
 | 
						|
	{ "xterm", ¬_configged_ops },
 | 
						|
#endif
 | 
						|
};
 | 
						|
 | 
						|
static struct chan *parse_chan(struct line *line, char *str, int device,
 | 
						|
			       const struct chan_opts *opts, char **error_out)
 | 
						|
{
 | 
						|
	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 < ARRAY_SIZE(chan_table); i++) {
 | 
						|
		entry = &chan_table[i];
 | 
						|
		if (!strncmp(str, entry->key, strlen(entry->key))) {
 | 
						|
			ops = entry->ops;
 | 
						|
			str += strlen(entry->key);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (ops == NULL) {
 | 
						|
		*error_out = "No match for configured backends";
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	data = (*ops->init)(str, device, opts);
 | 
						|
	if (data == NULL) {
 | 
						|
		*error_out = "Configuration failed";
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	chan = kmalloc(sizeof(*chan), GFP_ATOMIC);
 | 
						|
	if (chan == NULL) {
 | 
						|
		*error_out = "Memory allocation failed";
 | 
						|
		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,
 | 
						|
				 .ops 		= ops,
 | 
						|
				 .data 		= data });
 | 
						|
	return chan;
 | 
						|
}
 | 
						|
 | 
						|
int parse_chan_pair(char *str, struct line *line, int device,
 | 
						|
		    const struct chan_opts *opts, char **error_out)
 | 
						|
{
 | 
						|
	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);
 | 
						|
		free_chan(chans, 0);
 | 
						|
		INIT_LIST_HEAD(chans);
 | 
						|
	}
 | 
						|
 | 
						|
	out = strchr(str, ',');
 | 
						|
	if (out != NULL) {
 | 
						|
		in = str;
 | 
						|
		*out = '\0';
 | 
						|
		out++;
 | 
						|
		new = parse_chan(line, in, device, opts, error_out);
 | 
						|
		if (new == NULL)
 | 
						|
			return -1;
 | 
						|
 | 
						|
		new->input = 1;
 | 
						|
		list_add(&new->list, chans);
 | 
						|
 | 
						|
		new = parse_chan(line, out, device, opts, error_out);
 | 
						|
		if (new == NULL)
 | 
						|
			return -1;
 | 
						|
 | 
						|
		list_add(&new->list, chans);
 | 
						|
		new->output = 1;
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		new = parse_chan(line, str, device, opts, error_out);
 | 
						|
		if (new == NULL)
 | 
						|
			return -1;
 | 
						|
 | 
						|
		list_add(&new->list, chans);
 | 
						|
		new->input = 1;
 | 
						|
		new->output = 1;
 | 
						|
	}
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
	int err;
 | 
						|
	char c;
 | 
						|
 | 
						|
	list_for_each_safe(ele, next, chans) {
 | 
						|
		chan = list_entry(ele, struct chan, list);
 | 
						|
		if (!chan->input || (chan->ops->read == NULL))
 | 
						|
			continue;
 | 
						|
		do {
 | 
						|
			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);
 | 
						|
		} while (err > 0);
 | 
						|
 | 
						|
		if (err == 0)
 | 
						|
			reactivate_fd(chan->fd, irq);
 | 
						|
		if (err == -EIO) {
 | 
						|
			if (chan->primary) {
 | 
						|
				if (tty != NULL)
 | 
						|
					tty_hangup(tty);
 | 
						|
				close_chan(chans, 1);
 | 
						|
				return;
 | 
						|
			}
 | 
						|
			else close_one_chan(chan, 1);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 out:
 | 
						|
	if (tty)
 | 
						|
		tty_flip_buffer_push(tty);
 | 
						|
}
 |