diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /arch/um/drivers/port_kern.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'arch/um/drivers/port_kern.c')
-rw-r--r-- | arch/um/drivers/port_kern.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/arch/um/drivers/port_kern.c b/arch/um/drivers/port_kern.c new file mode 100644 index 00000000000..b5ee07472f7 --- /dev/null +++ b/arch/um/drivers/port_kern.c @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2001, 2002 Jeff Dike (jdike@karaya.com) + * Licensed under the GPL + */ + +#include "linux/list.h" +#include "linux/sched.h" +#include "linux/slab.h" +#include "linux/interrupt.h" +#include "linux/irq.h" +#include "linux/spinlock.h" +#include "linux/errno.h" +#include "asm/atomic.h" +#include "asm/semaphore.h" +#include "asm/errno.h" +#include "kern_util.h" +#include "kern.h" +#include "irq_user.h" +#include "irq_kern.h" +#include "port.h" +#include "init.h" +#include "os.h" + +struct port_list { + struct list_head list; + atomic_t wait_count; + int has_connection; + struct completion done; + int port; + int fd; + spinlock_t lock; + struct list_head pending; + struct list_head connections; +}; + +struct port_dev { + struct port_list *port; + int helper_pid; + int telnetd_pid; +}; + +struct connection { + struct list_head list; + int fd; + int helper_pid; + int socket[2]; + int telnetd_pid; + struct port_list *port; +}; + +static irqreturn_t pipe_interrupt(int irq, void *data, struct pt_regs *regs) +{ + struct connection *conn = data; + int fd; + + fd = os_rcv_fd(conn->socket[0], &conn->helper_pid); + if(fd < 0){ + if(fd == -EAGAIN) + return(IRQ_NONE); + + printk(KERN_ERR "pipe_interrupt : os_rcv_fd returned %d\n", + -fd); + os_close_file(conn->fd); + } + + list_del(&conn->list); + + conn->fd = fd; + list_add(&conn->list, &conn->port->connections); + + complete(&conn->port->done); + return(IRQ_HANDLED); +} + +#define NO_WAITER_MSG \ + "****\n" \ + "There are currently no UML consoles waiting for port connections.\n" \ + "Either disconnect from one to make it available or activate some more\n" \ + "by enabling more consoles in the UML /etc/inittab.\n" \ + "****\n" + +static int port_accept(struct port_list *port) +{ + struct connection *conn; + int fd, socket[2], pid, ret = 0; + + fd = port_connection(port->fd, socket, &pid); + if(fd < 0){ + if(fd != -EAGAIN) + printk(KERN_ERR "port_accept : port_connection " + "returned %d\n", -fd); + goto out; + } + + conn = kmalloc(sizeof(*conn), GFP_ATOMIC); + if(conn == NULL){ + printk(KERN_ERR "port_accept : failed to allocate " + "connection\n"); + goto out_close; + } + *conn = ((struct connection) + { .list = LIST_HEAD_INIT(conn->list), + .fd = fd, + .socket = { socket[0], socket[1] }, + .telnetd_pid = pid, + .port = port }); + + if(um_request_irq(TELNETD_IRQ, socket[0], IRQ_READ, pipe_interrupt, + SA_INTERRUPT | SA_SHIRQ | SA_SAMPLE_RANDOM, + "telnetd", conn)){ + printk(KERN_ERR "port_accept : failed to get IRQ for " + "telnetd\n"); + goto out_free; + } + + if(atomic_read(&port->wait_count) == 0){ + os_write_file(fd, NO_WAITER_MSG, sizeof(NO_WAITER_MSG)); + printk("No one waiting for port\n"); + } + list_add(&conn->list, &port->pending); + return(1); + + out_free: + kfree(conn); + out_close: + os_close_file(fd); + if(pid != -1) + os_kill_process(pid, 1); + out: + return(ret); +} + +DECLARE_MUTEX(ports_sem); +struct list_head ports = LIST_HEAD_INIT(ports); + +void port_work_proc(void *unused) +{ + struct port_list *port; + struct list_head *ele; + unsigned long flags; + + local_irq_save(flags); + list_for_each(ele, &ports){ + port = list_entry(ele, struct port_list, list); + if(!port->has_connection) + continue; + reactivate_fd(port->fd, ACCEPT_IRQ); + while(port_accept(port)) ; + port->has_connection = 0; + } + local_irq_restore(flags); +} + +DECLARE_WORK(port_work, port_work_proc, NULL); + +static irqreturn_t port_interrupt(int irq, void *data, struct pt_regs *regs) +{ + struct port_list *port = data; + + port->has_connection = 1; + schedule_work(&port_work); + return(IRQ_HANDLED); +} + +void *port_data(int port_num) +{ + struct list_head *ele; + struct port_list *port; + struct port_dev *dev = NULL; + int fd; + + down(&ports_sem); + list_for_each(ele, &ports){ + port = list_entry(ele, struct port_list, list); + if(port->port == port_num) goto found; + } + port = kmalloc(sizeof(struct port_list), GFP_KERNEL); + if(port == NULL){ + printk(KERN_ERR "Allocation of port list failed\n"); + goto out; + } + + fd = port_listen_fd(port_num); + if(fd < 0){ + printk(KERN_ERR "binding to port %d failed, errno = %d\n", + port_num, -fd); + goto out_free; + } + if(um_request_irq(ACCEPT_IRQ, fd, IRQ_READ, port_interrupt, + SA_INTERRUPT | SA_SHIRQ | SA_SAMPLE_RANDOM, "port", + port)){ + printk(KERN_ERR "Failed to get IRQ for port %d\n", port_num); + goto out_close; + } + + *port = ((struct port_list) + { .list = LIST_HEAD_INIT(port->list), + .wait_count = ATOMIC_INIT(0), + .has_connection = 0, + .port = port_num, + .fd = fd, + .pending = LIST_HEAD_INIT(port->pending), + .connections = LIST_HEAD_INIT(port->connections) }); + spin_lock_init(&port->lock); + init_completion(&port->done); + list_add(&port->list, &ports); + + found: + dev = kmalloc(sizeof(struct port_dev), GFP_KERNEL); + if(dev == NULL){ + printk(KERN_ERR "Allocation of port device entry failed\n"); + goto out; + } + + *dev = ((struct port_dev) { .port = port, + .helper_pid = -1, + .telnetd_pid = -1 }); + goto out; + + out_free: + kfree(port); + out_close: + os_close_file(fd); + out: + up(&ports_sem); + return(dev); +} + +int port_wait(void *data) +{ + struct port_dev *dev = data; + struct connection *conn; + struct port_list *port = dev->port; + int fd; + + atomic_inc(&port->wait_count); + while(1){ + fd = -ERESTARTSYS; + if(wait_for_completion_interruptible(&port->done)) + goto out; + + spin_lock(&port->lock); + + conn = list_entry(port->connections.next, struct connection, + list); + list_del(&conn->list); + spin_unlock(&port->lock); + + os_shutdown_socket(conn->socket[0], 1, 1); + os_close_file(conn->socket[0]); + os_shutdown_socket(conn->socket[1], 1, 1); + os_close_file(conn->socket[1]); + + /* This is done here because freeing an IRQ can't be done + * within the IRQ handler. So, pipe_interrupt always ups + * the semaphore regardless of whether it got a successful + * connection. Then we loop here throwing out failed + * connections until a good one is found. + */ + free_irq_by_irq_and_dev(TELNETD_IRQ, conn); + free_irq(TELNETD_IRQ, conn); + + if(conn->fd >= 0) break; + os_close_file(conn->fd); + kfree(conn); + } + + fd = conn->fd; + dev->helper_pid = conn->helper_pid; + dev->telnetd_pid = conn->telnetd_pid; + kfree(conn); + out: + atomic_dec(&port->wait_count); + return fd; +} + +void port_remove_dev(void *d) +{ + struct port_dev *dev = d; + + if(dev->helper_pid != -1) + os_kill_process(dev->helper_pid, 0); + if(dev->telnetd_pid != -1) + os_kill_process(dev->telnetd_pid, 1); + dev->helper_pid = -1; + dev->telnetd_pid = -1; +} + +void port_kern_free(void *d) +{ + struct port_dev *dev = d; + + port_remove_dev(dev); + kfree(dev); +} + +static void free_port(void) +{ + struct list_head *ele; + struct port_list *port; + + list_for_each(ele, &ports){ + port = list_entry(ele, struct port_list, list); + free_irq_by_fd(port->fd); + os_close_file(port->fd); + } +} + +__uml_exitcall(free_port); |