diff options
Diffstat (limited to 'arch/um/drivers')
59 files changed, 11191 insertions, 0 deletions
diff --git a/arch/um/drivers/Makefile b/arch/um/drivers/Makefile new file mode 100644 index 000000000..e7582e1d2 --- /dev/null +++ b/arch/um/drivers/Makefile @@ -0,0 +1,67 @@ +# +# Copyright (C) 2000, 2002, 2003 Jeff Dike (jdike@karaya.com) +# Licensed under the GPL +# + +# pcap is broken in 2.5 because kbuild doesn't allow pcap.a to be linked +# in to pcap.o + +slip-objs := slip_kern.o slip_user.o +slirp-objs := slirp_kern.o slirp_user.o +daemon-objs := daemon_kern.o daemon_user.o +umcast-objs := umcast_kern.o umcast_user.o +net-objs := net_kern.o net_user.o +mconsole-objs := mconsole_kern.o mconsole_user.o +hostaudio-objs := hostaudio_kern.o +ubd-objs := ubd_kern.o ubd_user.o +port-objs := port_kern.o port_user.o +harddog-objs := harddog_kern.o harddog_user.o + +LDFLAGS_pcap.o := -r $(shell $(CC) $(KBUILD_CFLAGS) -print-file-name=libpcap.a) + +LDFLAGS_vde.o := -r $(shell $(CC) $(CFLAGS) -print-file-name=libvdeplug.a) + +targets := pcap_kern.o pcap_user.o vde_kern.o vde_user.o + +$(obj)/pcap.o: $(obj)/pcap_kern.o $(obj)/pcap_user.o + $(LD) -r -dp -o $@ $^ $(LDFLAGS) $(LDFLAGS_pcap.o) + +$(obj)/vde.o: $(obj)/vde_kern.o $(obj)/vde_user.o + $(LD) -r -dp -o $@ $^ $(LDFLAGS) $(LDFLAGS_vde.o) + +#XXX: The call below does not work because the flags are added before the +# object name, so nothing from the library gets linked. +#$(call if_changed,ld) + +# When the above is fixed, don't forget to add this too! +#targets += $(obj)/pcap.o + +obj-y := stdio_console.o fd.o chan_kern.o chan_user.o line.o +obj-$(CONFIG_SSL) += ssl.o +obj-$(CONFIG_STDERR_CONSOLE) += stderr_console.o + +obj-$(CONFIG_UML_NET_SLIP) += slip.o slip_common.o +obj-$(CONFIG_UML_NET_SLIRP) += slirp.o slip_common.o +obj-$(CONFIG_UML_NET_DAEMON) += daemon.o +obj-$(CONFIG_UML_NET_VDE) += vde.o +obj-$(CONFIG_UML_NET_MCAST) += umcast.o +obj-$(CONFIG_UML_NET_PCAP) += pcap.o +obj-$(CONFIG_UML_NET) += net.o +obj-$(CONFIG_MCONSOLE) += mconsole.o +obj-$(CONFIG_MMAPPER) += mmapper_kern.o +obj-$(CONFIG_BLK_DEV_UBD) += ubd.o +obj-$(CONFIG_HOSTAUDIO) += hostaudio.o +obj-$(CONFIG_NULL_CHAN) += null.o +obj-$(CONFIG_PORT_CHAN) += port.o +obj-$(CONFIG_PTY_CHAN) += pty.o +obj-$(CONFIG_TTY_CHAN) += tty.o +obj-$(CONFIG_XTERM_CHAN) += xterm.o xterm_kern.o +obj-$(CONFIG_UML_WATCHDOG) += harddog.o +obj-$(CONFIG_BLK_DEV_COW_COMMON) += cow_user.o +obj-$(CONFIG_UML_RANDOM) += random.o + +# pcap_user.o must be added explicitly. +USER_OBJS := fd.o null.o pty.o tty.o xterm.o slip_common.o pcap_user.o vde_user.o +CFLAGS_null.o = -DDEV_NULL=$(DEV_NULL_PATH) + +include arch/um/scripts/Makefile.rules diff --git a/arch/um/drivers/chan.h b/arch/um/drivers/chan.h new file mode 100644 index 000000000..c512b0306 --- /dev/null +++ b/arch/um/drivers/chan.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2000, 2001 Jeff Dike (jdike@karaya.com) + * Licensed under the GPL + */ + +#ifndef __CHAN_KERN_H__ +#define __CHAN_KERN_H__ + +#include <linux/tty.h> +#include <linux/list.h> +#include <linux/console.h> +#include "chan_user.h" +#include "line.h" + +struct chan { + struct list_head list; + struct list_head free_list; + struct line *line; + char *dev; + unsigned int primary:1; + unsigned int input:1; + unsigned int output:1; + unsigned int opened:1; + unsigned int enabled:1; + int fd; + const struct chan_ops *ops; + void *data; +}; + +extern void chan_interrupt(struct line *line, int irq); +extern int parse_chan_pair(char *str, struct line *line, int device, + const struct chan_opts *opts, char **error_out); +extern int write_chan(struct chan *chan, const char *buf, int len, + int write_irq); +extern int console_write_chan(struct chan *chan, const char *buf, + int len); +extern int console_open_chan(struct line *line, struct console *co); +extern void deactivate_chan(struct chan *chan, int irq); +extern void reactivate_chan(struct chan *chan, int irq); +extern void chan_enable_winch(struct chan *chan, struct tty_port *port); +extern int enable_chan(struct line *line); +extern void close_chan(struct line *line); +extern int chan_window_size(struct line *line, + unsigned short *rows_out, + unsigned short *cols_out); +extern int chan_config_string(struct line *line, char *str, int size, + char **error_out); + +#endif diff --git a/arch/um/drivers/chan_kern.c b/arch/um/drivers/chan_kern.c new file mode 100644 index 000000000..acbe6c67a --- /dev/null +++ b/arch/um/drivers/chan_kern.c @@ -0,0 +1,581 @@ +/* + * 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.h" +#include <os.h> +#include <irq_kern.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 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 chan *chan, struct tty_port *port) +{ + if (chan && chan->primary && chan->ops->winch) + register_winch(chan->fd, port); +} + +static void line_timer_cb(struct work_struct *work) +{ + struct line *line = container_of(work, struct line, task.work); + + if (!line->throttled) + chan_interrupt(line, line->driver->read_irq); +} + +int enable_chan(struct line *line) +{ + struct list_head *ele; + struct chan *chan; + int err; + + INIT_DELAYED_WORK(&line->task, line_timer_cb); + + 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); + 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) + um_free_irq(chan->line->driver->read_irq, chan); + if (chan->output && chan->enabled) + um_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) + um_free_irq(chan->line->driver->read_irq, chan); + if (chan->output && chan->enabled) + um_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 line *line) +{ + 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, &line->chan_list, list) { + close_one_chan(chan, 0); + } +} + +void deactivate_chan(struct chan *chan, int irq) +{ + if (chan && chan->enabled) + deactivate_fd(chan->fd, irq); +} + +void reactivate_chan(struct chan *chan, int irq) +{ + if (chan && chan->enabled) + reactivate_fd(chan->fd, irq); +} + +int write_chan(struct chan *chan, const char *buf, int len, + int write_irq) +{ + int n, ret = 0; + + if (len == 0 || !chan || !chan->ops->write) + return 0; + + 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 chan *chan, const char *buf, int len) +{ + int n, ret = 0; + + if (!chan || !chan->ops->console_write) + return 0; + + 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 line *line, unsigned short *rows_out, + unsigned short *cols_out) +{ + struct chan *chan; + + chan = line->chan_in; + if (chan && chan->primary) { + if (chan->ops->window_size == NULL) + return 0; + return chan->ops->window_size(chan->fd, chan->data, + rows_out, cols_out); + } + chan = line->chan_out; + if (chan && 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) +{ + list_del(&chan->list); + + close_one_chan(chan, 0); + + 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) +{ + 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); + } +} + +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 line *line, char *str, int size, + char **error_out) +{ + struct chan *in = line->chan_in, *out = line->chan_out; + + if (in && !in->primary) + in = NULL; + if (out && !out->primary) + out = NULL; + + 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; + char *in, *out; + + if (!list_empty(chans)) { + line->chan_in = line->chan_out = NULL; + free_chan(chans); + INIT_LIST_HEAD(chans); + } + + if (!str) + return 0; + + 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); + line->chan_in = new; + + new = parse_chan(line, out, device, opts, error_out); + if (new == NULL) + return -1; + + list_add(&new->list, chans); + new->output = 1; + line->chan_out = new; + } + 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; + line->chan_in = line->chan_out = new; + } + return 0; +} + +void chan_interrupt(struct line *line, int irq) +{ + struct tty_port *port = &line->port; + struct chan *chan = line->chan_in; + int err; + char c; + + if (!chan || !chan->ops->read) + goto out; + + do { + if (!tty_buffer_request_room(port, 1)) { + schedule_delayed_work(&line->task, 1); + goto out; + } + err = chan->ops->read(chan->fd, &c, chan->data); + if (err > 0) + tty_insert_flip_char(port, c, TTY_NORMAL); + } while (err > 0); + + if (err == 0) + reactivate_fd(chan->fd, irq); + if (err == -EIO) { + if (chan->primary) { + tty_port_tty_hangup(&line->port, false); + if (line->chan_out != chan) + close_one_chan(line->chan_out, 1); + } + close_one_chan(chan, 1); + if (chan->primary) + return; + } + out: + tty_flip_buffer_push(port); +} diff --git a/arch/um/drivers/chan_user.c b/arch/um/drivers/chan_user.c new file mode 100644 index 000000000..3fd7c3efd --- /dev/null +++ b/arch/um/drivers/chan_user.c @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com) + * Licensed under the GPL + */ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <sched.h> +#include <signal.h> +#include <termios.h> +#include <sys/ioctl.h> +#include "chan_user.h" +#include <os.h> +#include <um_malloc.h> + +void generic_close(int fd, void *unused) +{ + close(fd); +} + +int generic_read(int fd, char *c_out, void *unused) +{ + int n; + + n = read(fd, c_out, sizeof(*c_out)); + if (n > 0) + return n; + else if (errno == EAGAIN) + return 0; + else if (n == 0) + return -EIO; + return -errno; +} + +/* XXX Trivial wrapper around write */ + +int generic_write(int fd, const char *buf, int n, void *unused) +{ + int err; + + err = write(fd, buf, n); + if (err > 0) + return err; + else if (errno == EAGAIN) + return 0; + else if (err == 0) + return -EIO; + return -errno; +} + +int generic_window_size(int fd, void *unused, unsigned short *rows_out, + unsigned short *cols_out) +{ + struct winsize size; + int ret; + + if (ioctl(fd, TIOCGWINSZ, &size) < 0) + return -errno; + + ret = ((*rows_out != size.ws_row) || (*cols_out != size.ws_col)); + + *rows_out = size.ws_row; + *cols_out = size.ws_col; + + return ret; +} + +void generic_free(void *data) +{ + kfree(data); +} + +int generic_console_write(int fd, const char *buf, int n) +{ + sigset_t old, no_sigio; + struct termios save, new; + int err; + + if (isatty(fd)) { + sigemptyset(&no_sigio); + sigaddset(&no_sigio, SIGIO); + if (sigprocmask(SIG_BLOCK, &no_sigio, &old)) + goto error; + + CATCH_EINTR(err = tcgetattr(fd, &save)); + if (err) + goto error; + new = save; + /* + * The terminal becomes a bit less raw, to handle \n also as + * "Carriage Return", not only as "New Line". Otherwise, the new + * line won't start at the first column. + */ + new.c_oflag |= OPOST; + CATCH_EINTR(err = tcsetattr(fd, TCSAFLUSH, &new)); + if (err) + goto error; + } + err = generic_write(fd, buf, n, NULL); + /* + * Restore raw mode, in any case; we *must* ignore any error apart + * EINTR, except for debug. + */ + if (isatty(fd)) { + CATCH_EINTR(tcsetattr(fd, TCSAFLUSH, &save)); + sigprocmask(SIG_SETMASK, &old, NULL); + } + + return err; +error: + return -errno; +} + +/* + * UML SIGWINCH handling + * + * The point of this is to handle SIGWINCH on consoles which have host + * ttys and relay them inside UML to whatever might be running on the + * console and cares about the window size (since SIGWINCH notifies + * about terminal size changes). + * + * So, we have a separate thread for each host tty attached to a UML + * device (side-issue - I'm annoyed that one thread can't have + * multiple controlling ttys for the purpose of handling SIGWINCH, but + * I imagine there are other reasons that doesn't make any sense). + * + * SIGWINCH can't be received synchronously, so you have to set up to + * receive it as a signal. That being the case, if you are going to + * wait for it, it is convenient to sit in sigsuspend() and wait for + * the signal to bounce you out of it (see below for how we make sure + * to exit only on SIGWINCH). + */ + +static void winch_handler(int sig) +{ +} + +struct winch_data { + int pty_fd; + int pipe_fd; +}; + +static int winch_thread(void *arg) +{ + struct winch_data *data = arg; + sigset_t sigs; + int pty_fd, pipe_fd; + int count; + char c = 1; + + pty_fd = data->pty_fd; + pipe_fd = data->pipe_fd; + count = write(pipe_fd, &c, sizeof(c)); + if (count != sizeof(c)) + printk(UM_KERN_ERR "winch_thread : failed to write " + "synchronization byte, err = %d\n", -count); + + /* + * We are not using SIG_IGN on purpose, so don't fix it as I thought to + * do! If using SIG_IGN, the sigsuspend() call below would not stop on + * SIGWINCH. + */ + + signal(SIGWINCH, winch_handler); + sigfillset(&sigs); + /* Block all signals possible. */ + if (sigprocmask(SIG_SETMASK, &sigs, NULL) < 0) { + printk(UM_KERN_ERR "winch_thread : sigprocmask failed, " + "errno = %d\n", errno); + exit(1); + } + /* In sigsuspend(), block anything else than SIGWINCH. */ + sigdelset(&sigs, SIGWINCH); + + if (setsid() < 0) { + printk(UM_KERN_ERR "winch_thread : setsid failed, errno = %d\n", + errno); + exit(1); + } + + if (ioctl(pty_fd, TIOCSCTTY, 0) < 0) { + printk(UM_KERN_ERR "winch_thread : TIOCSCTTY failed on " + "fd %d err = %d\n", pty_fd, errno); + exit(1); + } + + if (tcsetpgrp(pty_fd, os_getpid()) < 0) { + printk(UM_KERN_ERR "winch_thread : tcsetpgrp failed on " + "fd %d err = %d\n", pty_fd, errno); + exit(1); + } + + /* + * These are synchronization calls between various UML threads on the + * host - since they are not different kernel threads, we cannot use + * kernel semaphores. We don't use SysV semaphores because they are + * persistent. + */ + count = read(pipe_fd, &c, sizeof(c)); + if (count != sizeof(c)) + printk(UM_KERN_ERR "winch_thread : failed to read " + "synchronization byte, err = %d\n", errno); + + while(1) { + /* + * This will be interrupted by SIGWINCH only, since + * other signals are blocked. + */ + sigsuspend(&sigs); + + count = write(pipe_fd, &c, sizeof(c)); + if (count != sizeof(c)) + printk(UM_KERN_ERR "winch_thread : write failed, " + "err = %d\n", errno); + } +} + +static int winch_tramp(int fd, struct tty_port *port, int *fd_out, + unsigned long *stack_out) +{ + struct winch_data data; + int fds[2], n, err; + char c; + + err = os_pipe(fds, 1, 1); + if (err < 0) { + printk(UM_KERN_ERR "winch_tramp : os_pipe failed, err = %d\n", + -err); + goto out; + } + + data = ((struct winch_data) { .pty_fd = fd, + .pipe_fd = fds[1] } ); + /* + * CLONE_FILES so this thread doesn't hold open files which are open + * now, but later closed in a different thread. This is a + * problem with /dev/net/tun, which if held open by this + * thread, prevents the TUN/TAP device from being reused. + */ + err = run_helper_thread(winch_thread, &data, CLONE_FILES, stack_out); + if (err < 0) { + printk(UM_KERN_ERR "fork of winch_thread failed - errno = %d\n", + -err); + goto out_close; + } + + *fd_out = fds[0]; + n = read(fds[0], &c, sizeof(c)); + if (n != sizeof(c)) { + printk(UM_KERN_ERR "winch_tramp : failed to read " + "synchronization byte\n"); + printk(UM_KERN_ERR "read failed, err = %d\n", errno); + printk(UM_KERN_ERR "fd %d will not support SIGWINCH\n", fd); + err = -EINVAL; + goto out_close; + } + + if (os_set_fd_block(*fd_out, 0)) { + printk(UM_KERN_ERR "winch_tramp: failed to set thread_fd " + "non-blocking.\n"); + goto out_close; + } + + return err; + + out_close: + close(fds[1]); + close(fds[0]); + out: + return err; +} + +void register_winch(int fd, struct tty_port *port) +{ + unsigned long stack; + int pid, thread, count, thread_fd = -1; + char c = 1; + + if (!isatty(fd)) + return; + + pid = tcgetpgrp(fd); + if (is_skas_winch(pid, fd, port)) { + register_winch_irq(-1, fd, -1, port, 0); + return; + } + + if (pid == -1) { + thread = winch_tramp(fd, port, &thread_fd, &stack); + if (thread < 0) + return; + + register_winch_irq(thread_fd, fd, thread, port, stack); + + count = write(thread_fd, &c, sizeof(c)); + if (count != sizeof(c)) + printk(UM_KERN_ERR "register_winch : failed to write " + "synchronization byte, err = %d\n", errno); + } +} diff --git a/arch/um/drivers/chan_user.h b/arch/um/drivers/chan_user.h new file mode 100644 index 000000000..03f1b565c --- /dev/null +++ b/arch/um/drivers/chan_user.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2000, 2001 Jeff Dike (jdike@karaya.com) + * Licensed under the GPL + */ + +#ifndef __CHAN_USER_H__ +#define __CHAN_USER_H__ + +#include <init.h> + +struct chan_opts { + void (*const announce)(char *dev_name, int dev); + char *xterm_title; + const int raw; +}; + +struct chan_ops { + char *type; + void *(*init)(char *, int, const struct chan_opts *); + int (*open)(int, int, int, void *, char **); + void (*close)(int, void *); + int (*read)(int, char *, void *); + int (*write)(int, const char *, int, void *); + int (*console_write)(int, const char *, int); + int (*window_size)(int, void *, unsigned short *, unsigned short *); + void (*free)(void *); + int winch; +}; + +extern const struct chan_ops fd_ops, null_ops, port_ops, pts_ops, pty_ops, + tty_ops, xterm_ops; + +extern void generic_close(int fd, void *unused); +extern int generic_read(int fd, char *c_out, void *unused); +extern int generic_write(int fd, const char *buf, int n, void *unused); +extern int generic_console_write(int fd, const char *buf, int n); +extern int generic_window_size(int fd, void *unused, unsigned short *rows_out, + unsigned short *cols_out); +extern void generic_free(void *data); + +struct tty_port; +extern void register_winch(int fd, struct tty_port *port); +extern void register_winch_irq(int fd, int tty_fd, int pid, + struct tty_port *port, unsigned long stack); + +#define __channel_help(fn, prefix) \ +__uml_help(fn, prefix "[0-9]*=<channel description>\n" \ +" Attach a console or serial line to a host channel. See\n" \ +" http://user-mode-linux.sourceforge.net/old/input.html for a complete\n" \ +" description of this switch.\n\n" \ +); + +#endif diff --git a/arch/um/drivers/cow.h b/arch/um/drivers/cow.h new file mode 100644 index 000000000..6673508f3 --- /dev/null +++ b/arch/um/drivers/cow.h @@ -0,0 +1,32 @@ +#ifndef __COW_H__ +#define __COW_H__ + +#include <asm/types.h> + +extern int init_cow_file(int fd, char *cow_file, char *backing_file, + int sectorsize, int alignment, int *bitmap_offset_out, + unsigned long *bitmap_len_out, int *data_offset_out); + +extern int file_reader(__u64 offset, char *buf, int len, void *arg); +extern int read_cow_header(int (*reader)(__u64, char *, int, void *), + void *arg, __u32 *version_out, + char **backing_file_out, time_t *mtime_out, + unsigned long long *size_out, int *sectorsize_out, + __u32 *align_out, int *bitmap_offset_out); + +extern int write_cow_header(char *cow_file, int fd, char *backing_file, + int sectorsize, int alignment, + unsigned long long *size); + +extern void cow_sizes(int version, __u64 size, int sectorsize, int align, + int bitmap_offset, unsigned long *bitmap_len_out, + int *data_offset_out); + +#endif + +/* + * --------------------------------------------------------------------------- + * Local variables: + * c-file-style: "linux" + * End: + */ diff --git a/arch/um/drivers/cow_sys.h b/arch/um/drivers/cow_sys.h new file mode 100644 index 000000000..67cbee63e --- /dev/null +++ b/arch/um/drivers/cow_sys.h @@ -0,0 +1,40 @@ +#ifndef __COW_SYS_H__ +#define __COW_SYS_H__ + +#include <kern_util.h> +#include <os.h> +#include <um_malloc.h> + +static inline void *cow_malloc(int size) +{ + return uml_kmalloc(size, UM_GFP_KERNEL); +} + +static inline void cow_free(void *ptr) +{ + kfree(ptr); +} + +#define cow_printf printk + +static inline char *cow_strdup(char *str) +{ + return uml_strdup(str); +} + +static inline int cow_seek_file(int fd, __u64 offset) +{ + return os_seek_file(fd, offset); +} + +static inline int cow_file_size(char *file, unsigned long long *size_out) +{ + return os_file_size(file, size_out); +} + +static inline int cow_write_file(int fd, void *buf, int size) +{ + return os_write_file(fd, buf, size); +} + +#endif diff --git a/arch/um/drivers/cow_user.c b/arch/um/drivers/cow_user.c new file mode 100644 index 000000000..0ee9cc6cc --- /dev/null +++ b/arch/um/drivers/cow_user.c @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com) + * Licensed under the GPL + */ + +/* + * _XOPEN_SOURCE is needed for pread, but we define _GNU_SOURCE, which defines + * that. + */ +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <arpa/inet.h> +#include <endian.h> +#include "cow.h" +#include "cow_sys.h" + +#define PATH_LEN_V1 256 + +typedef __u32 time32_t; + +struct cow_header_v1 { + __s32 magic; + __s32 version; + char backing_file[PATH_LEN_V1]; + time32_t mtime; + __u64 size; + __s32 sectorsize; +} __attribute__((packed)); + +/* + * Define PATH_LEN_V3 as the usual value of MAXPATHLEN, just hard-code it in + * case other systems have different values for MAXPATHLEN. + * + * The same must hold for V2 - we want file format compatibility, not anything + * else. + */ +#define PATH_LEN_V3 4096 +#define PATH_LEN_V2 PATH_LEN_V3 + +struct cow_header_v2 { + __u32 magic; + __u32 version; + char backing_file[PATH_LEN_V2]; + time32_t mtime; + __u64 size; + __s32 sectorsize; +} __attribute__((packed)); + +/* + * Changes from V2 - + * PATH_LEN_V3 as described above + * Explicitly specify field bit lengths for systems with different + * lengths for the usual C types. Not sure whether char or + * time_t should be changed, this can be changed later without + * breaking compatibility + * Add alignment field so that different alignments can be used for the + * bitmap and data + * Add cow_format field to allow for the possibility of different ways + * of specifying the COW blocks. For now, the only value is 0, + * for the traditional COW bitmap. + * Move the backing_file field to the end of the header. This allows + * for the possibility of expanding it into the padding required + * by the bitmap alignment. + * The bitmap and data portions of the file will be aligned as specified + * by the alignment field. This is to allow COW files to be + * put on devices with restrictions on access alignments, such as + * /dev/raw, with a 512 byte alignment restriction. This also + * allows the data to be more aligned more strictly than on + * sector boundaries. This is needed for ubd-mmap, which needs + * the data to be page aligned. + * Fixed (finally!) the rounding bug + */ + +/* + * Until Dec2005, __attribute__((packed)) was left out from the below + * definition, leading on 64-bit systems to 4 bytes of padding after mtime, to + * align size to 8-byte alignment. This shifted all fields above (no padding + * was present on 32-bit, no other padding was added). + * + * However, this _can be detected_: it means that cow_format (always 0 until + * now) is shifted onto the first 4 bytes of backing_file, where it is otherwise + * impossible to find 4 zeros. -bb */ + +struct cow_header_v3 { + __u32 magic; + __u32 version; + __u32 mtime; + __u64 size; + __u32 sectorsize; + __u32 alignment; + __u32 cow_format; + char backing_file[PATH_LEN_V3]; +} __attribute__((packed)); + +/* This is the broken layout used by some 64-bit binaries. */ +struct cow_header_v3_broken { + __u32 magic; + __u32 version; + __s64 mtime; + __u64 size; + __u32 sectorsize; + __u32 alignment; + __u32 cow_format; + char backing_file[PATH_LEN_V3]; +}; + +/* COW format definitions - for now, we have only the usual COW bitmap */ +#define COW_BITMAP 0 + +union cow_header { + struct cow_header_v1 v1; + struct cow_header_v2 v2; + struct cow_header_v3 v3; + struct cow_header_v3_broken v3_b; +}; + +#define COW_MAGIC 0x4f4f4f4d /* MOOO */ +#define COW_VERSION 3 + +#define DIV_ROUND(x, len) (((x) + (len) - 1) / (len)) +#define ROUND_UP(x, align) DIV_ROUND(x, align) * (align) + +void cow_sizes(int version, __u64 size, int sectorsize, int align, + int bitmap_offset, unsigned long *bitmap_len_out, + int *data_offset_out) +{ + if (version < 3) { + *bitmap_len_out = (size + sectorsize - 1) / (8 * sectorsize); + + *data_offset_out = bitmap_offset + *bitmap_len_out; + *data_offset_out = (*data_offset_out + sectorsize - 1) / + sectorsize; + *data_offset_out *= sectorsize; + } + else { + *bitmap_len_out = DIV_ROUND(size, sectorsize); + *bitmap_len_out = DIV_ROUND(*bitmap_len_out, 8); + + *data_offset_out = bitmap_offset + *bitmap_len_out; + *data_offset_out = ROUND_UP(*data_offset_out, align); + } +} + +static int absolutize(char *to, int size, char *from) +{ + char save_cwd[256], *slash; + int remaining; + + if (getcwd(save_cwd, sizeof(save_cwd)) == NULL) { + cow_printf("absolutize : unable to get cwd - errno = %d\n", + errno); + return -1; + } + slash = strrchr(from, '/'); + if (slash != NULL) { + *slash = '\0'; + if (chdir(from)) { + *slash = '/'; + cow_printf("absolutize : Can't cd to '%s' - " + "errno = %d\n", from, errno); + return -1; + } + *slash = '/'; + if (getcwd(to, size) == NULL) { + cow_printf("absolutize : unable to get cwd of '%s' - " + "errno = %d\n", from, errno); + return -1; + } + remaining = size - strlen(to); + if (strlen(slash) + 1 > remaining) { + cow_printf("absolutize : unable to fit '%s' into %d " + "chars\n", from, size); + return -1; + } + strcat(to, slash); + } + else { + if (strlen(save_cwd) + 1 + strlen(from) + 1 > size) { + cow_printf("absolutize : unable to fit '%s' into %d " + "chars\n", from, size); + return -1; + } + strcpy(to, save_cwd); + strcat(to, "/"); + strcat(to, from); + } + if (chdir(save_cwd)) { + cow_printf("absolutize : Can't cd to '%s' - " + "errno = %d\n", save_cwd, errno); + return -1; + } + return 0; +} + +int write_cow_header(char *cow_file, int fd, char *backing_file, + int sectorsize, int alignment, unsigned long long *size) +{ + struct cow_header_v3 *header; + unsigned long modtime; + int err; + + err = cow_seek_file(fd, 0); + if (err < 0) { + cow_printf("write_cow_header - lseek failed, err = %d\n", -err); + goto out; + } + + err = -ENOMEM; + header = cow_malloc(sizeof(*header)); + if (header == NULL) { + cow_printf("write_cow_header - failed to allocate COW V3 " + "header\n"); + goto out; + } + header->magic = htobe32(COW_MAGIC); + header->version = htobe32(COW_VERSION); + + err = -EINVAL; + if (strlen(backing_file) > sizeof(header->backing_file) - 1) { + /* Below, %zd is for a size_t value */ + cow_printf("Backing file name \"%s\" is too long - names are " + "limited to %zd characters\n", backing_file, + sizeof(header->backing_file) - 1); + goto out_free; + } + + if (absolutize(header->backing_file, sizeof(header->backing_file), + backing_file)) + goto out_free; + + err = os_file_modtime(header->backing_file, &modtime); + if (err < 0) { + cow_printf("write_cow_header - backing file '%s' mtime " + "request failed, err = %d\n", header->backing_file, + -err); + goto out_free; + } + + err = cow_file_size(header->backing_file, size); + if (err < 0) { + cow_printf("write_cow_header - couldn't get size of " + "backing file '%s', err = %d\n", + header->backing_file, -err); + goto out_free; + } + + header->mtime = htobe32(modtime); + header->size = htobe64(*size); + header->sectorsize = htobe32(sectorsize); + header->alignment = htobe32(alignment); + header->cow_format = COW_BITMAP; + + err = cow_write_file(fd, header, sizeof(*header)); + if (err != sizeof(*header)) { + cow_printf("write_cow_header - write of header to " + "new COW file '%s' failed, err = %d\n", cow_file, + -err); + goto out_free; + } + err = 0; + out_free: + cow_free(header); + out: + return err; +} + +int file_reader(__u64 offset, char *buf, int len, void *arg) +{ + int fd = *((int *) arg); + + return pread(fd, buf, len, offset); +} + +/* XXX Need to sanity-check the values read from the header */ + +int read_cow_header(int (*reader)(__u64, char *, int, void *), void *arg, + __u32 *version_out, char **backing_file_out, + time_t *mtime_out, unsigned long long *size_out, + int *sectorsize_out, __u32 *align_out, + int *bitmap_offset_out) +{ + union cow_header *header; + char *file; + int err, n; + unsigned long version, magic; + + header = cow_malloc(sizeof(*header)); + if (header == NULL) { + cow_printf("read_cow_header - Failed to allocate header\n"); + return -ENOMEM; + } + err = -EINVAL; + n = (*reader)(0, (char *) header, sizeof(*header), arg); + if (n < offsetof(typeof(header->v1), backing_file)) { + cow_printf("read_cow_header - short header\n"); + goto out; + } + + magic = header->v1.magic; + if (magic == COW_MAGIC) + version = header->v1.version; + else if (magic == be32toh(COW_MAGIC)) + version = be32toh(header->v1.version); + /* No error printed because the non-COW case comes through here */ + else goto out; + + *version_out = version; + + if (version == 1) { + if (n < sizeof(header->v1)) { + cow_printf("read_cow_header - failed to read V1 " + "header\n"); + goto out; + } + *mtime_out = header->v1.mtime; + *size_out = header->v1.size; + *sectorsize_out = header->v1.sectorsize; + *bitmap_offset_out = sizeof(header->v1); + *align_out = *sectorsize_out; + file = header->v1.backing_file; + } + else if (version == 2) { + if (n < sizeof(header->v2)) { + cow_printf("read_cow_header - failed to read V2 " + "header\n"); + goto out; + } + *mtime_out = be32toh(header->v2.mtime); + *size_out = be64toh(header->v2.size); + *sectorsize_out = be32toh(header->v2.sectorsize); + *bitmap_offset_out = sizeof(header->v2); + *align_out = *sectorsize_out; + file = header->v2.backing_file; + } + /* This is very subtle - see above at union cow_header definition */ + else if (version == 3 && (*((int*)header->v3.backing_file) != 0)) { + if (n < sizeof(header->v3)) { + cow_printf("read_cow_header - failed to read V3 " + "header\n"); + goto out; + } + *mtime_out = be32toh(header->v3.mtime); + *size_out = be64toh(header->v3.size); + *sectorsize_out = be32toh(header->v3.sectorsize); + *align_out = be32toh(header->v3.alignment); + if (*align_out == 0) { + cow_printf("read_cow_header - invalid COW header, " + "align == 0\n"); + } + *bitmap_offset_out = ROUND_UP(sizeof(header->v3), *align_out); + file = header->v3.backing_file; + } + else if (version == 3) { + cow_printf("read_cow_header - broken V3 file with" + " 64-bit layout - recovering content.\n"); + + if (n < sizeof(header->v3_b)) { + cow_printf("read_cow_header - failed to read V3 " + "header\n"); + goto out; + } + + /* + * this was used until Dec2005 - 64bits are needed to represent + * 2038+. I.e. we can safely do this truncating cast. + * + * Additionally, we must use be32toh() instead of be64toh(), since + * the program used to use the former (tested - I got mtime + * mismatch "0 vs whatever"). + * + * Ever heard about bug-to-bug-compatibility ? ;-) */ + *mtime_out = (time32_t) be32toh(header->v3_b.mtime); + + *size_out = be64toh(header->v3_b.size); + *sectorsize_out = be32toh(header->v3_b.sectorsize); + *align_out = be32toh(header->v3_b.alignment); + if (*align_out == 0) { + cow_printf("read_cow_header - invalid COW header, " + "align == 0\n"); + } + *bitmap_offset_out = ROUND_UP(sizeof(header->v3_b), *align_out); + file = header->v3_b.backing_file; + } + else { + cow_printf("read_cow_header - invalid COW version\n"); + goto out; + } + err = -ENOMEM; + *backing_file_out = cow_strdup(file); + if (*backing_file_out == NULL) { + cow_printf("read_cow_header - failed to allocate backing " + "file\n"); + goto out; + } + err = 0; + out: + cow_free(header); + return err; +} + +int init_cow_file(int fd, char *cow_file, char *backing_file, int sectorsize, + int alignment, int *bitmap_offset_out, + unsigned long *bitmap_len_out, int *data_offset_out) +{ + unsigned long long size, offset; + char zero = 0; + int err; + + err = write_cow_header(cow_file, fd, backing_file, sectorsize, + alignment, &size); + if (err) + goto out; + + *bitmap_offset_out = ROUND_UP(sizeof(struct cow_header_v3), alignment); + cow_sizes(COW_VERSION, size, sectorsize, alignment, *bitmap_offset_out, + bitmap_len_out, data_offset_out); + + offset = *data_offset_out + size - sizeof(zero); + err = cow_seek_file(fd, offset); + if (err < 0) { + cow_printf("cow bitmap lseek failed : err = %d\n", -err); + goto out; + } + + /* + * does not really matter how much we write it is just to set EOF + * this also sets the entire COW bitmap + * to zero without having to allocate it + */ + err = cow_write_file(fd, &zero, sizeof(zero)); + if (err != sizeof(zero)) { + cow_printf("Write of bitmap to new COW file '%s' failed, " + "err = %d\n", cow_file, -err); + if (err >= 0) + err = -EINVAL; + goto out; + } + + return 0; + out: + return err; +} diff --git a/arch/um/drivers/daemon.h b/arch/um/drivers/daemon.h new file mode 100644 index 000000000..c2dd19515 --- /dev/null +++ b/arch/um/drivers/daemon.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL + */ + +#ifndef __DAEMON_H__ +#define __DAEMON_H__ + +#include <net_user.h> + +#define SWITCH_VERSION 3 + +struct daemon_data { + char *sock_type; + char *ctl_sock; + void *ctl_addr; + void *data_addr; + void *local_addr; + int fd; + int control; + void *dev; +}; + +extern const struct net_user_info daemon_user_info; + +extern int daemon_user_write(int fd, void *buf, int len, + struct daemon_data *pri); + +#endif diff --git a/arch/um/drivers/daemon_kern.c b/arch/um/drivers/daemon_kern.c new file mode 100644 index 000000000..7568cc2f3 --- /dev/null +++ b/arch/um/drivers/daemon_kern.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2001 Lennert Buytenhek (buytenh@gnu.org) and + * James Leu (jleu@mindspring.net). + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Copyright (C) 2001 by various other people who didn't put their name here. + * Licensed under the GPL. + */ + +#include <linux/init.h> +#include <linux/netdevice.h> +#include <net_kern.h> +#include "daemon.h" + +struct daemon_init { + char *sock_type; + char *ctl_sock; +}; + +static void daemon_init(struct net_device *dev, void *data) +{ + struct uml_net_private *pri; + struct daemon_data *dpri; + struct daemon_init *init = data; + + pri = netdev_priv(dev); + dpri = (struct daemon_data *) pri->user; + dpri->sock_type = init->sock_type; + dpri->ctl_sock = init->ctl_sock; + dpri->fd = -1; + dpri->control = -1; + dpri->dev = dev; + /* We will free this pointer. If it contains crap we're burned. */ + dpri->ctl_addr = NULL; + dpri->data_addr = NULL; + dpri->local_addr = NULL; + + printk("daemon backend (uml_switch version %d) - %s:%s", + SWITCH_VERSION, dpri->sock_type, dpri->ctl_sock); + printk("\n"); +} + +static int daemon_read(int fd, struct sk_buff *skb, struct uml_net_private *lp) +{ + return net_recvfrom(fd, skb_mac_header(skb), + skb->dev->mtu + ETH_HEADER_OTHER); +} + +static int daemon_write(int fd, struct sk_buff *skb, struct uml_net_private *lp) +{ + return daemon_user_write(fd, skb->data, skb->len, + (struct daemon_data *) &lp->user); +} + +static const struct net_kern_info daemon_kern_info = { + .init = daemon_init, + .protocol = eth_protocol, + .read = daemon_read, + .write = daemon_write, +}; + +static int daemon_setup(char *str, char **mac_out, void *data) +{ + struct daemon_init *init = data; + char *remain; + + *init = ((struct daemon_init) + { .sock_type = "unix", + .ctl_sock = "/tmp/uml.ctl" }); + + remain = split_if_spec(str, mac_out, &init->sock_type, &init->ctl_sock, + NULL); + if (remain != NULL) + printk(KERN_WARNING "daemon_setup : Ignoring data socket " + "specification\n"); + + return 1; +} + +static struct transport daemon_transport = { + .list = LIST_HEAD_INIT(daemon_transport.list), + .name = "daemon", + .setup = daemon_setup, + .user = &daemon_user_info, + .kern = &daemon_kern_info, + .private_size = sizeof(struct daemon_data), + .setup_size = sizeof(struct daemon_init), +}; + +static int register_daemon(void) +{ + register_transport(&daemon_transport); + return 0; +} + +late_initcall(register_daemon); diff --git a/arch/um/drivers/daemon_user.c b/arch/um/drivers/daemon_user.c new file mode 100644 index 000000000..8813c10d0 --- /dev/null +++ b/arch/um/drivers/daemon_user.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Copyright (C) 2001 Lennert Buytenhek (buytenh@gnu.org) and + * James Leu (jleu@mindspring.net). + * Copyright (C) 2001 by various other people who didn't put their name here. + * Licensed under the GPL. + */ + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/un.h> +#include "daemon.h" +#include <net_user.h> +#include <os.h> +#include <um_malloc.h> + +enum request_type { REQ_NEW_CONTROL }; + +#define SWITCH_MAGIC 0xfeedface + +struct request_v3 { + uint32_t magic; + uint32_t version; + enum request_type type; + struct sockaddr_un sock; +}; + +static struct sockaddr_un *new_addr(void *name, int len) +{ + struct sockaddr_un *sun; + + sun = uml_kmalloc(sizeof(struct sockaddr_un), UM_GFP_KERNEL); + if (sun == NULL) { + printk(UM_KERN_ERR "new_addr: allocation of sockaddr_un " + "failed\n"); + return NULL; + } + sun->sun_family = AF_UNIX; + memcpy(sun->sun_path, name, len); + return sun; +} + +static int connect_to_switch(struct daemon_data *pri) +{ + struct sockaddr_un *ctl_addr = pri->ctl_addr; + struct sockaddr_un *local_addr = pri->local_addr; + struct sockaddr_un *sun; + struct request_v3 req; + int fd, n, err; + + pri->control = socket(AF_UNIX, SOCK_STREAM, 0); + if (pri->control < 0) { + err = -errno; + printk(UM_KERN_ERR "daemon_open : control socket failed, " + "errno = %d\n", -err); + return err; + } + + if (connect(pri->control, (struct sockaddr *) ctl_addr, + sizeof(*ctl_addr)) < 0) { + err = -errno; + printk(UM_KERN_ERR "daemon_open : control connect failed, " + "errno = %d\n", -err); + goto out; + } + + fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (fd < 0) { + err = -errno; + printk(UM_KERN_ERR "daemon_open : data socket failed, " + "errno = %d\n", -err); + goto out; + } + if (bind(fd, (struct sockaddr *) local_addr, sizeof(*local_addr)) < 0) { + err = -errno; + printk(UM_KERN_ERR "daemon_open : data bind failed, " + "errno = %d\n", -err); + goto out_close; + } + + sun = uml_kmalloc(sizeof(struct sockaddr_un), UM_GFP_KERNEL); + if (sun == NULL) { + printk(UM_KERN_ERR "new_addr: allocation of sockaddr_un " + "failed\n"); + err = -ENOMEM; + goto out_close; + } + + req.magic = SWITCH_MAGIC; + req.version = SWITCH_VERSION; + req.type = REQ_NEW_CONTROL; + req.sock = *local_addr; + n = write(pri->control, &req, sizeof(req)); + if (n != sizeof(req)) { + printk(UM_KERN_ERR "daemon_open : control setup request " + "failed, err = %d\n", -errno); + err = -ENOTCONN; + goto out_free; + } + + n = read(pri->control, sun, sizeof(*sun)); + if (n != sizeof(*sun)) { + printk(UM_KERN_ERR "daemon_open : read of data socket failed, " + "err = %d\n", -errno); + err = -ENOTCONN; + goto out_free; + } + + pri->data_addr = sun; + return fd; + + out_free: + kfree(sun); + out_close: + close(fd); + out: + close(pri->control); + return err; +} + +static int daemon_user_init(void *data, void *dev) +{ + struct daemon_data *pri = data; + struct timeval tv; + struct { + char zero; + int pid; + int usecs; + } name; + + if (!strcmp(pri->sock_type, "unix")) + pri->ctl_addr = new_addr(pri->ctl_sock, + strlen(pri->ctl_sock) + 1); + name.zero = 0; + name.pid = os_getpid(); + gettimeofday(&tv, NULL); + name.usecs = tv.tv_usec; + pri->local_addr = new_addr(&name, sizeof(name)); + pri->dev = dev; + pri->fd = connect_to_switch(pri); + if (pri->fd < 0) { + kfree(pri->local_addr); + pri->local_addr = NULL; + return pri->fd; + } + + return 0; +} + +static int daemon_open(void *data) +{ + struct daemon_data *pri = data; + return pri->fd; +} + +static void daemon_remove(void *data) +{ + struct daemon_data *pri = data; + + close(pri->fd); + pri->fd = -1; + close(pri->control); + pri->control = -1; + + kfree(pri->data_addr); + pri->data_addr = NULL; + kfree(pri->ctl_addr); + pri->ctl_addr = NULL; + kfree(pri->local_addr); + pri->local_addr = NULL; +} + +int daemon_user_write(int fd, void *buf, int len, struct daemon_data *pri) +{ + struct sockaddr_un *data_addr = pri->data_addr; + + return net_sendto(fd, buf, len, data_addr, sizeof(*data_addr)); +} + +const struct net_user_info daemon_user_info = { + .init = daemon_user_init, + .open = daemon_open, + .close = NULL, + .remove = daemon_remove, + .add_address = NULL, + .delete_address = NULL, + .mtu = ETH_MAX_PACKET, + .max_packet = ETH_MAX_PACKET + ETH_HEADER_OTHER, +}; diff --git a/arch/um/drivers/fd.c b/arch/um/drivers/fd.c new file mode 100644 index 000000000..a13a427b9 --- /dev/null +++ b/arch/um/drivers/fd.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com) + * Licensed under the GPL + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <termios.h> +#include "chan_user.h" +#include <os.h> +#include <um_malloc.h> + +struct fd_chan { + int fd; + int raw; + struct termios tt; + char str[sizeof("1234567890\0")]; +}; + +static void *fd_init(char *str, int device, const struct chan_opts *opts) +{ + struct fd_chan *data; + char *end; + int n; + + if (*str != ':') { + printk(UM_KERN_ERR "fd_init : channel type 'fd' must specify a " + "file descriptor\n"); + return NULL; + } + str++; + n = strtoul(str, &end, 0); + if ((*end != '\0') || (end == str)) { + printk(UM_KERN_ERR "fd_init : couldn't parse file descriptor " + "'%s'\n", str); + return NULL; + } + + data = uml_kmalloc(sizeof(*data), UM_GFP_KERNEL); + if (data == NULL) + return NULL; + + *data = ((struct fd_chan) { .fd = n, + .raw = opts->raw }); + return data; +} + +static int fd_open(int input, int output, int primary, void *d, char **dev_out) +{ + struct fd_chan *data = d; + int err; + + if (data->raw && isatty(data->fd)) { + CATCH_EINTR(err = tcgetattr(data->fd, &data->tt)); + if (err) + return err; + + err = raw(data->fd); + if (err) + return err; + } + sprintf(data->str, "%d", data->fd); + *dev_out = data->str; + return data->fd; +} + +static void fd_close(int fd, void *d) +{ + struct fd_chan *data = d; + int err; + + if (!data->raw || !isatty(fd)) + return; + + CATCH_EINTR(err = tcsetattr(fd, TCSAFLUSH, &data->tt)); + if (err) + printk(UM_KERN_ERR "Failed to restore terminal state - " + "errno = %d\n", -err); + data->raw = 0; +} + +const struct chan_ops fd_ops = { + .type = "fd", + .init = fd_init, + .open = fd_open, + .close = fd_close, + .read = generic_read, + .write = generic_write, + .console_write = generic_console_write, + .window_size = generic_window_size, + .free = generic_free, + .winch = 1, +}; diff --git a/arch/um/drivers/harddog_kern.c b/arch/um/drivers/harddog_kern.c new file mode 100644 index 000000000..2d0266d02 --- /dev/null +++ b/arch/um/drivers/harddog_kern.c @@ -0,0 +1,201 @@ +/* UML hardware watchdog, shamelessly stolen from: + * + * SoftDog 0.05: A Software Watchdog Device + * + * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. + * http://www.redhat.com + * + * 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. + * + * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide + * warranty for any of this software. This material is provided + * "AS-IS" and at no charge. + * + * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk> + * + * Software only watchdog driver. Unlike its big brother the WDT501P + * driver this won't always recover a failed machine. + * + * 03/96: Angelo Haritsis <ah@doc.ic.ac.uk> : + * Modularised. + * Added soft_margin; use upon insmod to change the timer delay. + * NB: uses same minor as wdt (WATCHDOG_MINOR); we could use separate + * minors. + * + * 19980911 Alan Cox + * Made SMP safe for 2.3.x + * + * 20011127 Joel Becker (jlbec@evilplan.org> + * Added soft_noboot; Allows testing the softdog trigger without + * requiring a recompile. + * Added WDIOC_GETTIMEOUT and WDIOC_SETTIMOUT. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/reboot.h> +#include <linux/mutex.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <asm/uaccess.h> +#include "mconsole.h" + +MODULE_LICENSE("GPL"); + +static DEFINE_MUTEX(harddog_mutex); +static DEFINE_SPINLOCK(lock); +static int timer_alive; +static int harddog_in_fd = -1; +static int harddog_out_fd = -1; + +/* + * Allow only one person to hold it open + */ + +extern int start_watchdog(int *in_fd_ret, int *out_fd_ret, char *sock); + +static int harddog_open(struct inode *inode, struct file *file) +{ + int err = -EBUSY; + char *sock = NULL; + + mutex_lock(&harddog_mutex); + spin_lock(&lock); + if(timer_alive) + goto err; +#ifdef CONFIG_WATCHDOG_NOWAYOUT + __module_get(THIS_MODULE); +#endif + +#ifdef CONFIG_MCONSOLE + sock = mconsole_notify_socket(); +#endif + err = start_watchdog(&harddog_in_fd, &harddog_out_fd, sock); + if(err) + goto err; + + timer_alive = 1; + spin_unlock(&lock); + mutex_unlock(&harddog_mutex); + return nonseekable_open(inode, file); +err: + spin_unlock(&lock); + mutex_unlock(&harddog_mutex); + return err; +} + +extern void stop_watchdog(int in_fd, int out_fd); + +static int harddog_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + */ + + spin_lock(&lock); + + stop_watchdog(harddog_in_fd, harddog_out_fd); + harddog_in_fd = -1; + harddog_out_fd = -1; + + timer_alive=0; + spin_unlock(&lock); + + return 0; +} + +extern int ping_watchdog(int fd); + +static ssize_t harddog_write(struct file *file, const char __user *data, size_t len, + loff_t *ppos) +{ + /* + * Refresh the timer. + */ + if(len) + return ping_watchdog(harddog_out_fd); + return 0; +} + +static int harddog_ioctl_unlocked(struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp= (void __user *)arg; + static struct watchdog_info ident = { + WDIOC_SETTIMEOUT, + 0, + "UML Hardware Watchdog" + }; + switch (cmd) { + default: + return -ENOTTY; + case WDIOC_GETSUPPORT: + if(copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + return 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0,(int __user *)argp); + case WDIOC_KEEPALIVE: + return ping_watchdog(harddog_out_fd); + } +} + +static long harddog_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + long ret; + + mutex_lock(&harddog_mutex); + ret = harddog_ioctl_unlocked(file, cmd, arg); + mutex_unlock(&harddog_mutex); + + return ret; +} + +static const struct file_operations harddog_fops = { + .owner = THIS_MODULE, + .write = harddog_write, + .unlocked_ioctl = harddog_ioctl, + .open = harddog_open, + .release = harddog_release, + .llseek = no_llseek, +}; + +static struct miscdevice harddog_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &harddog_fops, +}; + +static char banner[] __initdata = KERN_INFO "UML Watchdog Timer\n"; + +static int __init harddog_init(void) +{ + int ret; + + ret = misc_register(&harddog_miscdev); + + if (ret) + return ret; + + printk(banner); + + return 0; +} + +static void __exit harddog_exit(void) +{ + misc_deregister(&harddog_miscdev); +} + +module_init(harddog_init); +module_exit(harddog_exit); diff --git a/arch/um/drivers/harddog_user.c b/arch/um/drivers/harddog_user.c new file mode 100644 index 000000000..f99b32a4d --- /dev/null +++ b/arch/um/drivers/harddog_user.c @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL + */ + +#include <stdio.h> +#include <unistd.h> +#include <errno.h> +#include <os.h> + +struct dog_data { + int stdin; + int stdout; + int close_me[2]; +}; + +static void pre_exec(void *d) +{ + struct dog_data *data = d; + + dup2(data->stdin, 0); + dup2(data->stdout, 1); + dup2(data->stdout, 2); + close(data->stdin); + close(data->stdout); + close(data->close_me[0]); + close(data->close_me[1]); +} + +int start_watchdog(int *in_fd_ret, int *out_fd_ret, char *sock) +{ + struct dog_data data; + int in_fds[2], out_fds[2], pid, n, err; + char pid_buf[sizeof("nnnnnnn\0")], c; + char *pid_args[] = { "/usr/bin/uml_watchdog", "-pid", pid_buf, NULL }; + char *mconsole_args[] = { "/usr/bin/uml_watchdog", "-mconsole", NULL, + NULL }; + char **args = NULL; + + err = os_pipe(in_fds, 1, 0); + if (err < 0) { + printk("harddog_open - os_pipe failed, err = %d\n", -err); + goto out; + } + + err = os_pipe(out_fds, 1, 0); + if (err < 0) { + printk("harddog_open - os_pipe failed, err = %d\n", -err); + goto out_close_in; + } + + data.stdin = out_fds[0]; + data.stdout = in_fds[1]; + data.close_me[0] = out_fds[1]; + data.close_me[1] = in_fds[0]; + + if (sock != NULL) { + mconsole_args[2] = sock; + args = mconsole_args; + } + else { + /* XXX The os_getpid() is not SMP correct */ + sprintf(pid_buf, "%d", os_getpid()); + args = pid_args; + } + + pid = run_helper(pre_exec, &data, args); + + close(out_fds[0]); + close(in_fds[1]); + + if (pid < 0) { + err = -pid; + printk("harddog_open - run_helper failed, errno = %d\n", -err); + goto out_close_out; + } + + n = read(in_fds[0], &c, sizeof(c)); + if (n == 0) { + printk("harddog_open - EOF on watchdog pipe\n"); + helper_wait(pid); + err = -EIO; + goto out_close_out; + } + else if (n < 0) { + printk("harddog_open - read of watchdog pipe failed, " + "err = %d\n", errno); + helper_wait(pid); + err = n; + goto out_close_out; + } + *in_fd_ret = in_fds[0]; + *out_fd_ret = out_fds[1]; + return 0; + + out_close_in: + close(in_fds[0]); + close(in_fds[1]); + out_close_out: + close(out_fds[0]); + close(out_fds[1]); + out: + return err; +} + +void stop_watchdog(int in_fd, int out_fd) +{ + close(in_fd); + close(out_fd); +} + +int ping_watchdog(int fd) +{ + int n; + char c = '\n'; + + n = write(fd, &c, sizeof(c)); + if (n != sizeof(c)) { + printk("ping_watchdog - write failed, ret = %d, err = %d\n", + n, errno); + if (n < 0) + return n; + return -EIO; + } + return 1; + +} diff --git a/arch/um/drivers/hostaudio_kern.c b/arch/um/drivers/hostaudio_kern.c new file mode 100644 index 000000000..9b90fdc4b --- /dev/null +++ b/arch/um/drivers/hostaudio_kern.c @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2002 Steve Schmidtke + * Licensed under the GPL + */ + +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/sound.h> +#include <linux/soundcard.h> +#include <linux/mutex.h> +#include <asm/uaccess.h> +#include <init.h> +#include <os.h> + +struct hostaudio_state { + int fd; +}; + +struct hostmixer_state { + int fd; +}; + +#define HOSTAUDIO_DEV_DSP "/dev/sound/dsp" +#define HOSTAUDIO_DEV_MIXER "/dev/sound/mixer" + +/* + * Changed either at boot time or module load time. At boot, this is + * single-threaded; at module load, multiple modules would each have + * their own copy of these variables. + */ +static char *dsp = HOSTAUDIO_DEV_DSP; +static char *mixer = HOSTAUDIO_DEV_MIXER; + +#define DSP_HELP \ +" This is used to specify the host dsp device to the hostaudio driver.\n" \ +" The default is \"" HOSTAUDIO_DEV_DSP "\".\n\n" + +#define MIXER_HELP \ +" This is used to specify the host mixer device to the hostaudio driver.\n"\ +" The default is \"" HOSTAUDIO_DEV_MIXER "\".\n\n" + +module_param(dsp, charp, 0644); +MODULE_PARM_DESC(dsp, DSP_HELP); +module_param(mixer, charp, 0644); +MODULE_PARM_DESC(mixer, MIXER_HELP); + +#ifndef MODULE +static int set_dsp(char *name, int *add) +{ + dsp = name; + return 0; +} + +__uml_setup("dsp=", set_dsp, "dsp=<dsp device>\n" DSP_HELP); + +static int set_mixer(char *name, int *add) +{ + mixer = name; + return 0; +} + +__uml_setup("mixer=", set_mixer, "mixer=<mixer device>\n" MIXER_HELP); +#endif + +static DEFINE_MUTEX(hostaudio_mutex); + +/* /dev/dsp file operations */ + +static ssize_t hostaudio_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct hostaudio_state *state = file->private_data; + void *kbuf; + int err; + +#ifdef DEBUG + printk(KERN_DEBUG "hostaudio: read called, count = %d\n", count); +#endif + + kbuf = kmalloc(count, GFP_KERNEL); + if (kbuf == NULL) + return -ENOMEM; + + err = os_read_file(state->fd, kbuf, count); + if (err < 0) + goto out; + + if (copy_to_user(buffer, kbuf, err)) + err = -EFAULT; + +out: + kfree(kbuf); + return err; +} + +static ssize_t hostaudio_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct hostaudio_state *state = file->private_data; + void *kbuf; + int err; + +#ifdef DEBUG + printk(KERN_DEBUG "hostaudio: write called, count = %d\n", count); +#endif + + kbuf = kmalloc(count, GFP_KERNEL); + if (kbuf == NULL) + return -ENOMEM; + + err = -EFAULT; + if (copy_from_user(kbuf, buffer, count)) + goto out; + + err = os_write_file(state->fd, kbuf, count); + if (err < 0) + goto out; + *ppos += err; + + out: + kfree(kbuf); + return err; +} + +static unsigned int hostaudio_poll(struct file *file, + struct poll_table_struct *wait) +{ + unsigned int mask = 0; + +#ifdef DEBUG + printk(KERN_DEBUG "hostaudio: poll called (unimplemented)\n"); +#endif + + return mask; +} + +static long hostaudio_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct hostaudio_state *state = file->private_data; + unsigned long data = 0; + int err; + +#ifdef DEBUG + printk(KERN_DEBUG "hostaudio: ioctl called, cmd = %u\n", cmd); +#endif + switch(cmd){ + case SNDCTL_DSP_SPEED: + case SNDCTL_DSP_STEREO: + case SNDCTL_DSP_GETBLKSIZE: + case SNDCTL_DSP_CHANNELS: + case SNDCTL_DSP_SUBDIVIDE: + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(data, (int __user *) arg)) + return -EFAULT; + break; + default: + break; + } + + err = os_ioctl_generic(state->fd, cmd, (unsigned long) &data); + + switch(cmd){ + case SNDCTL_DSP_SPEED: + case SNDCTL_DSP_STEREO: + case SNDCTL_DSP_GETBLKSIZE: + case SNDCTL_DSP_CHANNELS: + case SNDCTL_DSP_SUBDIVIDE: + case SNDCTL_DSP_SETFRAGMENT: + if (put_user(data, (int __user *) arg)) + return -EFAULT; + break; + default: + break; + } + + return err; +} + +static int hostaudio_open(struct inode *inode, struct file *file) +{ + struct hostaudio_state *state; + int r = 0, w = 0; + int ret; + +#ifdef DEBUG + kparam_block_sysfs_write(dsp); + printk(KERN_DEBUG "hostaudio: open called (host: %s)\n", dsp); + kparam_unblock_sysfs_write(dsp); +#endif + + state = kmalloc(sizeof(struct hostaudio_state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + + if (file->f_mode & FMODE_READ) + r = 1; + if (file->f_mode & FMODE_WRITE) + w = 1; + + kparam_block_sysfs_write(dsp); + mutex_lock(&hostaudio_mutex); + ret = os_open_file(dsp, of_set_rw(OPENFLAGS(), r, w), 0); + mutex_unlock(&hostaudio_mutex); + kparam_unblock_sysfs_write(dsp); + + if (ret < 0) { + kfree(state); + return ret; + } + state->fd = ret; + file->private_data = state; + return 0; +} + +static int hostaudio_release(struct inode *inode, struct file *file) +{ + struct hostaudio_state *state = file->private_data; + +#ifdef DEBUG + printk(KERN_DEBUG "hostaudio: release called\n"); +#endif + os_close_file(state->fd); + kfree(state); + + return 0; +} + +/* /dev/mixer file operations */ + +static long hostmixer_ioctl_mixdev(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct hostmixer_state *state = file->private_data; + +#ifdef DEBUG + printk(KERN_DEBUG "hostmixer: ioctl called\n"); +#endif + + return os_ioctl_generic(state->fd, cmd, arg); +} + +static int hostmixer_open_mixdev(struct inode *inode, struct file *file) +{ + struct hostmixer_state *state; + int r = 0, w = 0; + int ret; + +#ifdef DEBUG + printk(KERN_DEBUG "hostmixer: open called (host: %s)\n", mixer); +#endif + + state = kmalloc(sizeof(struct hostmixer_state), GFP_KERNEL); + if (state == NULL) + return -ENOMEM; + + if (file->f_mode & FMODE_READ) + r = 1; + if (file->f_mode & FMODE_WRITE) + w = 1; + + kparam_block_sysfs_write(mixer); + mutex_lock(&hostaudio_mutex); + ret = os_open_file(mixer, of_set_rw(OPENFLAGS(), r, w), 0); + mutex_unlock(&hostaudio_mutex); + kparam_unblock_sysfs_write(mixer); + + if (ret < 0) { + kparam_block_sysfs_write(dsp); + printk(KERN_ERR "hostaudio_open_mixdev failed to open '%s', " + "err = %d\n", dsp, -ret); + kparam_unblock_sysfs_write(dsp); + kfree(state); + return ret; + } + + file->private_data = state; + return 0; +} + +static int hostmixer_release(struct inode *inode, struct file *file) +{ + struct hostmixer_state *state = file->private_data; + +#ifdef DEBUG + printk(KERN_DEBUG "hostmixer: release called\n"); +#endif + + os_close_file(state->fd); + kfree(state); + + return 0; +} + +/* kernel module operations */ + +static const struct file_operations hostaudio_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = hostaudio_read, + .write = hostaudio_write, + .poll = hostaudio_poll, + .unlocked_ioctl = hostaudio_ioctl, + .mmap = NULL, + .open = hostaudio_open, + .release = hostaudio_release, +}; + +static const struct file_operations hostmixer_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = hostmixer_ioctl_mixdev, + .open = hostmixer_open_mixdev, + .release = hostmixer_release, +}; + +struct { + int dev_audio; + int dev_mixer; +} module_data; + +MODULE_AUTHOR("Steve Schmidtke"); +MODULE_DESCRIPTION("UML Audio Relay"); +MODULE_LICENSE("GPL"); + +static int __init hostaudio_init_module(void) +{ + __kernel_param_lock(); + printk(KERN_INFO "UML Audio Relay (host dsp = %s, host mixer = %s)\n", + dsp, mixer); + __kernel_param_unlock(); + + module_data.dev_audio = register_sound_dsp(&hostaudio_fops, -1); + if (module_data.dev_audio < 0) { + printk(KERN_ERR "hostaudio: couldn't register DSP device!\n"); + return -ENODEV; + } + + module_data.dev_mixer = register_sound_mixer(&hostmixer_fops, -1); + if (module_data.dev_mixer < 0) { + printk(KERN_ERR "hostmixer: couldn't register mixer " + "device!\n"); + unregister_sound_dsp(module_data.dev_audio); + return -ENODEV; + } + + return 0; +} + +static void __exit hostaudio_cleanup_module (void) +{ + unregister_sound_mixer(module_data.dev_mixer); + unregister_sound_dsp(module_data.dev_audio); +} + +module_init(hostaudio_init_module); +module_exit(hostaudio_cleanup_module); diff --git a/arch/um/drivers/line.c b/arch/um/drivers/line.c new file mode 100644 index 000000000..62087028a --- /dev/null +++ b/arch/um/drivers/line.c @@ -0,0 +1,768 @@ +/* + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL + */ + +#include <linux/irqreturn.h> +#include <linux/kd.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include "chan.h" +#include <irq_kern.h> +#include <irq_user.h> +#include <kern_util.h> +#include <os.h> + +#define LINE_BUFSIZE 4096 + +static irqreturn_t line_interrupt(int irq, void *data) +{ + struct chan *chan = data; + struct line *line = chan->line; + + if (line) + chan_interrupt(line, irq); + + return IRQ_HANDLED; +} + +/* + * Returns the free space inside the ring buffer of this line. + * + * Should be called while holding line->lock (this does not modify data). + */ +static int write_room(struct line *line) +{ + int n; + + if (line->buffer == NULL) + return LINE_BUFSIZE - 1; + + /* This is for the case where the buffer is wrapped! */ + n = line->head - line->tail; + + if (n <= 0) + n += LINE_BUFSIZE; /* The other case */ + return n - 1; +} + +int line_write_room(struct tty_struct *tty) +{ + struct line *line = tty->driver_data; + unsigned long flags; + int room; + + spin_lock_irqsave(&line->lock, flags); + room = write_room(line); + spin_unlock_irqrestore(&line->lock, flags); + + return room; +} + +int line_chars_in_buffer(struct tty_struct *tty) +{ + struct line *line = tty->driver_data; + unsigned long flags; + int ret; + + spin_lock_irqsave(&line->lock, flags); + /* write_room subtracts 1 for the needed NULL, so we readd it.*/ + ret = LINE_BUFSIZE - (write_room(line) + 1); + spin_unlock_irqrestore(&line->lock, flags); + + return ret; +} + +/* + * This copies the content of buf into the circular buffer associated with + * this line. + * The return value is the number of characters actually copied, i.e. the ones + * for which there was space: this function is not supposed to ever flush out + * the circular buffer. + * + * Must be called while holding line->lock! + */ +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(KERN_ERR "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 { + /* The circular buffer is wrapping */ + memcpy(line->tail, buf, end); + buf += end; + memcpy(line->buffer, buf, len - end); + line->tail = line->buffer + len - end; + } + + return len; +} + +/* + * Flushes the ring buffer to the output channels. That is, write_chan is + * called, passing it line->head as buffer, and an appropriate count. + * + * On exit, returns 1 when the buffer is empty, + * 0 when the buffer is not empty on exit, + * and -errno when an error occurred. + * + * Must be called while holding line->lock!*/ +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) { + /* line->buffer + LINE_BUFSIZE is the end of the buffer! */ + count = line->buffer + LINE_BUFSIZE - line->head; + + n = write_chan(line->chan_out, line->head, count, + line->driver->write_irq); + if (n < 0) + return n; + if (n == count) { + /* + * We have flushed from ->head to buffer end, now we + * must flush only from the beginning to ->tail. + */ + line->head = line->buffer; + } else { + line->head += n; + return 0; + } + } + + count = line->tail - line->head; + n = write_chan(line->chan_out, line->head, count, + line->driver->write_irq); + + if (n < 0) + return n; + + line->head += n; + return line->head == line->tail; +} + +void line_flush_buffer(struct tty_struct *tty) +{ + struct line *line = tty->driver_data; + unsigned long flags; + + spin_lock_irqsave(&line->lock, flags); + flush_buffer(line); + spin_unlock_irqrestore(&line->lock, flags); +} + +/* + * We map both ->flush_chars and ->put_char (which go in pair) onto + * ->flush_buffer and ->write. Hope it's not that bad. + */ +void line_flush_chars(struct tty_struct *tty) +{ + line_flush_buffer(tty); +} + +int line_put_char(struct tty_struct *tty, unsigned char ch) +{ + return line_write(tty, &ch, sizeof(ch)); +} + +int line_write(struct tty_struct *tty, const unsigned char *buf, int len) +{ + struct line *line = tty->driver_data; + unsigned long flags; + int n, ret = 0; + + spin_lock_irqsave(&line->lock, flags); + if (line->head != line->tail) + ret = buffer_data(line, buf, len); + else { + n = write_chan(line->chan_out, 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: + spin_unlock_irqrestore(&line->lock, flags); + return ret; +} + +void line_set_termios(struct tty_struct *tty, struct ktermios * old) +{ + /* nothing */ +} + +void line_throttle(struct tty_struct *tty) +{ + struct line *line = tty->driver_data; + + deactivate_chan(line->chan_in, line->driver->read_irq); + line->throttled = 1; +} + +void line_unthrottle(struct tty_struct *tty) +{ + struct line *line = tty->driver_data; + + line->throttled = 0; + chan_interrupt(line, line->driver->read_irq); + + /* + * Maybe there is enough stuff pending that calling the interrupt + * throttles us again. In this case, line->throttled will be 1 + * again and we shouldn't turn the interrupt back on. + */ + if (!line->throttled) + reactivate_chan(line->chan_in, line->driver->read_irq); +} + +static irqreturn_t line_write_interrupt(int irq, void *data) +{ + struct chan *chan = data; + struct line *line = chan->line; + int err; + + /* + * Interrupts are disabled here because genirq keep irqs disabled when + * calling the action handler. + */ + + spin_lock(&line->lock); + err = flush_buffer(line); + if (err == 0) { + spin_unlock(&line->lock); + return IRQ_NONE; + } else if (err < 0) { + line->head = line->buffer; + line->tail = line->buffer; + } + spin_unlock(&line->lock); + + tty_port_tty_wakeup(&line->port); + + return IRQ_HANDLED; +} + +int line_setup_irq(int fd, int input, int output, struct line *line, void *data) +{ + const struct line_driver *driver = line->driver; + int err = 0; + + if (input) + err = um_request_irq(driver->read_irq, fd, IRQ_READ, + line_interrupt, IRQF_SHARED, + driver->read_irq_name, data); + if (err) + return err; + if (output) + err = um_request_irq(driver->write_irq, fd, IRQ_WRITE, + line_write_interrupt, IRQF_SHARED, + driver->write_irq_name, data); + return err; +} + +static int line_activate(struct tty_port *port, struct tty_struct *tty) +{ + int ret; + struct line *line = tty->driver_data; + + ret = enable_chan(line); + if (ret) + return ret; + + if (!line->sigio) { + chan_enable_winch(line->chan_out, port); + line->sigio = 1; + } + + chan_window_size(line, &tty->winsize.ws_row, + &tty->winsize.ws_col); + + return 0; +} + +static void unregister_winch(struct tty_struct *tty); + +static void line_destruct(struct tty_port *port) +{ + struct tty_struct *tty = tty_port_tty_get(port); + struct line *line = tty->driver_data; + + if (line->sigio) { + unregister_winch(tty); + line->sigio = 0; + } +} + +static const struct tty_port_operations line_port_ops = { + .activate = line_activate, + .destruct = line_destruct, +}; + +int line_open(struct tty_struct *tty, struct file *filp) +{ + struct line *line = tty->driver_data; + + return tty_port_open(&line->port, tty, filp); +} + +int line_install(struct tty_driver *driver, struct tty_struct *tty, + struct line *line) +{ + int ret; + + ret = tty_standard_install(driver, tty); + if (ret) + return ret; + + tty->driver_data = line; + + return 0; +} + +void line_close(struct tty_struct *tty, struct file * filp) +{ + struct line *line = tty->driver_data; + + tty_port_close(&line->port, tty, filp); +} + +void line_hangup(struct tty_struct *tty) +{ + struct line *line = tty->driver_data; + + tty_port_hangup(&line->port); +} + +void close_lines(struct line *lines, int nlines) +{ + int i; + + for(i = 0; i < nlines; i++) + close_chan(&lines[i]); +} + +int setup_one_line(struct line *lines, int n, char *init, + const struct chan_opts *opts, char **error_out) +{ + struct line *line = &lines[n]; + struct tty_driver *driver = line->driver->driver; + int err = -EINVAL; + + if (line->port.count) { + *error_out = "Device is already open"; + goto out; + } + + if (!strcmp(init, "none")) { + if (line->valid) { + line->valid = 0; + kfree(line->init_str); + tty_unregister_device(driver, n); + parse_chan_pair(NULL, line, n, opts, error_out); + err = 0; + } + } else { + char *new = kstrdup(init, GFP_KERNEL); + if (!new) { + *error_out = "Failed to allocate memory"; + return -ENOMEM; + } + if (line->valid) { + tty_unregister_device(driver, n); + kfree(line->init_str); + } + line->init_str = new; + line->valid = 1; + err = parse_chan_pair(new, line, n, opts, error_out); + if (!err) { + struct device *d = tty_port_register_device(&line->port, + driver, n, NULL); + if (IS_ERR(d)) { + *error_out = "Failed to register device"; + err = PTR_ERR(d); + parse_chan_pair(NULL, line, n, opts, error_out); + } + } + if (err) { + line->init_str = NULL; + line->valid = 0; + kfree(new); + } + } +out: + return err; +} + +/* + * Common setup code for both startup command line and mconsole initialization. + * @lines contains the array (of size @num) to modify; + * @init is the setup string; + * @error_out is an error string in the case of failure; + */ + +int line_setup(char **conf, unsigned int num, char **def, + char *init, char *name) +{ + char *error; + + if (*init == '=') { + /* + * We said con=/ssl= instead of con#=, so we are configuring all + * consoles at once. + */ + *def = init + 1; + } else { + char *end; + unsigned n = simple_strtoul(init, &end, 0); + + if (*end != '=') { + error = "Couldn't parse device number"; + goto out; + } + if (n >= num) { + error = "Device number out of range"; + goto out; + } + conf[n] = end + 1; + } + return 0; + +out: + printk(KERN_ERR "Failed to set up %s with " + "configuration string \"%s\" : %s\n", name, init, error); + return -EINVAL; +} + +int line_config(struct line *lines, unsigned int num, char *str, + const struct chan_opts *opts, char **error_out) +{ + char *end; + int n; + + if (*str == '=') { + *error_out = "Can't configure all devices from mconsole"; + return -EINVAL; + } + + n = simple_strtoul(str, &end, 0); + if (*end++ != '=') { + *error_out = "Couldn't parse device number"; + return -EINVAL; + } + if (n >= num) { + *error_out = "Device number out of range"; + return -EINVAL; + } + + return setup_one_line(lines, n, end, opts, error_out); +} + +int line_get_config(char *name, struct line *lines, unsigned 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 out of range"; + return 0; + } + + line = &lines[dev]; + + if (!line->valid) + CONFIG_CHUNK(str, size, n, "none", 1); + else { + struct tty_struct *tty = tty_port_tty_get(&line->port); + if (tty == NULL) { + CONFIG_CHUNK(str, size, n, line->init_str, 1); + } else { + n = chan_config_string(line, str, size, error_out); + tty_kref_put(tty); + } + } + + return n; +} + +int line_id(char **str, int *start_out, int *end_out) +{ + char *end; + int n; + + n = simple_strtoul(*str, &end, 0); + if ((*end != '\0') || (end == *str)) + return -1; + + *str = end; + *start_out = n; + *end_out = n; + return n; +} + +int line_remove(struct line *lines, unsigned int num, int n, char **error_out) +{ + if (n >= num) { + *error_out = "Device number out of range"; + return -EINVAL; + } + return setup_one_line(lines, n, "none", NULL, error_out); +} + +int register_lines(struct line_driver *line_driver, + const struct tty_operations *ops, + struct line *lines, int nlines) +{ + struct tty_driver *driver = alloc_tty_driver(nlines); + int err; + int i; + + if (!driver) + return -ENOMEM; + + driver->driver_name = line_driver->name; + driver->name = line_driver->device_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 | TTY_DRIVER_DYNAMIC_DEV; + driver->init_termios = tty_std_termios; + + for (i = 0; i < nlines; i++) { + tty_port_init(&lines[i].port); + lines[i].port.ops = &line_port_ops; + spin_lock_init(&lines[i].lock); + lines[i].driver = line_driver; + INIT_LIST_HEAD(&lines[i].chan_list); + } + tty_set_operations(driver, ops); + + err = tty_register_driver(driver); + if (err) { + printk(KERN_ERR "register_lines : can't register %s driver\n", + line_driver->name); + put_tty_driver(driver); + for (i = 0; i < nlines; i++) + tty_port_destroy(&lines[i].port); + return err; + } + + line_driver->driver = driver; + mconsole_register_dev(&line_driver->mc); + return 0; +} + +static DEFINE_SPINLOCK(winch_handler_lock); +static LIST_HEAD(winch_handlers); + +struct winch { + struct list_head list; + int fd; + int tty_fd; + int pid; + struct tty_port *port; + unsigned long stack; + struct work_struct work; +}; + +static void __free_winch(struct work_struct *work) +{ + struct winch *winch = container_of(work, struct winch, work); + um_free_irq(WINCH_IRQ, winch); + + if (winch->pid != -1) + os_kill_process(winch->pid, 1); + if (winch->stack != 0) + free_stack(winch->stack, 0); + kfree(winch); +} + +static void free_winch(struct winch *winch) +{ + int fd = winch->fd; + winch->fd = -1; + if (fd != -1) + os_close_file(fd); + list_del(&winch->list); + __free_winch(&winch->work); +} + +static irqreturn_t winch_interrupt(int irq, void *data) +{ + struct winch *winch = data; + struct tty_struct *tty; + struct line *line; + int fd = winch->fd; + int err; + char c; + struct pid *pgrp; + + if (fd != -1) { + err = generic_read(fd, &c, NULL); + if (err < 0) { + if (err != -EAGAIN) { + winch->fd = -1; + list_del(&winch->list); + os_close_file(fd); + printk(KERN_ERR "winch_interrupt : " + "read failed, errno = %d\n", -err); + printk(KERN_ERR "fd %d is losing SIGWINCH " + "support\n", winch->tty_fd); + INIT_WORK(&winch->work, __free_winch); + schedule_work(&winch->work); + return IRQ_HANDLED; + } + goto out; + } + } + tty = tty_port_tty_get(winch->port); + if (tty != NULL) { + line = tty->driver_data; + if (line != NULL) { + chan_window_size(line, &tty->winsize.ws_row, + &tty->winsize.ws_col); + pgrp = tty_get_pgrp(tty); + if (pgrp) + kill_pgrp(pgrp, SIGWINCH, 1); + put_pid(pgrp); + } + tty_kref_put(tty); + } + out: + if (winch->fd != -1) + reactivate_fd(winch->fd, WINCH_IRQ); + return IRQ_HANDLED; +} + +void register_winch_irq(int fd, int tty_fd, int pid, struct tty_port *port, + unsigned long stack) +{ + struct winch *winch; + + winch = kmalloc(sizeof(*winch), GFP_KERNEL); + if (winch == NULL) { + printk(KERN_ERR "register_winch_irq - kmalloc failed\n"); + goto cleanup; + } + + *winch = ((struct winch) { .list = LIST_HEAD_INIT(winch->list), + .fd = fd, + .tty_fd = tty_fd, + .pid = pid, + .port = port, + .stack = stack }); + + if (um_request_irq(WINCH_IRQ, fd, IRQ_READ, winch_interrupt, + IRQF_SHARED, "winch", winch) < 0) { + printk(KERN_ERR "register_winch_irq - failed to register " + "IRQ\n"); + goto out_free; + } + + spin_lock(&winch_handler_lock); + list_add(&winch->list, &winch_handlers); + spin_unlock(&winch_handler_lock); + + return; + + out_free: + kfree(winch); + cleanup: + os_kill_process(pid, 1); + os_close_file(fd); + if (stack != 0) + free_stack(stack, 0); +} + +static void unregister_winch(struct tty_struct *tty) +{ + struct list_head *ele, *next; + struct winch *winch; + struct tty_struct *wtty; + + spin_lock(&winch_handler_lock); + + list_for_each_safe(ele, next, &winch_handlers) { + winch = list_entry(ele, struct winch, list); + wtty = tty_port_tty_get(winch->port); + if (wtty == tty) { + free_winch(winch); + break; + } + tty_kref_put(wtty); + } + spin_unlock(&winch_handler_lock); +} + +static void winch_cleanup(void) +{ + struct list_head *ele, *next; + struct winch *winch; + + spin_lock(&winch_handler_lock); + + list_for_each_safe(ele, next, &winch_handlers) { + winch = list_entry(ele, struct winch, list); + free_winch(winch); + } + + spin_unlock(&winch_handler_lock); +} +__uml_exitcall(winch_cleanup); + +char *add_xterm_umid(char *base) +{ + char *umid, *title; + int len; + + umid = get_umid(); + if (*umid == '\0') + return base; + + len = strlen(base) + strlen(" ()") + strlen(umid) + 1; + title = kmalloc(len, GFP_KERNEL); + if (title == NULL) { + printk(KERN_ERR "Failed to allocate buffer for xterm title\n"); + return base; + } + + snprintf(title, len, "%s (%s)", base, umid); + return title; +} diff --git a/arch/um/drivers/line.h b/arch/um/drivers/line.h new file mode 100644 index 000000000..138a14526 --- /dev/null +++ b/arch/um/drivers/line.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2001, 2002 Jeff Dike (jdike@karaya.com) + * Licensed under the GPL + */ + +#ifndef __LINE_H__ +#define __LINE_H__ + +#include <linux/list.h> +#include <linux/workqueue.h> +#include <linux/tty.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/mutex.h> +#include "chan_user.h" +#include "mconsole_kern.h" + +/* There's only two modifiable fields in this - .mc.list and .driver */ +struct line_driver { + const char *name; + const char *device_name; + const short major; + const short minor_start; + const short type; + const short subtype; + const int read_irq; + const char *read_irq_name; + const int write_irq; + const char *write_irq_name; + struct mc_device mc; + struct tty_driver *driver; +}; + +struct line { + struct tty_port port; + int valid; + + char *init_str; + struct list_head chan_list; + struct chan *chan_in, *chan_out; + + /*This lock is actually, mostly, local to*/ + spinlock_t lock; + int throttled; + /* Yes, this is a real circular buffer. + * XXX: And this should become a struct kfifo! + * + * buffer points to a buffer allocated on demand, of length + * LINE_BUFSIZE, head to the start of the ring, tail to the end.*/ + char *buffer; + char *head; + char *tail; + + int sigio; + struct delayed_work task; + const struct line_driver *driver; +}; + +extern void line_close(struct tty_struct *tty, struct file * filp); +extern int line_open(struct tty_struct *tty, struct file *filp); +extern int line_install(struct tty_driver *driver, struct tty_struct *tty, + struct line *line); +extern void line_cleanup(struct tty_struct *tty); +extern void line_hangup(struct tty_struct *tty); +extern int line_setup(char **conf, unsigned nlines, char **def, + char *init, char *name); +extern int line_write(struct tty_struct *tty, const unsigned char *buf, + int len); +extern int line_put_char(struct tty_struct *tty, unsigned char ch); +extern void line_set_termios(struct tty_struct *tty, struct ktermios * old); +extern int line_chars_in_buffer(struct tty_struct *tty); +extern void line_flush_buffer(struct tty_struct *tty); +extern void line_flush_chars(struct tty_struct *tty); +extern int line_write_room(struct tty_struct *tty); +extern void line_throttle(struct tty_struct *tty); +extern void line_unthrottle(struct tty_struct *tty); + +extern char *add_xterm_umid(char *base); +extern int line_setup_irq(int fd, int input, int output, struct line *line, + void *data); +extern void line_close_chan(struct line *line); +extern int register_lines(struct line_driver *line_driver, + const struct tty_operations *driver, + struct line *lines, int nlines); +extern int setup_one_line(struct line *lines, int n, char *init, + const struct chan_opts *opts, char **error_out); +extern void close_lines(struct line *lines, int nlines); + +extern int line_config(struct line *lines, unsigned int sizeof_lines, + char *str, const struct chan_opts *opts, + char **error_out); +extern int line_id(char **str, int *start_out, int *end_out); +extern int line_remove(struct line *lines, unsigned int sizeof_lines, int n, + char **error_out); +extern int line_get_config(char *dev, struct line *lines, + unsigned int sizeof_lines, char *str, + int size, char **error_out); + +#endif diff --git a/arch/um/drivers/mconsole.h b/arch/um/drivers/mconsole.h new file mode 100644 index 000000000..8b22535c6 --- /dev/null +++ b/arch/um/drivers/mconsole.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2001 Lennert Buytenhek (buytenh@gnu.org) + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL + */ + +#ifndef __MCONSOLE_H__ +#define __MCONSOLE_H__ + +#ifndef __KERNEL__ +#include <stdint.h> +#define u32 uint32_t +#endif + +#include <sysdep/ptrace.h> + +#define MCONSOLE_MAGIC (0xcafebabe) +#define MCONSOLE_MAX_DATA (512) +#define MCONSOLE_VERSION 2 + +struct mconsole_request { + u32 magic; + u32 version; + u32 len; + char data[MCONSOLE_MAX_DATA]; +}; + +struct mconsole_reply { + u32 err; + u32 more; + u32 len; + char data[MCONSOLE_MAX_DATA]; +}; + +struct mconsole_notify { + u32 magic; + u32 version; + enum { MCONSOLE_SOCKET, MCONSOLE_PANIC, MCONSOLE_HANG, + MCONSOLE_USER_NOTIFY } type; + u32 len; + char data[MCONSOLE_MAX_DATA]; +}; + +struct mc_request; + +enum mc_context { MCONSOLE_INTR, MCONSOLE_PROC }; + +struct mconsole_command +{ + char *command; + void (*handler)(struct mc_request *req); + enum mc_context context; +}; + +struct mc_request +{ + int len; + int as_interrupt; + + int originating_fd; + unsigned int originlen; + unsigned char origin[128]; /* sockaddr_un */ + + struct mconsole_request request; + struct mconsole_command *cmd; + struct uml_pt_regs regs; +}; + +extern char mconsole_socket_name[]; + +extern int mconsole_unlink_socket(void); +extern int mconsole_reply_len(struct mc_request *req, const char *reply, + int len, int err, int more); +extern int mconsole_reply(struct mc_request *req, const char *str, int err, + int more); + +extern void mconsole_version(struct mc_request *req); +extern void mconsole_help(struct mc_request *req); +extern void mconsole_halt(struct mc_request *req); +extern void mconsole_reboot(struct mc_request *req); +extern void mconsole_config(struct mc_request *req); +extern void mconsole_remove(struct mc_request *req); +extern void mconsole_sysrq(struct mc_request *req); +extern void mconsole_cad(struct mc_request *req); +extern void mconsole_stop(struct mc_request *req); +extern void mconsole_go(struct mc_request *req); +extern void mconsole_log(struct mc_request *req); +extern void mconsole_proc(struct mc_request *req); +extern void mconsole_stack(struct mc_request *req); + +extern int mconsole_get_request(int fd, struct mc_request *req); +extern int mconsole_notify(char *sock_name, int type, const void *data, + int len); +extern char *mconsole_notify_socket(void); +extern void lock_notify(void); +extern void unlock_notify(void); + +#endif diff --git a/arch/um/drivers/mconsole_kern.c b/arch/um/drivers/mconsole_kern.c new file mode 100644 index 000000000..29880c9b3 --- /dev/null +++ b/arch/um/drivers/mconsole_kern.c @@ -0,0 +1,858 @@ +/* + * Copyright (C) 2001 Lennert Buytenhek (buytenh@gnu.org) + * Copyright (C) 2001 - 2008 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL + */ + +#include <linux/console.h> +#include <linux/ctype.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/proc_fs.h> +#include <linux/slab.h> +#include <linux/syscalls.h> +#include <linux/utsname.h> +#include <linux/socket.h> +#include <linux/un.h> +#include <linux/workqueue.h> +#include <linux/mutex.h> +#include <linux/fs.h> +#include <linux/mount.h> +#include <linux/file.h> +#include <asm/uaccess.h> +#include <asm/switch_to.h> + +#include <init.h> +#include <irq_kern.h> +#include <irq_user.h> +#include <kern_util.h> +#include "mconsole.h" +#include "mconsole_kern.h" +#include <os.h> + +static int do_unlink_socket(struct notifier_block *notifier, + unsigned long what, void *data) +{ + return mconsole_unlink_socket(); +} + + +static struct notifier_block reboot_notifier = { + .notifier_call = do_unlink_socket, + .priority = 0, +}; + +/* Safe without explicit locking for now. Tasklets provide their own + * locking, and the interrupt handler is safe because it can't interrupt + * itself and it can only happen on CPU 0. + */ + +static LIST_HEAD(mc_requests); + +static void mc_work_proc(struct work_struct *unused) +{ + struct mconsole_entry *req; + unsigned long flags; + + while (!list_empty(&mc_requests)) { + local_irq_save(flags); + req = list_entry(mc_requests.next, struct mconsole_entry, list); + list_del(&req->list); + local_irq_restore(flags); + req->request.cmd->handler(&req->request); + kfree(req); + } +} + +static DECLARE_WORK(mconsole_work, mc_work_proc); + +static irqreturn_t mconsole_interrupt(int irq, void *dev_id) +{ + /* long to avoid size mismatch warnings from gcc */ + long fd; + struct mconsole_entry *new; + static struct mc_request req; /* that's OK */ + + fd = (long) dev_id; + while (mconsole_get_request(fd, &req)) { + if (req.cmd->context == MCONSOLE_INTR) + (*req.cmd->handler)(&req); + else { + new = kmalloc(sizeof(*new), GFP_NOWAIT); + if (new == NULL) + mconsole_reply(&req, "Out of memory", 1, 0); + else { + new->request = req; + new->request.regs = get_irq_regs()->regs; + list_add(&new->list, &mc_requests); + } + } + } + if (!list_empty(&mc_requests)) + schedule_work(&mconsole_work); + reactivate_fd(fd, MCONSOLE_IRQ); + return IRQ_HANDLED; +} + +void mconsole_version(struct mc_request *req) +{ + char version[256]; + + sprintf(version, "%s %s %s %s %s", utsname()->sysname, + utsname()->nodename, utsname()->release, utsname()->version, + utsname()->machine); + mconsole_reply(req, version, 0, 0); +} + +void mconsole_log(struct mc_request *req) +{ + int len; + char *ptr = req->request.data; + + ptr += strlen("log "); + + len = req->len - (ptr - req->request.data); + printk(KERN_WARNING "%.*s", len, ptr); + mconsole_reply(req, "", 0, 0); +} + +void mconsole_proc(struct mc_request *req) +{ + struct vfsmount *mnt = task_active_pid_ns(current)->proc_mnt; + char *buf; + int len; + struct file *file; + int first_chunk = 1; + char *ptr = req->request.data; + + ptr += strlen("proc"); + ptr = skip_spaces(ptr); + + file = file_open_root(mnt->mnt_root, mnt, ptr, O_RDONLY); + if (IS_ERR(file)) { + mconsole_reply(req, "Failed to open file", 1, 0); + printk(KERN_ERR "open /proc/%s: %ld\n", ptr, PTR_ERR(file)); + goto out; + } + + buf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (buf == NULL) { + mconsole_reply(req, "Failed to allocate buffer", 1, 0); + goto out_fput; + } + + do { + loff_t pos = file->f_pos; + mm_segment_t old_fs = get_fs(); + set_fs(KERNEL_DS); + len = vfs_read(file, buf, PAGE_SIZE - 1, &pos); + set_fs(old_fs); + file->f_pos = pos; + if (len < 0) { + mconsole_reply(req, "Read of file failed", 1, 0); + goto out_free; + } + /* Begin the file content on his own line. */ + if (first_chunk) { + mconsole_reply(req, "\n", 0, 1); + first_chunk = 0; + } + buf[len] = '\0'; + mconsole_reply(req, buf, 0, (len > 0)); + } while (len > 0); + out_free: + kfree(buf); + out_fput: + fput(file); + out: ; +} + +#define UML_MCONSOLE_HELPTEXT \ +"Commands: \n\ + version - Get kernel version \n\ + help - Print this message \n\ + halt - Halt UML \n\ + reboot - Reboot UML \n\ + config <dev>=<config> - Add a new device to UML; \n\ + same syntax as command line \n\ + config <dev> - Query the configuration of a device \n\ + remove <dev> - Remove a device from UML \n\ + sysrq <letter> - Performs the SysRq action controlled by the letter \n\ + cad - invoke the Ctrl-Alt-Del handler \n\ + stop - pause the UML; it will do nothing until it receives a 'go' \n\ + go - continue the UML after a 'stop' \n\ + log <string> - make UML enter <string> into the kernel log\n\ + proc <file> - returns the contents of the UML's /proc/<file>\n\ + stack <pid> - returns the stack of the specified pid\n\ +" + +void mconsole_help(struct mc_request *req) +{ + mconsole_reply(req, UML_MCONSOLE_HELPTEXT, 0, 0); +} + +void mconsole_halt(struct mc_request *req) +{ + mconsole_reply(req, "", 0, 0); + machine_halt(); +} + +void mconsole_reboot(struct mc_request *req) +{ + mconsole_reply(req, "", 0, 0); + machine_restart(NULL); +} + +void mconsole_cad(struct mc_request *req) +{ + mconsole_reply(req, "", 0, 0); + ctrl_alt_del(); +} + +void mconsole_go(struct mc_request *req) +{ + mconsole_reply(req, "Not stopped", 1, 0); +} + +void mconsole_stop(struct mc_request *req) +{ + deactivate_fd(req->originating_fd, MCONSOLE_IRQ); + os_set_fd_block(req->originating_fd, 1); + mconsole_reply(req, "stopped", 0, 0); + for (;;) { + if (!mconsole_get_request(req->originating_fd, req)) + continue; + if (req->cmd->handler == mconsole_go) + break; + if (req->cmd->handler == mconsole_stop) { + mconsole_reply(req, "Already stopped", 1, 0); + continue; + } + if (req->cmd->handler == mconsole_sysrq) { + struct pt_regs *old_regs; + old_regs = set_irq_regs((struct pt_regs *)&req->regs); + mconsole_sysrq(req); + set_irq_regs(old_regs); + continue; + } + (*req->cmd->handler)(req); + } + os_set_fd_block(req->originating_fd, 0); + reactivate_fd(req->originating_fd, MCONSOLE_IRQ); + mconsole_reply(req, "", 0, 0); +} + +static DEFINE_SPINLOCK(mc_devices_lock); +static LIST_HEAD(mconsole_devices); + +void mconsole_register_dev(struct mc_device *new) +{ + spin_lock(&mc_devices_lock); + BUG_ON(!list_empty(&new->list)); + list_add(&new->list, &mconsole_devices); + spin_unlock(&mc_devices_lock); +} + +static struct mc_device *mconsole_find_dev(char *name) +{ + struct list_head *ele; + struct mc_device *dev; + + list_for_each(ele, &mconsole_devices) { + dev = list_entry(ele, struct mc_device, list); + if (!strncmp(name, dev->name, strlen(dev->name))) + return dev; + } + return NULL; +} + +#define UNPLUGGED_PER_PAGE \ + ((PAGE_SIZE - sizeof(struct list_head)) / sizeof(unsigned long)) + +struct unplugged_pages { + struct list_head list; + void *pages[UNPLUGGED_PER_PAGE]; +}; + +static DEFINE_MUTEX(plug_mem_mutex); +static unsigned long long unplugged_pages_count = 0; +static LIST_HEAD(unplugged_pages); +static int unplug_index = UNPLUGGED_PER_PAGE; + +static int mem_config(char *str, char **error_out) +{ + unsigned long long diff; + int err = -EINVAL, i, add; + char *ret; + + if (str[0] != '=') { + *error_out = "Expected '=' after 'mem'"; + goto out; + } + + str++; + if (str[0] == '-') + add = 0; + else if (str[0] == '+') { + add = 1; + } + else { + *error_out = "Expected increment to start with '-' or '+'"; + goto out; + } + + str++; + diff = memparse(str, &ret); + if (*ret != '\0') { + *error_out = "Failed to parse memory increment"; + goto out; + } + + diff /= PAGE_SIZE; + + mutex_lock(&plug_mem_mutex); + for (i = 0; i < diff; i++) { + struct unplugged_pages *unplugged; + void *addr; + + if (add) { + if (list_empty(&unplugged_pages)) + break; + + unplugged = list_entry(unplugged_pages.next, + struct unplugged_pages, list); + if (unplug_index > 0) + addr = unplugged->pages[--unplug_index]; + else { + list_del(&unplugged->list); + addr = unplugged; + unplug_index = UNPLUGGED_PER_PAGE; + } + + free_page((unsigned long) addr); + unplugged_pages_count--; + } + else { + struct page *page; + + page = alloc_page(GFP_ATOMIC); + if (page == NULL) + break; + + unplugged = page_address(page); + if (unplug_index == UNPLUGGED_PER_PAGE) { + list_add(&unplugged->list, &unplugged_pages); + unplug_index = 0; + } + else { + struct list_head *entry = unplugged_pages.next; + addr = unplugged; + + unplugged = list_entry(entry, + struct unplugged_pages, + list); + err = os_drop_memory(addr, PAGE_SIZE); + if (err) { + printk(KERN_ERR "Failed to release " + "memory - errno = %d\n", err); + *error_out = "Failed to release memory"; + goto out_unlock; + } + unplugged->pages[unplug_index++] = addr; + } + + unplugged_pages_count++; + } + } + + err = 0; +out_unlock: + mutex_unlock(&plug_mem_mutex); +out: + return err; +} + +static int mem_get_config(char *name, char *str, int size, char **error_out) +{ + char buf[sizeof("18446744073709551615")]; + int len = 0; + + sprintf(buf, "%ld", uml_physmem); + CONFIG_CHUNK(str, size, len, buf, 1); + + return len; +} + +static int mem_id(char **str, int *start_out, int *end_out) +{ + *start_out = 0; + *end_out = 0; + + return 0; +} + +static int mem_remove(int n, char **error_out) +{ + *error_out = "Memory doesn't support the remove operation"; + return -EBUSY; +} + +static struct mc_device mem_mc = { + .list = LIST_HEAD_INIT(mem_mc.list), + .name = "mem", + .config = mem_config, + .get_config = mem_get_config, + .id = mem_id, + .remove = mem_remove, +}; + +static int __init mem_mc_init(void) +{ + if (can_drop_memory()) + mconsole_register_dev(&mem_mc); + else printk(KERN_ERR "Can't release memory to the host - memory " + "hotplug won't be supported\n"); + return 0; +} + +__initcall(mem_mc_init); + +#define CONFIG_BUF_SIZE 64 + +static void mconsole_get_config(int (*get_config)(char *, char *, int, + char **), + struct mc_request *req, char *name) +{ + char default_buf[CONFIG_BUF_SIZE], *error, *buf; + int n, size; + + if (get_config == NULL) { + mconsole_reply(req, "No get_config routine defined", 1, 0); + return; + } + + error = NULL; + size = ARRAY_SIZE(default_buf); + buf = default_buf; + + while (1) { + n = (*get_config)(name, buf, size, &error); + if (error != NULL) { + mconsole_reply(req, error, 1, 0); + goto out; + } + + if (n <= size) { + mconsole_reply(req, buf, 0, 0); + goto out; + } + + if (buf != default_buf) + kfree(buf); + + size = n; + buf = kmalloc(size, GFP_KERNEL); + if (buf == NULL) { + mconsole_reply(req, "Failed to allocate buffer", 1, 0); + return; + } + } + out: + if (buf != default_buf) + kfree(buf); +} + +void mconsole_config(struct mc_request *req) +{ + struct mc_device *dev; + char *ptr = req->request.data, *name, *error_string = ""; + int err; + + ptr += strlen("config"); + ptr = skip_spaces(ptr); + dev = mconsole_find_dev(ptr); + if (dev == NULL) { + mconsole_reply(req, "Bad configuration option", 1, 0); + return; + } + + name = &ptr[strlen(dev->name)]; + ptr = name; + while ((*ptr != '=') && (*ptr != '\0')) + ptr++; + + if (*ptr == '=') { + err = (*dev->config)(name, &error_string); + mconsole_reply(req, error_string, err, 0); + } + else mconsole_get_config(dev->get_config, req, name); +} + +void mconsole_remove(struct mc_request *req) +{ + struct mc_device *dev; + char *ptr = req->request.data, *err_msg = ""; + char error[256]; + int err, start, end, n; + + ptr += strlen("remove"); + ptr = skip_spaces(ptr); + dev = mconsole_find_dev(ptr); + if (dev == NULL) { + mconsole_reply(req, "Bad remove option", 1, 0); + return; + } + + ptr = &ptr[strlen(dev->name)]; + + err = 1; + n = (*dev->id)(&ptr, &start, &end); + if (n < 0) { + err_msg = "Couldn't parse device number"; + goto out; + } + else if ((n < start) || (n > end)) { + sprintf(error, "Invalid device number - must be between " + "%d and %d", start, end); + err_msg = error; + goto out; + } + + err_msg = NULL; + err = (*dev->remove)(n, &err_msg); + switch(err) { + case 0: + err_msg = ""; + break; + case -ENODEV: + if (err_msg == NULL) + err_msg = "Device doesn't exist"; + break; + case -EBUSY: + if (err_msg == NULL) + err_msg = "Device is currently open"; + break; + default: + break; + } +out: + mconsole_reply(req, err_msg, err, 0); +} + +struct mconsole_output { + struct list_head list; + struct mc_request *req; +}; + +static DEFINE_SPINLOCK(client_lock); +static LIST_HEAD(clients); +static char console_buf[MCONSOLE_MAX_DATA]; + +static void console_write(struct console *console, const char *string, + unsigned int len) +{ + struct list_head *ele; + int n; + + if (list_empty(&clients)) + return; + + while (len > 0) { + n = min((size_t) len, ARRAY_SIZE(console_buf)); + strncpy(console_buf, string, n); + string += n; + len -= n; + + list_for_each(ele, &clients) { + struct mconsole_output *entry; + + entry = list_entry(ele, struct mconsole_output, list); + mconsole_reply_len(entry->req, console_buf, n, 0, 1); + } + } +} + +static struct console mc_console = { .name = "mc", + .write = console_write, + .flags = CON_ENABLED, + .index = -1 }; + +static int mc_add_console(void) +{ + register_console(&mc_console); + return 0; +} + +late_initcall(mc_add_console); + +static void with_console(struct mc_request *req, void (*proc)(void *), + void *arg) +{ + struct mconsole_output entry; + unsigned long flags; + + entry.req = req; + spin_lock_irqsave(&client_lock, flags); + list_add(&entry.list, &clients); + spin_unlock_irqrestore(&client_lock, flags); + + (*proc)(arg); + + mconsole_reply_len(req, "", 0, 0, 0); + + spin_lock_irqsave(&client_lock, flags); + list_del(&entry.list); + spin_unlock_irqrestore(&client_lock, flags); +} + +#ifdef CONFIG_MAGIC_SYSRQ + +#include <linux/sysrq.h> + +static void sysrq_proc(void *arg) +{ + char *op = arg; + handle_sysrq(*op); +} + +void mconsole_sysrq(struct mc_request *req) +{ + char *ptr = req->request.data; + + ptr += strlen("sysrq"); + ptr = skip_spaces(ptr); + + /* + * With 'b', the system will shut down without a chance to reply, + * so in this case, we reply first. + */ + if (*ptr == 'b') + mconsole_reply(req, "", 0, 0); + + with_console(req, sysrq_proc, ptr); +} +#else +void mconsole_sysrq(struct mc_request *req) +{ + mconsole_reply(req, "Sysrq not compiled in", 1, 0); +} +#endif + +static void stack_proc(void *arg) +{ + struct task_struct *task = arg; + + show_stack(task, NULL); +} + +/* + * Mconsole stack trace + * Added by Allan Graves, Jeff Dike + * Dumps a stacks registers to the linux console. + * Usage stack <pid>. + */ +void mconsole_stack(struct mc_request *req) +{ + char *ptr = req->request.data; + int pid_requested= -1; + struct task_struct *to = NULL; + + /* + * Would be nice: + * 1) Send showregs output to mconsole. + * 2) Add a way to stack dump all pids. + */ + + ptr += strlen("stack"); + ptr = skip_spaces(ptr); + + /* + * Should really check for multiple pids or reject bad args here + */ + /* What do the arguments in mconsole_reply mean? */ + if (sscanf(ptr, "%d", &pid_requested) == 0) { + mconsole_reply(req, "Please specify a pid", 1, 0); + return; + } + + to = find_task_by_pid_ns(pid_requested, &init_pid_ns); + if ((to == NULL) || (pid_requested == 0)) { + mconsole_reply(req, "Couldn't find that pid", 1, 0); + return; + } + with_console(req, stack_proc, to); +} + +/* + * Changed by mconsole_setup, which is __setup, and called before SMP is + * active. + */ +static char *notify_socket = NULL; + +static int __init mconsole_init(void) +{ + /* long to avoid size mismatch warnings from gcc */ + long sock; + int err; + char file[UNIX_PATH_MAX]; + + if (umid_file_name("mconsole", file, sizeof(file))) + return -1; + snprintf(mconsole_socket_name, sizeof(file), "%s", file); + + sock = os_create_unix_socket(file, sizeof(file), 1); + if (sock < 0) { + printk(KERN_ERR "Failed to initialize management console\n"); + return 1; + } + if (os_set_fd_block(sock, 0)) + goto out; + + register_reboot_notifier(&reboot_notifier); + + err = um_request_irq(MCONSOLE_IRQ, sock, IRQ_READ, mconsole_interrupt, + IRQF_SHARED, "mconsole", (void *)sock); + if (err) { + printk(KERN_ERR "Failed to get IRQ for management console\n"); + goto out; + } + + if (notify_socket != NULL) { + notify_socket = kstrdup(notify_socket, GFP_KERNEL); + if (notify_socket != NULL) + mconsole_notify(notify_socket, MCONSOLE_SOCKET, + mconsole_socket_name, + strlen(mconsole_socket_name) + 1); + else printk(KERN_ERR "mconsole_setup failed to strdup " + "string\n"); + } + + printk(KERN_INFO "mconsole (version %d) initialized on %s\n", + MCONSOLE_VERSION, mconsole_socket_name); + return 0; + + out: + os_close_file(sock); + return 1; +} + +__initcall(mconsole_init); + +static ssize_t mconsole_proc_write(struct file *file, + const char __user *buffer, size_t count, loff_t *pos) +{ + char *buf; + + buf = kmalloc(count + 1, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (copy_from_user(buf, buffer, count)) { + count = -EFAULT; + goto out; + } + + buf[count] = '\0'; + + mconsole_notify(notify_socket, MCONSOLE_USER_NOTIFY, buf, count); + out: + kfree(buf); + return count; +} + +static const struct file_operations mconsole_proc_fops = { + .owner = THIS_MODULE, + .write = mconsole_proc_write, + .llseek = noop_llseek, +}; + +static int create_proc_mconsole(void) +{ + struct proc_dir_entry *ent; + + if (notify_socket == NULL) + return 0; + + ent = proc_create("mconsole", 0200, NULL, &mconsole_proc_fops); + if (ent == NULL) { + printk(KERN_INFO "create_proc_mconsole : proc_create failed\n"); + return 0; + } + return 0; +} + +static DEFINE_SPINLOCK(notify_spinlock); + +void lock_notify(void) +{ + spin_lock(¬ify_spinlock); +} + +void unlock_notify(void) +{ + spin_unlock(¬ify_spinlock); +} + +__initcall(create_proc_mconsole); + +#define NOTIFY "notify:" + +static int mconsole_setup(char *str) +{ + if (!strncmp(str, NOTIFY, strlen(NOTIFY))) { + str += strlen(NOTIFY); + notify_socket = str; + } + else printk(KERN_ERR "mconsole_setup : Unknown option - '%s'\n", str); + return 1; +} + +__setup("mconsole=", mconsole_setup); + +__uml_help(mconsole_setup, +"mconsole=notify:<socket>\n" +" Requests that the mconsole driver send a message to the named Unix\n" +" socket containing the name of the mconsole socket. This also serves\n" +" to notify outside processes when UML has booted far enough to respond\n" +" to mconsole requests.\n\n" +); + +static int notify_panic(struct notifier_block *self, unsigned long unused1, + void *ptr) +{ + char *message = ptr; + + if (notify_socket == NULL) + return 0; + + mconsole_notify(notify_socket, MCONSOLE_PANIC, message, + strlen(message) + 1); + return 0; +} + +static struct notifier_block panic_exit_notifier = { + .notifier_call = notify_panic, + .next = NULL, + .priority = 1 +}; + +static int add_notifier(void) +{ + atomic_notifier_chain_register(&panic_notifier_list, + &panic_exit_notifier); + return 0; +} + +__initcall(add_notifier); + +char *mconsole_notify_socket(void) +{ + return notify_socket; +} + +EXPORT_SYMBOL(mconsole_notify_socket); diff --git a/arch/um/drivers/mconsole_kern.h b/arch/um/drivers/mconsole_kern.h new file mode 100644 index 000000000..7a0c6a1ad --- /dev/null +++ b/arch/um/drivers/mconsole_kern.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2001, 2002 Jeff Dike (jdike@karaya.com) + * Licensed under the GPL + */ + +#ifndef __MCONSOLE_KERN_H__ +#define __MCONSOLE_KERN_H__ + +#include <linux/list.h> +#include "mconsole.h" + +struct mconsole_entry { + struct list_head list; + struct mc_request request; +}; + +/* All these methods are called in process context. */ +struct mc_device { + struct list_head list; + char *name; + int (*config)(char *, char **); + int (*get_config)(char *, char *, int, char **); + int (*id)(char **, int *, int *); + int (*remove)(int, char **); +}; + +#define CONFIG_CHUNK(str, size, current, chunk, end) \ +do { \ + current += strlen(chunk); \ + if(current >= size) \ + str = NULL; \ + if(str != NULL){ \ + strcpy(str, chunk); \ + str += strlen(chunk); \ + } \ + if(end) \ + current++; \ +} while(0) + +#ifdef CONFIG_MCONSOLE + +extern void mconsole_register_dev(struct mc_device *new); + +#else + +static inline void mconsole_register_dev(struct mc_device *new) +{ +} + +#endif + +#endif diff --git a/arch/um/drivers/mconsole_user.c b/arch/um/drivers/mconsole_user.c new file mode 100644 index 000000000..99209826a --- /dev/null +++ b/arch/um/drivers/mconsole_user.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2001 Lennert Buytenhek (buytenh@gnu.org) + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL + */ + +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/un.h> +#include "mconsole.h" + +static struct mconsole_command commands[] = { + /* + * With uts namespaces, uts information becomes process-specific, so + * we need a process context. If we try handling this in interrupt + * context, we may hit an exiting process without a valid uts + * namespace. + */ + { "version", mconsole_version, MCONSOLE_PROC }, + { "halt", mconsole_halt, MCONSOLE_PROC }, + { "reboot", mconsole_reboot, MCONSOLE_PROC }, + { "config", mconsole_config, MCONSOLE_PROC }, + { "remove", mconsole_remove, MCONSOLE_PROC }, + { "sysrq", mconsole_sysrq, MCONSOLE_INTR }, + { "help", mconsole_help, MCONSOLE_INTR }, + { "cad", mconsole_cad, MCONSOLE_INTR }, + { "stop", mconsole_stop, MCONSOLE_PROC }, + { "go", mconsole_go, MCONSOLE_INTR }, + { "log", mconsole_log, MCONSOLE_INTR }, + { "proc", mconsole_proc, MCONSOLE_PROC }, + { "stack", mconsole_stack, MCONSOLE_INTR }, +}; + +/* Initialized in mconsole_init, which is an initcall */ +char mconsole_socket_name[256]; + +static int mconsole_reply_v0(struct mc_request *req, char *reply) +{ + struct iovec iov; + struct msghdr msg; + + iov.iov_base = reply; + iov.iov_len = strlen(reply); + + msg.msg_name = &(req->origin); + msg.msg_namelen = req->originlen; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + return sendmsg(req->originating_fd, &msg, 0); +} + +static struct mconsole_command *mconsole_parse(struct mc_request *req) +{ + struct mconsole_command *cmd; + int i; + + for (i = 0; i < ARRAY_SIZE(commands); i++) { + cmd = &commands[i]; + if (!strncmp(req->request.data, cmd->command, + strlen(cmd->command))) { + return cmd; + } + } + return NULL; +} + +#define MIN(a,b) ((a)<(b) ? (a):(b)) + +#define STRINGX(x) #x +#define STRING(x) STRINGX(x) + +int mconsole_get_request(int fd, struct mc_request *req) +{ + int len; + + req->originlen = sizeof(req->origin); + req->len = recvfrom(fd, &req->request, sizeof(req->request), 0, + (struct sockaddr *) req->origin, &req->originlen); + if (req->len < 0) + return 0; + + req->originating_fd = fd; + + if (req->request.magic != MCONSOLE_MAGIC) { + /* Unversioned request */ + len = MIN(sizeof(req->request.data) - 1, + strlen((char *) &req->request)); + memmove(req->request.data, &req->request, len); + req->request.data[len] = '\0'; + + req->request.magic = MCONSOLE_MAGIC; + req->request.version = 0; + req->request.len = len; + + mconsole_reply_v0(req, "ERR Version 0 mconsole clients are " + "not supported by this driver"); + return 0; + } + + if (req->request.len >= MCONSOLE_MAX_DATA) { + mconsole_reply(req, "Request too large", 1, 0); + return 0; + } + if (req->request.version != MCONSOLE_VERSION) { + mconsole_reply(req, "This driver only supports version " + STRING(MCONSOLE_VERSION) " clients", 1, 0); + } + + req->request.data[req->request.len] = '\0'; + req->cmd = mconsole_parse(req); + if (req->cmd == NULL) { + mconsole_reply(req, "Unknown command", 1, 0); + return 0; + } + + return 1; +} + +int mconsole_reply_len(struct mc_request *req, const char *str, int total, + int err, int more) +{ + /* + * XXX This is a stack consumption problem. It'd be nice to + * make it global and serialize access to it, but there are a + * ton of callers to this function. + */ + struct mconsole_reply reply; + int len, n; + + do { + reply.err = err; + + /* err can only be true on the first packet */ + err = 0; + + len = MIN(total, MCONSOLE_MAX_DATA - 1); + + if (len == total) reply.more = more; + else reply.more = 1; + + memcpy(reply.data, str, len); + reply.data[len] = '\0'; + total -= len; + str += len; + reply.len = len + 1; + + len = sizeof(reply) + reply.len - sizeof(reply.data); + + n = sendto(req->originating_fd, &reply, len, 0, + (struct sockaddr *) req->origin, req->originlen); + + if (n < 0) + return -errno; + } while (total > 0); + return 0; +} + +int mconsole_reply(struct mc_request *req, const char *str, int err, int more) +{ + return mconsole_reply_len(req, str, strlen(str), err, more); +} + + +int mconsole_unlink_socket(void) +{ + unlink(mconsole_socket_name); + return 0; +} + +static int notify_sock = -1; + +int mconsole_notify(char *sock_name, int type, const void *data, int len) +{ + struct sockaddr_un target; + struct mconsole_notify packet; + int n, err = 0; + + lock_notify(); + if (notify_sock < 0) { + notify_sock = socket(PF_UNIX, SOCK_DGRAM, 0); + if (notify_sock < 0) { + err = -errno; + printk(UM_KERN_ERR "mconsole_notify - socket failed, " + "errno = %d\n", errno); + } + } + unlock_notify(); + + if (err) + return err; + + target.sun_family = AF_UNIX; + strcpy(target.sun_path, sock_name); + + packet.magic = MCONSOLE_MAGIC; + packet.version = MCONSOLE_VERSION; + packet.type = type; + len = (len > sizeof(packet.data)) ? sizeof(packet.data) : len; + packet.len = len; + memcpy(packet.data, data, len); + + err = 0; + len = sizeof(packet) + packet.len - sizeof(packet.data); + n = sendto(notify_sock, &packet, len, 0, (struct sockaddr *) &target, + sizeof(target)); + if (n < 0) { + err = -errno; + printk(UM_KERN_ERR "mconsole_notify - sendto failed, " + "errno = %d\n", errno); + } + return err; +} diff --git a/arch/um/drivers/mmapper_kern.c b/arch/um/drivers/mmapper_kern.c new file mode 100644 index 000000000..62145c276 --- /dev/null +++ b/arch/um/drivers/mmapper_kern.c @@ -0,0 +1,134 @@ +/* + * arch/um/drivers/mmapper_kern.c + * + * BRIEF MODULE DESCRIPTION + * + * Copyright (C) 2000 RidgeRun, Inc. + * Author: RidgeRun, Inc. + * Greg Lonnon glonnon@ridgerun.com or info@ridgerun.com + * + */ + +#include <linux/stddef.h> +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/mm.h> + +#include <asm/uaccess.h> +#include <mem_user.h> + +/* These are set in mmapper_init, which is called at boot time */ +static unsigned long mmapper_size; +static unsigned long p_buf; +static char *v_buf; + +static ssize_t mmapper_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + return simple_read_from_buffer(buf, count, ppos, v_buf, mmapper_size); +} + +static ssize_t mmapper_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + if (*ppos > mmapper_size) + return -EINVAL; + + return simple_write_to_buffer(v_buf, mmapper_size, ppos, buf, count); +} + +static long mmapper_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + +static int mmapper_mmap(struct file *file, struct vm_area_struct *vma) +{ + int ret = -EINVAL; + int size; + + if (vma->vm_pgoff != 0) + goto out; + + size = vma->vm_end - vma->vm_start; + if (size > mmapper_size) + return -EFAULT; + + /* + * XXX A comment above remap_pfn_range says it should only be + * called when the mm semaphore is held + */ + if (remap_pfn_range(vma, vma->vm_start, p_buf >> PAGE_SHIFT, size, + vma->vm_page_prot)) + goto out; + ret = 0; +out: + return ret; +} + +static int mmapper_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int mmapper_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations mmapper_fops = { + .owner = THIS_MODULE, + .read = mmapper_read, + .write = mmapper_write, + .unlocked_ioctl = mmapper_ioctl, + .mmap = mmapper_mmap, + .open = mmapper_open, + .release = mmapper_release, + .llseek = default_llseek, +}; + +/* + * No locking needed - only used (and modified) by below initcall and exitcall. + */ +static struct miscdevice mmapper_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "mmapper", + .fops = &mmapper_fops +}; + +static int __init mmapper_init(void) +{ + int err; + + printk(KERN_INFO "Mapper v0.1\n"); + + v_buf = (char *) find_iomem("mmapper", &mmapper_size); + if (mmapper_size == 0) { + printk(KERN_ERR "mmapper_init - find_iomem failed\n"); + return -ENODEV; + } + p_buf = __pa(v_buf); + + err = misc_register(&mmapper_dev); + if (err) { + printk(KERN_ERR "mmapper - misc_register failed, err = %d\n", + err); + return err; + } + return 0; +} + +static void mmapper_exit(void) +{ + misc_deregister(&mmapper_dev); +} + +module_init(mmapper_init); +module_exit(mmapper_exit); + +MODULE_AUTHOR("Greg Lonnon <glonnon@ridgerun.com>"); +MODULE_DESCRIPTION("DSPLinux simulator mmapper driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/um/drivers/net_kern.c b/arch/um/drivers/net_kern.c new file mode 100644 index 000000000..f70dd5406 --- /dev/null +++ b/arch/um/drivers/net_kern.c @@ -0,0 +1,911 @@ +/* + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Copyright (C) 2001 Lennert Buytenhek (buytenh@gnu.org) and + * James Leu (jleu@mindspring.net). + * Copyright (C) 2001 by various other people who didn't put their name here. + * Licensed under the GPL. + */ + +#include <linux/bootmem.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/inetdevice.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/netdevice.h> +#include <linux/platform_device.h> +#include <linux/rtnetlink.h> +#include <linux/skbuff.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <init.h> +#include <irq_kern.h> +#include <irq_user.h> +#include "mconsole_kern.h" +#include <net_kern.h> +#include <net_user.h> + +#define DRIVER_NAME "uml-netdev" + +static DEFINE_SPINLOCK(opened_lock); +static LIST_HEAD(opened); + +/* + * The drop_skb is used when we can't allocate an skb. The + * packet is read into drop_skb in order to get the data off the + * connection to the host. + * It is reallocated whenever a maximum packet size is seen which is + * larger than any seen before. update_drop_skb is called from + * eth_configure when a new interface is added. + */ +static DEFINE_SPINLOCK(drop_lock); +static struct sk_buff *drop_skb; +static int drop_max; + +static int update_drop_skb(int max) +{ + struct sk_buff *new; + unsigned long flags; + int err = 0; + + spin_lock_irqsave(&drop_lock, flags); + + if (max <= drop_max) + goto out; + + err = -ENOMEM; + new = dev_alloc_skb(max); + if (new == NULL) + goto out; + + skb_put(new, max); + + kfree_skb(drop_skb); + drop_skb = new; + drop_max = max; + err = 0; +out: + spin_unlock_irqrestore(&drop_lock, flags); + + return err; +} + +static int uml_net_rx(struct net_device *dev) +{ + struct uml_net_private *lp = netdev_priv(dev); + int pkt_len; + struct sk_buff *skb; + + /* If we can't allocate memory, try again next round. */ + skb = dev_alloc_skb(lp->max_packet); + if (skb == NULL) { + drop_skb->dev = dev; + /* Read a packet into drop_skb and don't do anything with it. */ + (*lp->read)(lp->fd, drop_skb, lp); + dev->stats.rx_dropped++; + return 0; + } + + skb->dev = dev; + skb_put(skb, lp->max_packet); + skb_reset_mac_header(skb); + pkt_len = (*lp->read)(lp->fd, skb, lp); + + if (pkt_len > 0) { + skb_trim(skb, pkt_len); + skb->protocol = (*lp->protocol)(skb); + + dev->stats.rx_bytes += skb->len; + dev->stats.rx_packets++; + netif_rx(skb); + return pkt_len; + } + + kfree_skb(skb); + return pkt_len; +} + +static void uml_dev_close(struct work_struct *work) +{ + struct uml_net_private *lp = + container_of(work, struct uml_net_private, work); + dev_close(lp->dev); +} + +static irqreturn_t uml_net_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct uml_net_private *lp = netdev_priv(dev); + int err; + + if (!netif_running(dev)) + return IRQ_NONE; + + spin_lock(&lp->lock); + while ((err = uml_net_rx(dev)) > 0) ; + if (err < 0) { + printk(KERN_ERR + "Device '%s' read returned %d, shutting it down\n", + dev->name, err); + /* dev_close can't be called in interrupt context, and takes + * again lp->lock. + * And dev_close() can be safely called multiple times on the + * same device, since it tests for (dev->flags & IFF_UP). So + * there's no harm in delaying the device shutdown. + * Furthermore, the workqueue will not re-enqueue an already + * enqueued work item. */ + schedule_work(&lp->work); + goto out; + } + reactivate_fd(lp->fd, UM_ETH_IRQ); + +out: + spin_unlock(&lp->lock); + return IRQ_HANDLED; +} + +static int uml_net_open(struct net_device *dev) +{ + struct uml_net_private *lp = netdev_priv(dev); + int err; + + if (lp->fd >= 0) { + err = -ENXIO; + goto out; + } + + lp->fd = (*lp->open)(&lp->user); + if (lp->fd < 0) { + err = lp->fd; + goto out; + } + + err = um_request_irq(dev->irq, lp->fd, IRQ_READ, uml_net_interrupt, + IRQF_SHARED, dev->name, dev); + if (err != 0) { + printk(KERN_ERR "uml_net_open: failed to get irq(%d)\n", err); + err = -ENETUNREACH; + goto out_close; + } + + lp->tl.data = (unsigned long) &lp->user; + netif_start_queue(dev); + + /* clear buffer - it can happen that the host side of the interface + * is full when we get here. In this case, new data is never queued, + * SIGIOs never arrive, and the net never works. + */ + while ((err = uml_net_rx(dev)) > 0) ; + + spin_lock(&opened_lock); + list_add(&lp->list, &opened); + spin_unlock(&opened_lock); + + return 0; +out_close: + if (lp->close != NULL) (*lp->close)(lp->fd, &lp->user); + lp->fd = -1; +out: + return err; +} + +static int uml_net_close(struct net_device *dev) +{ + struct uml_net_private *lp = netdev_priv(dev); + + netif_stop_queue(dev); + + um_free_irq(dev->irq, dev); + if (lp->close != NULL) + (*lp->close)(lp->fd, &lp->user); + lp->fd = -1; + + spin_lock(&opened_lock); + list_del(&lp->list); + spin_unlock(&opened_lock); + + return 0; +} + +static int uml_net_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct uml_net_private *lp = netdev_priv(dev); + unsigned long flags; + int len; + + netif_stop_queue(dev); + + spin_lock_irqsave(&lp->lock, flags); + + len = (*lp->write)(lp->fd, skb, lp); + skb_tx_timestamp(skb); + + if (len == skb->len) { + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; + dev->trans_start = jiffies; + netif_start_queue(dev); + + /* this is normally done in the interrupt when tx finishes */ + netif_wake_queue(dev); + } + else if (len == 0) { + netif_start_queue(dev); + dev->stats.tx_dropped++; + } + else { + netif_start_queue(dev); + printk(KERN_ERR "uml_net_start_xmit: failed(%d)\n", len); + } + + spin_unlock_irqrestore(&lp->lock, flags); + + dev_consume_skb_any(skb); + + return NETDEV_TX_OK; +} + +static void uml_net_set_multicast_list(struct net_device *dev) +{ + return; +} + +static void uml_net_tx_timeout(struct net_device *dev) +{ + dev->trans_start = jiffies; + netif_wake_queue(dev); +} + +static int uml_net_change_mtu(struct net_device *dev, int new_mtu) +{ + dev->mtu = new_mtu; + + return 0; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +static void uml_net_poll_controller(struct net_device *dev) +{ + disable_irq(dev->irq); + uml_net_interrupt(dev->irq, dev); + enable_irq(dev->irq); +} +#endif + +static void uml_net_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strlcpy(info->driver, DRIVER_NAME, sizeof(info->driver)); + strlcpy(info->version, "42", sizeof(info->version)); +} + +static const struct ethtool_ops uml_net_ethtool_ops = { + .get_drvinfo = uml_net_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_ts_info = ethtool_op_get_ts_info, +}; + +static void uml_net_user_timer_expire(unsigned long _conn) +{ +#ifdef undef + struct connection *conn = (struct connection *)_conn; + + dprintk(KERN_INFO "uml_net_user_timer_expire [%p]\n", conn); + do_connect(conn); +#endif +} + +static void setup_etheraddr(struct net_device *dev, char *str) +{ + unsigned char *addr = dev->dev_addr; + char *end; + int i; + + if (str == NULL) + goto random; + + for (i = 0; i < 6; i++) { + addr[i] = simple_strtoul(str, &end, 16); + if ((end == str) || + ((*end != ':') && (*end != ',') && (*end != '\0'))) { + printk(KERN_ERR + "setup_etheraddr: failed to parse '%s' " + "as an ethernet address\n", str); + goto random; + } + str = end + 1; + } + if (is_multicast_ether_addr(addr)) { + printk(KERN_ERR + "Attempt to assign a multicast ethernet address to a " + "device disallowed\n"); + goto random; + } + if (!is_valid_ether_addr(addr)) { + printk(KERN_ERR + "Attempt to assign an invalid ethernet address to a " + "device disallowed\n"); + goto random; + } + if (!is_local_ether_addr(addr)) { + printk(KERN_WARNING + "Warning: Assigning a globally valid ethernet " + "address to a device\n"); + printk(KERN_WARNING "You should set the 2nd rightmost bit in " + "the first byte of the MAC,\n"); + printk(KERN_WARNING "i.e. %02x:%02x:%02x:%02x:%02x:%02x\n", + addr[0] | 0x02, addr[1], addr[2], addr[3], addr[4], + addr[5]); + } + return; + +random: + printk(KERN_INFO + "Choosing a random ethernet address for device %s\n", dev->name); + eth_hw_addr_random(dev); +} + +static DEFINE_SPINLOCK(devices_lock); +static LIST_HEAD(devices); + +static struct platform_driver uml_net_driver = { + .driver = { + .name = DRIVER_NAME, + }, +}; + +static void net_device_release(struct device *dev) +{ + struct uml_net *device = dev_get_drvdata(dev); + struct net_device *netdev = device->dev; + struct uml_net_private *lp = netdev_priv(netdev); + + if (lp->remove != NULL) + (*lp->remove)(&lp->user); + list_del(&device->list); + kfree(device); + free_netdev(netdev); +} + +static const struct net_device_ops uml_netdev_ops = { + .ndo_open = uml_net_open, + .ndo_stop = uml_net_close, + .ndo_start_xmit = uml_net_start_xmit, + .ndo_set_rx_mode = uml_net_set_multicast_list, + .ndo_tx_timeout = uml_net_tx_timeout, + .ndo_set_mac_address = eth_mac_addr, + .ndo_change_mtu = uml_net_change_mtu, + .ndo_validate_addr = eth_validate_addr, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = uml_net_poll_controller, +#endif +}; + +/* + * Ensures that platform_driver_register is called only once by + * eth_configure. Will be set in an initcall. + */ +static int driver_registered; + +static void eth_configure(int n, void *init, char *mac, + struct transport *transport) +{ + struct uml_net *device; + struct net_device *dev; + struct uml_net_private *lp; + int err, size; + + size = transport->private_size + sizeof(struct uml_net_private); + + device = kzalloc(sizeof(*device), GFP_KERNEL); + if (device == NULL) { + printk(KERN_ERR "eth_configure failed to allocate struct " + "uml_net\n"); + return; + } + + dev = alloc_etherdev(size); + if (dev == NULL) { + printk(KERN_ERR "eth_configure: failed to allocate struct " + "net_device for eth%d\n", n); + goto out_free_device; + } + + INIT_LIST_HEAD(&device->list); + device->index = n; + + /* If this name ends up conflicting with an existing registered + * netdevice, that is OK, register_netdev{,ice}() will notice this + * and fail. + */ + snprintf(dev->name, sizeof(dev->name), "eth%d", n); + + setup_etheraddr(dev, mac); + + printk(KERN_INFO "Netdevice %d (%pM) : ", n, dev->dev_addr); + + lp = netdev_priv(dev); + /* This points to the transport private data. It's still clear, but we + * must memset it to 0 *now*. Let's help the drivers. */ + memset(lp, 0, size); + INIT_WORK(&lp->work, uml_dev_close); + + /* sysfs register */ + if (!driver_registered) { + platform_driver_register(¨_net_driver); + driver_registered = 1; + } + device->pdev.id = n; + device->pdev.name = DRIVER_NAME; + device->pdev.dev.release = net_device_release; + dev_set_drvdata(&device->pdev.dev, device); + if (platform_device_register(&device->pdev)) + goto out_free_netdev; + SET_NETDEV_DEV(dev,&device->pdev.dev); + + device->dev = dev; + + /* + * These just fill in a data structure, so there's no failure + * to be worried about. + */ + (*transport->kern->init)(dev, init); + + *lp = ((struct uml_net_private) + { .list = LIST_HEAD_INIT(lp->list), + .dev = dev, + .fd = -1, + .mac = { 0xfe, 0xfd, 0x0, 0x0, 0x0, 0x0}, + .max_packet = transport->user->max_packet, + .protocol = transport->kern->protocol, + .open = transport->user->open, + .close = transport->user->close, + .remove = transport->user->remove, + .read = transport->kern->read, + .write = transport->kern->write, + .add_address = transport->user->add_address, + .delete_address = transport->user->delete_address }); + + init_timer(&lp->tl); + spin_lock_init(&lp->lock); + lp->tl.function = uml_net_user_timer_expire; + memcpy(lp->mac, dev->dev_addr, sizeof(lp->mac)); + + if ((transport->user->init != NULL) && + ((*transport->user->init)(&lp->user, dev) != 0)) + goto out_unregister; + + dev->mtu = transport->user->mtu; + dev->netdev_ops = ¨_netdev_ops; + dev->ethtool_ops = ¨_net_ethtool_ops; + dev->watchdog_timeo = (HZ >> 1); + dev->irq = UM_ETH_IRQ; + + err = update_drop_skb(lp->max_packet); + if (err) + goto out_undo_user_init; + + rtnl_lock(); + err = register_netdevice(dev); + rtnl_unlock(); + if (err) + goto out_undo_user_init; + + spin_lock(&devices_lock); + list_add(&device->list, &devices); + spin_unlock(&devices_lock); + + return; + +out_undo_user_init: + if (transport->user->remove != NULL) + (*transport->user->remove)(&lp->user); +out_unregister: + platform_device_unregister(&device->pdev); + return; /* platform_device_unregister frees dev and device */ +out_free_netdev: + free_netdev(dev); +out_free_device: + kfree(device); +} + +static struct uml_net *find_device(int n) +{ + struct uml_net *device; + struct list_head *ele; + + spin_lock(&devices_lock); + list_for_each(ele, &devices) { + device = list_entry(ele, struct uml_net, list); + if (device->index == n) + goto out; + } + device = NULL; + out: + spin_unlock(&devices_lock); + return device; +} + +static int eth_parse(char *str, int *index_out, char **str_out, + char **error_out) +{ + char *end; + int n, err = -EINVAL; + + n = simple_strtoul(str, &end, 0); + if (end == str) { + *error_out = "Bad device number"; + return err; + } + + str = end; + if (*str != '=') { + *error_out = "Expected '=' after device number"; + return err; + } + + str++; + if (find_device(n)) { + *error_out = "Device already configured"; + return err; + } + + *index_out = n; + *str_out = str; + return 0; +} + +struct eth_init { + struct list_head list; + char *init; + int index; +}; + +static DEFINE_SPINLOCK(transports_lock); +static LIST_HEAD(transports); + +/* Filled in during early boot */ +static LIST_HEAD(eth_cmd_line); + +static int check_transport(struct transport *transport, char *eth, int n, + void **init_out, char **mac_out) +{ + int len; + + len = strlen(transport->name); + if (strncmp(eth, transport->name, len)) + return 0; + + eth += len; + if (*eth == ',') + eth++; + else if (*eth != '\0') + return 0; + + *init_out = kmalloc(transport->setup_size, GFP_KERNEL); + if (*init_out == NULL) + return 1; + + if (!transport->setup(eth, mac_out, *init_out)) { + kfree(*init_out); + *init_out = NULL; + } + return 1; +} + +void register_transport(struct transport *new) +{ + struct list_head *ele, *next; + struct eth_init *eth; + void *init; + char *mac = NULL; + int match; + + spin_lock(&transports_lock); + BUG_ON(!list_empty(&new->list)); + list_add(&new->list, &transports); + spin_unlock(&transports_lock); + + list_for_each_safe(ele, next, ð_cmd_line) { + eth = list_entry(ele, struct eth_init, list); + match = check_transport(new, eth->init, eth->index, &init, + &mac); + if (!match) + continue; + else if (init != NULL) { + eth_configure(eth->index, init, mac, new); + kfree(init); + } + list_del(ð->list); + } +} + +static int eth_setup_common(char *str, int index) +{ + struct list_head *ele; + struct transport *transport; + void *init; + char *mac = NULL; + int found = 0; + + spin_lock(&transports_lock); + list_for_each(ele, &transports) { + transport = list_entry(ele, struct transport, list); + if (!check_transport(transport, str, index, &init, &mac)) + continue; + if (init != NULL) { + eth_configure(index, init, mac, transport); + kfree(init); + } + found = 1; + break; + } + + spin_unlock(&transports_lock); + return found; +} + +static int __init eth_setup(char *str) +{ + struct eth_init *new; + char *error; + int n, err; + + err = eth_parse(str, &n, &str, &error); + if (err) { + printk(KERN_ERR "eth_setup - Couldn't parse '%s' : %s\n", + str, error); + return 1; + } + + new = alloc_bootmem(sizeof(*new)); + + INIT_LIST_HEAD(&new->list); + new->index = n; + new->init = str; + + list_add_tail(&new->list, ð_cmd_line); + return 1; +} + +__setup("eth", eth_setup); +__uml_help(eth_setup, +"eth[0-9]+=<transport>,<options>\n" +" Configure a network device.\n\n" +); + +static int net_config(char *str, char **error_out) +{ + int n, err; + + err = eth_parse(str, &n, &str, error_out); + if (err) + return err; + + /* This string is broken up and the pieces used by the underlying + * driver. So, it is freed only if eth_setup_common fails. + */ + str = kstrdup(str, GFP_KERNEL); + if (str == NULL) { + *error_out = "net_config failed to strdup string"; + return -ENOMEM; + } + err = !eth_setup_common(str, n); + if (err) + kfree(str); + return err; +} + +static int net_id(char **str, int *start_out, int *end_out) +{ + char *end; + int n; + + n = simple_strtoul(*str, &end, 0); + if ((*end != '\0') || (end == *str)) + return -1; + + *start_out = n; + *end_out = n; + *str = end; + return n; +} + +static int net_remove(int n, char **error_out) +{ + struct uml_net *device; + struct net_device *dev; + struct uml_net_private *lp; + + device = find_device(n); + if (device == NULL) + return -ENODEV; + + dev = device->dev; + lp = netdev_priv(dev); + if (lp->fd > 0) + return -EBUSY; + unregister_netdev(dev); + platform_device_unregister(&device->pdev); + + return 0; +} + +static struct mc_device net_mc = { + .list = LIST_HEAD_INIT(net_mc.list), + .name = "eth", + .config = net_config, + .get_config = NULL, + .id = net_id, + .remove = net_remove, +}; + +#ifdef CONFIG_INET +static int uml_inetaddr_event(struct notifier_block *this, unsigned long event, + void *ptr) +{ + struct in_ifaddr *ifa = ptr; + struct net_device *dev = ifa->ifa_dev->dev; + struct uml_net_private *lp; + void (*proc)(unsigned char *, unsigned char *, void *); + unsigned char addr_buf[4], netmask_buf[4]; + + if (dev->netdev_ops->ndo_open != uml_net_open) + return NOTIFY_DONE; + + lp = netdev_priv(dev); + + proc = NULL; + switch (event) { + case NETDEV_UP: + proc = lp->add_address; + break; + case NETDEV_DOWN: + proc = lp->delete_address; + break; + } + if (proc != NULL) { + memcpy(addr_buf, &ifa->ifa_address, sizeof(addr_buf)); + memcpy(netmask_buf, &ifa->ifa_mask, sizeof(netmask_buf)); + (*proc)(addr_buf, netmask_buf, &lp->user); + } + return NOTIFY_DONE; +} + +/* uml_net_init shouldn't be called twice on two CPUs at the same time */ +static struct notifier_block uml_inetaddr_notifier = { + .notifier_call = uml_inetaddr_event, +}; + +static void inet_register(void) +{ + struct list_head *ele; + struct uml_net_private *lp; + struct in_device *ip; + struct in_ifaddr *in; + + register_inetaddr_notifier(¨_inetaddr_notifier); + + /* Devices may have been opened already, so the uml_inetaddr_notifier + * didn't get a chance to run for them. This fakes it so that + * addresses which have already been set up get handled properly. + */ + spin_lock(&opened_lock); + list_for_each(ele, &opened) { + lp = list_entry(ele, struct uml_net_private, list); + ip = lp->dev->ip_ptr; + if (ip == NULL) + continue; + in = ip->ifa_list; + while (in != NULL) { + uml_inetaddr_event(NULL, NETDEV_UP, in); + in = in->ifa_next; + } + } + spin_unlock(&opened_lock); +} +#else +static inline void inet_register(void) +{ +} +#endif + +static int uml_net_init(void) +{ + mconsole_register_dev(&net_mc); + inet_register(); + return 0; +} + +__initcall(uml_net_init); + +static void close_devices(void) +{ + struct list_head *ele; + struct uml_net_private *lp; + + spin_lock(&opened_lock); + list_for_each(ele, &opened) { + lp = list_entry(ele, struct uml_net_private, list); + um_free_irq(lp->dev->irq, lp->dev); + if ((lp->close != NULL) && (lp->fd >= 0)) + (*lp->close)(lp->fd, &lp->user); + if (lp->remove != NULL) + (*lp->remove)(&lp->user); + } + spin_unlock(&opened_lock); +} + +__uml_exitcall(close_devices); + +void iter_addresses(void *d, void (*cb)(unsigned char *, unsigned char *, + void *), + void *arg) +{ + struct net_device *dev = d; + struct in_device *ip = dev->ip_ptr; + struct in_ifaddr *in; + unsigned char address[4], netmask[4]; + + if (ip == NULL) return; + in = ip->ifa_list; + while (in != NULL) { + memcpy(address, &in->ifa_address, sizeof(address)); + memcpy(netmask, &in->ifa_mask, sizeof(netmask)); + (*cb)(address, netmask, arg); + in = in->ifa_next; + } +} + +int dev_netmask(void *d, void *m) +{ + struct net_device *dev = d; + struct in_device *ip = dev->ip_ptr; + struct in_ifaddr *in; + __be32 *mask_out = m; + + if (ip == NULL) + return 1; + + in = ip->ifa_list; + if (in == NULL) + return 1; + + *mask_out = in->ifa_mask; + return 0; +} + +void *get_output_buffer(int *len_out) +{ + void *ret; + + ret = (void *) __get_free_pages(GFP_KERNEL, 0); + if (ret) *len_out = PAGE_SIZE; + else *len_out = 0; + return ret; +} + +void free_output_buffer(void *buffer) +{ + free_pages((unsigned long) buffer, 0); +} + +int tap_setup_common(char *str, char *type, char **dev_name, char **mac_out, + char **gate_addr) +{ + char *remain; + + remain = split_if_spec(str, dev_name, mac_out, gate_addr, NULL); + if (remain != NULL) { + printk(KERN_ERR "tap_setup_common - Extra garbage on " + "specification : '%s'\n", remain); + return 1; + } + + return 0; +} + +unsigned short eth_protocol(struct sk_buff *skb) +{ + return eth_type_trans(skb, skb->dev); +} diff --git a/arch/um/drivers/net_user.c b/arch/um/drivers/net_user.c new file mode 100644 index 000000000..cd14157b5 --- /dev/null +++ b/arch/um/drivers/net_user.c @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL + */ + +#include <stdio.h> +#include <unistd.h> +#include <stdarg.h> +#include <errno.h> +#include <stddef.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <net_user.h> +#include <os.h> +#include <um_malloc.h> + +int tap_open_common(void *dev, char *gate_addr) +{ + int tap_addr[4]; + + if (gate_addr == NULL) + return 0; + if (sscanf(gate_addr, "%d.%d.%d.%d", &tap_addr[0], + &tap_addr[1], &tap_addr[2], &tap_addr[3]) != 4) { + printk(UM_KERN_ERR "Invalid tap IP address - '%s'\n", + gate_addr); + return -EINVAL; + } + return 0; +} + +void tap_check_ips(char *gate_addr, unsigned char *eth_addr) +{ + int tap_addr[4]; + + if ((gate_addr != NULL) && + (sscanf(gate_addr, "%d.%d.%d.%d", &tap_addr[0], + &tap_addr[1], &tap_addr[2], &tap_addr[3]) == 4) && + (eth_addr[0] == tap_addr[0]) && + (eth_addr[1] == tap_addr[1]) && + (eth_addr[2] == tap_addr[2]) && + (eth_addr[3] == tap_addr[3])) { + printk(UM_KERN_ERR "The tap IP address and the UML eth IP " + "address must be different\n"); + } +} + +/* Do reliable error handling as this fails frequently enough. */ +void read_output(int fd, char *output, int len) +{ + int remain, ret, expected; + char c; + char *str; + + if (output == NULL) { + output = &c; + len = sizeof(c); + } + + *output = '\0'; + ret = read(fd, &remain, sizeof(remain)); + + if (ret != sizeof(remain)) { + if (ret < 0) + ret = -errno; + expected = sizeof(remain); + str = "length"; + goto err; + } + + while (remain != 0) { + expected = (remain < len) ? remain : len; + ret = read(fd, output, expected); + if (ret != expected) { + if (ret < 0) + ret = -errno; + str = "data"; + goto err; + } + remain -= ret; + } + + return; + +err: + if (ret < 0) + printk(UM_KERN_ERR "read_output - read of %s failed, " + "errno = %d\n", str, -ret); + else + printk(UM_KERN_ERR "read_output - read of %s failed, read only " + "%d of %d bytes\n", str, ret, expected); +} + +int net_read(int fd, void *buf, int len) +{ + int n; + + n = read(fd, buf, len); + + if ((n < 0) && (errno == EAGAIN)) + return 0; + else if (n == 0) + return -ENOTCONN; + return n; +} + +int net_recvfrom(int fd, void *buf, int len) +{ + int n; + + CATCH_EINTR(n = recvfrom(fd, buf, len, 0, NULL, NULL)); + if (n < 0) { + if (errno == EAGAIN) + return 0; + return -errno; + } + else if (n == 0) + return -ENOTCONN; + return n; +} + +int net_write(int fd, void *buf, int len) +{ + int n; + + n = write(fd, buf, len); + + if ((n < 0) && (errno == EAGAIN)) + return 0; + else if (n == 0) + return -ENOTCONN; + return n; +} + +int net_send(int fd, void *buf, int len) +{ + int n; + + CATCH_EINTR(n = send(fd, buf, len, 0)); + if (n < 0) { + if (errno == EAGAIN) + return 0; + return -errno; + } + else if (n == 0) + return -ENOTCONN; + return n; +} + +int net_sendto(int fd, void *buf, int len, void *to, int sock_len) +{ + int n; + + CATCH_EINTR(n = sendto(fd, buf, len, 0, (struct sockaddr *) to, + sock_len)); + if (n < 0) { + if (errno == EAGAIN) + return 0; + return -errno; + } + else if (n == 0) + return -ENOTCONN; + return n; +} + +struct change_pre_exec_data { + int close_me; + int stdout; +}; + +static void change_pre_exec(void *arg) +{ + struct change_pre_exec_data *data = arg; + + close(data->close_me); + dup2(data->stdout, 1); +} + +static int change_tramp(char **argv, char *output, int output_len) +{ + int pid, fds[2], err; + struct change_pre_exec_data pe_data; + + err = os_pipe(fds, 1, 0); + if (err < 0) { + printk(UM_KERN_ERR "change_tramp - pipe failed, err = %d\n", + -err); + return err; + } + pe_data.close_me = fds[0]; + pe_data.stdout = fds[1]; + pid = run_helper(change_pre_exec, &pe_data, argv); + + if (pid > 0) /* Avoid hang as we won't get data in failure case. */ + read_output(fds[0], output, output_len); + + close(fds[0]); + close(fds[1]); + + if (pid > 0) + helper_wait(pid); + return pid; +} + +static void change(char *dev, char *what, unsigned char *addr, + unsigned char *netmask) +{ + char addr_buf[sizeof("255.255.255.255\0")]; + char netmask_buf[sizeof("255.255.255.255\0")]; + char version[sizeof("nnnnn\0")]; + char *argv[] = { "uml_net", version, what, dev, addr_buf, + netmask_buf, NULL }; + char *output; + int output_len, pid; + + sprintf(version, "%d", UML_NET_VERSION); + sprintf(addr_buf, "%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]); + sprintf(netmask_buf, "%d.%d.%d.%d", netmask[0], netmask[1], + netmask[2], netmask[3]); + + output_len = UM_KERN_PAGE_SIZE; + output = uml_kmalloc(output_len, UM_GFP_KERNEL); + if (output == NULL) + printk(UM_KERN_ERR "change : failed to allocate output " + "buffer\n"); + + pid = change_tramp(argv, output, output_len); + if (pid < 0) { + kfree(output); + return; + } + + if (output != NULL) { + printk("%s", output); + kfree(output); + } +} + +void open_addr(unsigned char *addr, unsigned char *netmask, void *arg) +{ + change(arg, "add", addr, netmask); +} + +void close_addr(unsigned char *addr, unsigned char *netmask, void *arg) +{ + change(arg, "del", addr, netmask); +} + +char *split_if_spec(char *str, ...) +{ + char **arg, *end; + va_list ap; + + va_start(ap, str); + while ((arg = va_arg(ap, char **)) != NULL) { + if (*str == '\0') + return NULL; + end = strchr(str, ','); + if (end != str) + *arg = str; + if (end == NULL) + return NULL; + *end++ = '\0'; + str = end; + } + va_end(ap); + return str; +} diff --git a/arch/um/drivers/null.c b/arch/um/drivers/null.c new file mode 100644 index 000000000..10495747c --- /dev/null +++ b/arch/um/drivers/null.c @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2002 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com) + * Licensed under the GPL + */ + +#include <stddef.h> +#include <errno.h> +#include <fcntl.h> +#include "chan_user.h" +#include <os.h> + +/* This address is used only as a unique identifier */ +static int null_chan; + +static void *null_init(char *str, int device, const struct chan_opts *opts) +{ + return &null_chan; +} + +static int null_open(int input, int output, int primary, void *d, + char **dev_out) +{ + int fd; + + *dev_out = NULL; + + fd = open(DEV_NULL, O_RDWR); + return (fd < 0) ? -errno : fd; +} + +static int null_read(int fd, char *c_out, void *unused) +{ + return -ENODEV; +} + +static void null_free(void *data) +{ +} + +const struct chan_ops null_ops = { + .type = "null", + .init = null_init, + .open = null_open, + .close = generic_close, + .read = null_read, + .write = generic_write, + .console_write = generic_console_write, + .window_size = generic_window_size, + .free = null_free, + .winch = 0, +}; diff --git a/arch/um/drivers/pcap_kern.c b/arch/um/drivers/pcap_kern.c new file mode 100644 index 000000000..be0fb57bd --- /dev/null +++ b/arch/um/drivers/pcap_kern.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL. + */ + +#include <linux/init.h> +#include <linux/netdevice.h> +#include <net_kern.h> +#include "pcap_user.h" + +struct pcap_init { + char *host_if; + int promisc; + int optimize; + char *filter; +}; + +void pcap_init(struct net_device *dev, void *data) +{ + struct uml_net_private *pri; + struct pcap_data *ppri; + struct pcap_init *init = data; + + pri = netdev_priv(dev); + ppri = (struct pcap_data *) pri->user; + ppri->host_if = init->host_if; + ppri->promisc = init->promisc; + ppri->optimize = init->optimize; + ppri->filter = init->filter; + + printk("pcap backend, host interface %s\n", ppri->host_if); +} + +static int pcap_read(int fd, struct sk_buff *skb, struct uml_net_private *lp) +{ + return pcap_user_read(fd, skb_mac_header(skb), + skb->dev->mtu + ETH_HEADER_OTHER, + (struct pcap_data *) &lp->user); +} + +static int pcap_write(int fd, struct sk_buff *skb, struct uml_net_private *lp) +{ + return -EPERM; +} + +static const struct net_kern_info pcap_kern_info = { + .init = pcap_init, + .protocol = eth_protocol, + .read = pcap_read, + .write = pcap_write, +}; + +int pcap_setup(char *str, char **mac_out, void *data) +{ + struct pcap_init *init = data; + char *remain, *host_if = NULL, *options[2] = { NULL, NULL }; + int i; + + *init = ((struct pcap_init) + { .host_if = "eth0", + .promisc = 1, + .optimize = 0, + .filter = NULL }); + + remain = split_if_spec(str, &host_if, &init->filter, + &options[0], &options[1], mac_out, NULL); + if (remain != NULL) { + printk(KERN_ERR "pcap_setup - Extra garbage on " + "specification : '%s'\n", remain); + return 0; + } + + if (host_if != NULL) + init->host_if = host_if; + + for (i = 0; i < ARRAY_SIZE(options); i++) { + if (options[i] == NULL) + continue; + if (!strcmp(options[i], "promisc")) + init->promisc = 1; + else if (!strcmp(options[i], "nopromisc")) + init->promisc = 0; + else if (!strcmp(options[i], "optimize")) + init->optimize = 1; + else if (!strcmp(options[i], "nooptimize")) + init->optimize = 0; + else { + printk(KERN_ERR "pcap_setup : bad option - '%s'\n", + options[i]); + return 0; + } + } + + return 1; +} + +static struct transport pcap_transport = { + .list = LIST_HEAD_INIT(pcap_transport.list), + .name = "pcap", + .setup = pcap_setup, + .user = &pcap_user_info, + .kern = &pcap_kern_info, + .private_size = sizeof(struct pcap_data), + .setup_size = sizeof(struct pcap_init), +}; + +static int register_pcap(void) +{ + register_transport(&pcap_transport); + return 0; +} + +late_initcall(register_pcap); diff --git a/arch/um/drivers/pcap_user.c b/arch/um/drivers/pcap_user.c new file mode 100644 index 000000000..c07b9c752 --- /dev/null +++ b/arch/um/drivers/pcap_user.c @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL. + */ + +#include <errno.h> +#include <pcap.h> +#include <string.h> +#include <asm/types.h> +#include <net_user.h> +#include "pcap_user.h" +#include <um_malloc.h> + +#define PCAP_FD(p) (*(int *)(p)) + +static int pcap_user_init(void *data, void *dev) +{ + struct pcap_data *pri = data; + pcap_t *p; + char errors[PCAP_ERRBUF_SIZE]; + + p = pcap_open_live(pri->host_if, ETH_MAX_PACKET + ETH_HEADER_OTHER, + pri->promisc, 0, errors); + if (p == NULL) { + printk(UM_KERN_ERR "pcap_user_init : pcap_open_live failed - " + "'%s'\n", errors); + return -EINVAL; + } + + pri->dev = dev; + pri->pcap = p; + return 0; +} + +static int pcap_open(void *data) +{ + struct pcap_data *pri = data; + __u32 netmask; + int err; + + if (pri->pcap == NULL) + return -ENODEV; + + if (pri->filter != NULL) { + err = dev_netmask(pri->dev, &netmask); + if (err < 0) { + printk(UM_KERN_ERR "pcap_open : dev_netmask failed\n"); + return -EIO; + } + + pri->compiled = uml_kmalloc(sizeof(struct bpf_program), + UM_GFP_KERNEL); + if (pri->compiled == NULL) { + printk(UM_KERN_ERR "pcap_open : kmalloc failed\n"); + return -ENOMEM; + } + + err = pcap_compile(pri->pcap, + (struct bpf_program *) pri->compiled, + pri->filter, pri->optimize, netmask); + if (err < 0) { + printk(UM_KERN_ERR "pcap_open : pcap_compile failed - " + "'%s'\n", pcap_geterr(pri->pcap)); + goto out; + } + + err = pcap_setfilter(pri->pcap, pri->compiled); + if (err < 0) { + printk(UM_KERN_ERR "pcap_open : pcap_setfilter " + "failed - '%s'\n", pcap_geterr(pri->pcap)); + goto out; + } + } + + return PCAP_FD(pri->pcap); + + out: + kfree(pri->compiled); + return -EIO; +} + +static void pcap_remove(void *data) +{ + struct pcap_data *pri = data; + + if (pri->compiled != NULL) + pcap_freecode(pri->compiled); + + if (pri->pcap != NULL) + pcap_close(pri->pcap); +} + +struct pcap_handler_data { + char *buffer; + int len; +}; + +static void handler(u_char *data, const struct pcap_pkthdr *header, + const u_char *packet) +{ + int len; + + struct pcap_handler_data *hdata = (struct pcap_handler_data *) data; + + len = hdata->len < header->caplen ? hdata->len : header->caplen; + memcpy(hdata->buffer, packet, len); + hdata->len = len; +} + +int pcap_user_read(int fd, void *buffer, int len, struct pcap_data *pri) +{ + struct pcap_handler_data hdata = ((struct pcap_handler_data) + { .buffer = buffer, + .len = len }); + int n; + + n = pcap_dispatch(pri->pcap, 1, handler, (u_char *) &hdata); + if (n < 0) { + printk(UM_KERN_ERR "pcap_dispatch failed - %s\n", + pcap_geterr(pri->pcap)); + return -EIO; + } + else if (n == 0) + return 0; + return hdata.len; +} + +const struct net_user_info pcap_user_info = { + .init = pcap_user_init, + .open = pcap_open, + .close = NULL, + .remove = pcap_remove, + .add_address = NULL, + .delete_address = NULL, + .mtu = ETH_MAX_PACKET, + .max_packet = ETH_MAX_PACKET + ETH_HEADER_OTHER, +}; diff --git a/arch/um/drivers/pcap_user.h b/arch/um/drivers/pcap_user.h new file mode 100644 index 000000000..1ca7c764c --- /dev/null +++ b/arch/um/drivers/pcap_user.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2002 Jeff Dike (jdike@karaya.com) + * Licensed under the GPL + */ + +#include <net_user.h> + +struct pcap_data { + char *host_if; + int promisc; + int optimize; + char *filter; + void *compiled; + void *pcap; + void *dev; +}; + +extern const struct net_user_info pcap_user_info; + +extern int pcap_user_read(int fd, void *buf, int len, struct pcap_data *pri); + diff --git a/arch/um/drivers/port.h b/arch/um/drivers/port.h new file mode 100644 index 000000000..372a80c05 --- /dev/null +++ b/arch/um/drivers/port.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2001 Jeff Dike (jdike@karaya.com) + * Licensed under the GPL + */ + +#ifndef __PORT_H__ +#define __PORT_H__ + +extern void *port_data(int port); +extern int port_wait(void *data); +extern void port_kern_close(void *d); +extern int port_connection(int fd, int *socket_out, int *pid_out); +extern int port_listen_fd(int port); +extern void port_read(int fd, void *data); +extern void port_kern_free(void *d); +extern int port_rcv_fd(int fd); +extern void port_remove_dev(void *d); + +#endif + diff --git a/arch/um/drivers/port_kern.c b/arch/um/drivers/port_kern.c new file mode 100644 index 000000000..40ca5cc27 --- /dev/null +++ b/arch/um/drivers/port_kern.c @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com) + * Licensed under the GPL + */ + +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <asm/atomic.h> +#include <init.h> +#include <irq_kern.h> +#include <os.h> +#include "port.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 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; + + 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, + IRQF_SHARED, "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(KERN_ERR "No one waiting for port\n"); + } + list_add(&conn->list, &port->pending); + return 1; + + out_free: + kfree(conn); + out_close: + os_close_file(fd); + os_kill_process(pid, 1); + out: + return 0; +} + +static DEFINE_MUTEX(ports_mutex); +static LIST_HEAD(ports); + +static void port_work_proc(struct work_struct *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); + +static irqreturn_t port_interrupt(int irq, void *data) +{ + 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; + + mutex_lock(&ports_mutex); + 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, + IRQF_SHARED, "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_close: + os_close_file(fd); + out_free: + kfree(port); + out: + mutex_unlock(&ports_mutex); + 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. + */ + um_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); diff --git a/arch/um/drivers/port_user.c b/arch/um/drivers/port_user.c new file mode 100644 index 000000000..9a8e1b64c --- /dev/null +++ b/arch/um/drivers/port_user.c @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com) + * Licensed under the GPL + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <termios.h> +#include <unistd.h> +#include <netinet/in.h> +#include "chan_user.h" +#include <os.h> +#include "port.h" +#include <um_malloc.h> + +struct port_chan { + int raw; + struct termios tt; + void *kernel_data; + char dev[sizeof("32768\0")]; +}; + +static void *port_init(char *str, int device, const struct chan_opts *opts) +{ + struct port_chan *data; + void *kern_data; + char *end; + int port; + + if (*str != ':') { + printk(UM_KERN_ERR "port_init : channel type 'port' must " + "specify a port number\n"); + return NULL; + } + str++; + port = strtoul(str, &end, 0); + if ((*end != '\0') || (end == str)) { + printk(UM_KERN_ERR "port_init : couldn't parse port '%s'\n", + str); + return NULL; + } + + kern_data = port_data(port); + if (kern_data == NULL) + return NULL; + + data = uml_kmalloc(sizeof(*data), UM_GFP_KERNEL); + if (data == NULL) + goto err; + + *data = ((struct port_chan) { .raw = opts->raw, + .kernel_data = kern_data }); + sprintf(data->dev, "%d", port); + + return data; + err: + port_kern_free(kern_data); + return NULL; +} + +static void port_free(void *d) +{ + struct port_chan *data = d; + + port_kern_free(data->kernel_data); + kfree(data); +} + +static int port_open(int input, int output, int primary, void *d, + char **dev_out) +{ + struct port_chan *data = d; + int fd, err; + + fd = port_wait(data->kernel_data); + if ((fd >= 0) && data->raw) { + CATCH_EINTR(err = tcgetattr(fd, &data->tt)); + if (err) + return err; + + err = raw(fd); + if (err) + return err; + } + *dev_out = data->dev; + return fd; +} + +static void port_close(int fd, void *d) +{ + struct port_chan *data = d; + + port_remove_dev(data->kernel_data); + os_close_file(fd); +} + +const struct chan_ops port_ops = { + .type = "port", + .init = port_init, + .open = port_open, + .close = port_close, + .read = generic_read, + .write = generic_write, + .console_write = generic_console_write, + .window_size = generic_window_size, + .free = port_free, + .winch = 1, +}; + +int port_listen_fd(int port) +{ + struct sockaddr_in addr; + int fd, err, arg; + + fd = socket(PF_INET, SOCK_STREAM, 0); + if (fd == -1) + return -errno; + + arg = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &arg, sizeof(arg)) < 0) { + err = -errno; + goto out; + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + err = -errno; + goto out; + } + + if (listen(fd, 1) < 0) { + err = -errno; + goto out; + } + + err = os_set_fd_block(fd, 0); + if (err < 0) + goto out; + + return fd; + out: + close(fd); + return err; +} + +struct port_pre_exec_data { + int sock_fd; + int pipe_fd; +}; + +static void port_pre_exec(void *arg) +{ + struct port_pre_exec_data *data = arg; + + dup2(data->sock_fd, 0); + dup2(data->sock_fd, 1); + dup2(data->sock_fd, 2); + close(data->sock_fd); + dup2(data->pipe_fd, 3); + shutdown(3, SHUT_RD); + close(data->pipe_fd); +} + +int port_connection(int fd, int *socket, int *pid_out) +{ + int new, err; + char *argv[] = { "/usr/sbin/in.telnetd", "-L", + "/usr/lib/uml/port-helper", NULL }; + struct port_pre_exec_data data; + + new = accept(fd, NULL, 0); + if (new < 0) + return -errno; + + err = os_pipe(socket, 0, 0); + if (err < 0) + goto out_close; + + data = ((struct port_pre_exec_data) + { .sock_fd = new, + .pipe_fd = socket[1] }); + + err = run_helper(port_pre_exec, &data, argv); + if (err < 0) + goto out_shutdown; + + *pid_out = err; + return new; + + out_shutdown: + shutdown(socket[0], SHUT_RDWR); + close(socket[0]); + shutdown(socket[1], SHUT_RDWR); + close(socket[1]); + out_close: + close(new); + return err; +} diff --git a/arch/um/drivers/pty.c b/arch/um/drivers/pty.c new file mode 100644 index 000000000..f1fcc2ced --- /dev/null +++ b/arch/um/drivers/pty.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <termios.h> +#include <sys/stat.h> +#include "chan_user.h" +#include <os.h> +#include <um_malloc.h> + +struct pty_chan { + void (*announce)(char *dev_name, int dev); + int dev; + int raw; + struct termios tt; + char dev_name[sizeof("/dev/pts/0123456\0")]; +}; + +static void *pty_chan_init(char *str, int device, const struct chan_opts *opts) +{ + struct pty_chan *data; + + data = uml_kmalloc(sizeof(*data), UM_GFP_KERNEL); + if (data == NULL) + return NULL; + + *data = ((struct pty_chan) { .announce = opts->announce, + .dev = device, + .raw = opts->raw }); + return data; +} + +static int pts_open(int input, int output, int primary, void *d, + char **dev_out) +{ + struct pty_chan *data = d; + char *dev; + int fd, err; + + fd = get_pty(); + if (fd < 0) { + err = -errno; + printk(UM_KERN_ERR "open_pts : Failed to open pts\n"); + return err; + } + + if (data->raw) { + CATCH_EINTR(err = tcgetattr(fd, &data->tt)); + if (err) + goto out_close; + + err = raw(fd); + if (err) + goto out_close; + } + + dev = ptsname(fd); + sprintf(data->dev_name, "%s", dev); + *dev_out = data->dev_name; + + if (data->announce) + (*data->announce)(dev, data->dev); + + return fd; + +out_close: + close(fd); + return err; +} + +static int getmaster(char *line) +{ + struct stat buf; + char *pty, *bank, *cp; + int master, err; + + pty = &line[strlen("/dev/ptyp")]; + for (bank = "pqrs"; *bank; bank++) { + line[strlen("/dev/pty")] = *bank; + *pty = '0'; + /* Did we hit the end ? */ + if ((stat(line, &buf) < 0) && (errno == ENOENT)) + break; + + for (cp = "0123456789abcdef"; *cp; cp++) { + *pty = *cp; + master = open(line, O_RDWR); + if (master >= 0) { + char *tp = &line[strlen("/dev/")]; + + /* verify slave side is usable */ + *tp = 't'; + err = access(line, R_OK | W_OK); + *tp = 'p'; + if (!err) + return master; + close(master); + } + } + } + + printk(UM_KERN_ERR "getmaster - no usable host pty devices\n"); + return -ENOENT; +} + +static int pty_open(int input, int output, int primary, void *d, + char **dev_out) +{ + struct pty_chan *data = d; + int fd, err; + char dev[sizeof("/dev/ptyxx\0")] = "/dev/ptyxx"; + + fd = getmaster(dev); + if (fd < 0) + return fd; + + if (data->raw) { + err = raw(fd); + if (err) { + close(fd); + return err; + } + } + + if (data->announce) + (*data->announce)(dev, data->dev); + + sprintf(data->dev_name, "%s", dev); + *dev_out = data->dev_name; + + return fd; +} + +const struct chan_ops pty_ops = { + .type = "pty", + .init = pty_chan_init, + .open = pty_open, + .close = generic_close, + .read = generic_read, + .write = generic_write, + .console_write = generic_console_write, + .window_size = generic_window_size, + .free = generic_free, + .winch = 0, +}; + +const struct chan_ops pts_ops = { + .type = "pts", + .init = pty_chan_init, + .open = pts_open, + .close = generic_close, + .read = generic_read, + .write = generic_write, + .console_write = generic_console_write, + .window_size = generic_window_size, + .free = generic_free, + .winch = 0, +}; diff --git a/arch/um/drivers/random.c b/arch/um/drivers/random.c new file mode 100644 index 000000000..dd16c902f --- /dev/null +++ b/arch/um/drivers/random.c @@ -0,0 +1,167 @@ +/* Copyright (C) 2005 - 2008 Jeff Dike <jdike@{linux.intel,addtoit}.com> */ + +/* Much of this ripped from drivers/char/hw_random.c, see there for other + * copyright. + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ +#include <linux/sched.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/miscdevice.h> +#include <linux/delay.h> +#include <asm/uaccess.h> +#include <irq_kern.h> +#include <os.h> + +/* + * core module and version information + */ +#define RNG_VERSION "1.0.0" +#define RNG_MODULE_NAME "hw_random" + +#define RNG_MISCDEV_MINOR 183 /* official */ + +/* Changed at init time, in the non-modular case, and at module load + * time, in the module case. Presumably, the module subsystem + * protects against a module being loaded twice at the same time. + */ +static int random_fd = -1; +static DECLARE_WAIT_QUEUE_HEAD(host_read_wait); + +static int rng_dev_open (struct inode *inode, struct file *filp) +{ + /* enforce read-only access to this chrdev */ + if ((filp->f_mode & FMODE_READ) == 0) + return -EINVAL; + if ((filp->f_mode & FMODE_WRITE) != 0) + return -EINVAL; + + return 0; +} + +static atomic_t host_sleep_count = ATOMIC_INIT(0); + +static ssize_t rng_dev_read (struct file *filp, char __user *buf, size_t size, + loff_t *offp) +{ + u32 data; + int n, ret = 0, have_data; + + while (size) { + n = os_read_file(random_fd, &data, sizeof(data)); + if (n > 0) { + have_data = n; + while (have_data && size) { + if (put_user((u8) data, buf++)) { + ret = ret ? : -EFAULT; + break; + } + size--; + ret++; + have_data--; + data >>= 8; + } + } + else if (n == -EAGAIN) { + DECLARE_WAITQUEUE(wait, current); + + if (filp->f_flags & O_NONBLOCK) + return ret ? : -EAGAIN; + + atomic_inc(&host_sleep_count); + reactivate_fd(random_fd, RANDOM_IRQ); + add_sigio_fd(random_fd); + + add_wait_queue(&host_read_wait, &wait); + set_task_state(current, TASK_INTERRUPTIBLE); + + schedule(); + remove_wait_queue(&host_read_wait, &wait); + + if (atomic_dec_and_test(&host_sleep_count)) { + ignore_sigio_fd(random_fd); + deactivate_fd(random_fd, RANDOM_IRQ); + } + } + else + return n; + + if (signal_pending (current)) + return ret ? : -ERESTARTSYS; + } + return ret; +} + +static const struct file_operations rng_chrdev_ops = { + .owner = THIS_MODULE, + .open = rng_dev_open, + .read = rng_dev_read, + .llseek = noop_llseek, +}; + +/* rng_init shouldn't be called more than once at boot time */ +static struct miscdevice rng_miscdev = { + RNG_MISCDEV_MINOR, + RNG_MODULE_NAME, + &rng_chrdev_ops, +}; + +static irqreturn_t random_interrupt(int irq, void *data) +{ + wake_up(&host_read_wait); + + return IRQ_HANDLED; +} + +/* + * rng_init - initialize RNG module + */ +static int __init rng_init (void) +{ + int err; + + err = os_open_file("/dev/random", of_read(OPENFLAGS()), 0); + if (err < 0) + goto out; + + random_fd = err; + + err = um_request_irq(RANDOM_IRQ, random_fd, IRQ_READ, random_interrupt, + 0, "random", NULL); + if (err) + goto err_out_cleanup_hw; + + sigio_broken(random_fd, 1); + + err = misc_register (&rng_miscdev); + if (err) { + printk (KERN_ERR RNG_MODULE_NAME ": misc device register " + "failed\n"); + goto err_out_cleanup_hw; + } +out: + return err; + +err_out_cleanup_hw: + os_close_file(random_fd); + random_fd = -1; + goto out; +} + +/* + * rng_cleanup - shutdown RNG module + */ +static void __exit rng_cleanup (void) +{ + os_close_file(random_fd); + misc_deregister (&rng_miscdev); +} + +module_init (rng_init); +module_exit (rng_cleanup); + +MODULE_DESCRIPTION("UML Host Random Number Generator (RNG) driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/um/drivers/slip.h b/arch/um/drivers/slip.h new file mode 100644 index 000000000..c64f8c61d --- /dev/null +++ b/arch/um/drivers/slip.h @@ -0,0 +1,20 @@ +#ifndef __UM_SLIP_H +#define __UM_SLIP_H + +#include "slip_common.h" + +struct slip_data { + void *dev; + char name[sizeof("slnnnnn\0")]; + char *addr; + char *gate_addr; + int slave; + struct slip_proto slip; +}; + +extern const struct net_user_info slip_user_info; + +extern int slip_user_read(int fd, void *buf, int len, struct slip_data *pri); +extern int slip_user_write(int fd, void *buf, int len, struct slip_data *pri); + +#endif diff --git a/arch/um/drivers/slip_common.c b/arch/um/drivers/slip_common.c new file mode 100644 index 000000000..f597fa7c9 --- /dev/null +++ b/arch/um/drivers/slip_common.c @@ -0,0 +1,54 @@ +#include <string.h> +#include "slip_common.h" +#include <net_user.h> + +int slip_proto_read(int fd, void *buf, int len, struct slip_proto *slip) +{ + int i, n, size, start; + + if(slip->more > 0){ + i = 0; + while(i < slip->more){ + size = slip_unesc(slip->ibuf[i++], slip->ibuf, + &slip->pos, &slip->esc); + if(size){ + memcpy(buf, slip->ibuf, size); + memmove(slip->ibuf, &slip->ibuf[i], + slip->more - i); + slip->more = slip->more - i; + return size; + } + } + slip->more = 0; + } + + n = net_read(fd, &slip->ibuf[slip->pos], + sizeof(slip->ibuf) - slip->pos); + if(n <= 0) + return n; + + start = slip->pos; + for(i = 0; i < n; i++){ + size = slip_unesc(slip->ibuf[start + i], slip->ibuf,&slip->pos, + &slip->esc); + if(size){ + memcpy(buf, slip->ibuf, size); + memmove(slip->ibuf, &slip->ibuf[start+i+1], + n - (i + 1)); + slip->more = n - (i + 1); + return size; + } + } + return 0; +} + +int slip_proto_write(int fd, void *buf, int len, struct slip_proto *slip) +{ + int actual, n; + + actual = slip_esc(buf, slip->obuf, len); + n = net_write(fd, slip->obuf, actual); + if(n < 0) + return n; + else return len; +} diff --git a/arch/um/drivers/slip_common.h b/arch/um/drivers/slip_common.h new file mode 100644 index 000000000..d574e0a9d --- /dev/null +++ b/arch/um/drivers/slip_common.h @@ -0,0 +1,105 @@ +#ifndef __UM_SLIP_COMMON_H +#define __UM_SLIP_COMMON_H + +#define BUF_SIZE 1500 + /* two bytes each for a (pathological) max packet of escaped chars + * + * terminating END char + initial END char */ +#define ENC_BUF_SIZE (2 * BUF_SIZE + 2) + +/* SLIP protocol characters. */ +#define SLIP_END 0300 /* indicates end of frame */ +#define SLIP_ESC 0333 /* indicates byte stuffing */ +#define SLIP_ESC_END 0334 /* ESC ESC_END means END 'data' */ +#define SLIP_ESC_ESC 0335 /* ESC ESC_ESC means ESC 'data' */ + +static inline int slip_unesc(unsigned char c, unsigned char *buf, int *pos, + int *esc) +{ + int ret; + + switch(c){ + case SLIP_END: + *esc = 0; + ret=*pos; + *pos=0; + return(ret); + case SLIP_ESC: + *esc = 1; + return(0); + case SLIP_ESC_ESC: + if(*esc){ + *esc = 0; + c = SLIP_ESC; + } + break; + case SLIP_ESC_END: + if(*esc){ + *esc = 0; + c = SLIP_END; + } + break; + } + buf[(*pos)++] = c; + return(0); +} + +static inline int slip_esc(unsigned char *s, unsigned char *d, int len) +{ + unsigned char *ptr = d; + unsigned char c; + + /* + * Send an initial END character to flush out any + * data that may have accumulated in the receiver + * due to line noise. + */ + + *ptr++ = SLIP_END; + + /* + * For each byte in the packet, send the appropriate + * character sequence, according to the SLIP protocol. + */ + + while (len-- > 0) { + switch(c = *s++) { + case SLIP_END: + *ptr++ = SLIP_ESC; + *ptr++ = SLIP_ESC_END; + break; + case SLIP_ESC: + *ptr++ = SLIP_ESC; + *ptr++ = SLIP_ESC_ESC; + break; + default: + *ptr++ = c; + break; + } + } + *ptr++ = SLIP_END; + return (ptr - d); +} + +struct slip_proto { + unsigned char ibuf[ENC_BUF_SIZE]; + unsigned char obuf[ENC_BUF_SIZE]; + int more; /* more data: do not read fd until ibuf has been drained */ + int pos; + int esc; +}; + +static inline void slip_proto_init(struct slip_proto * slip) +{ + memset(slip->ibuf, 0, sizeof(slip->ibuf)); + memset(slip->obuf, 0, sizeof(slip->obuf)); + slip->more = 0; + slip->pos = 0; + slip->esc = 0; +} + +extern int slip_proto_read(int fd, void *buf, int len, + struct slip_proto *slip); +extern int slip_proto_write(int fd, void *buf, int len, + struct slip_proto *slip); + +#endif diff --git a/arch/um/drivers/slip_kern.c b/arch/um/drivers/slip_kern.c new file mode 100644 index 000000000..ed5249fc0 --- /dev/null +++ b/arch/um/drivers/slip_kern.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL. + */ + +#include <linux/if_arp.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <net_kern.h> +#include "slip.h" + +struct slip_init { + char *gate_addr; +}; + +static void slip_init(struct net_device *dev, void *data) +{ + struct uml_net_private *private; + struct slip_data *spri; + struct slip_init *init = data; + + private = netdev_priv(dev); + spri = (struct slip_data *) private->user; + + memset(spri->name, 0, sizeof(spri->name)); + spri->addr = NULL; + spri->gate_addr = init->gate_addr; + spri->slave = -1; + spri->dev = dev; + + slip_proto_init(&spri->slip); + + dev->hard_header_len = 0; + dev->header_ops = NULL; + dev->addr_len = 0; + dev->type = ARPHRD_SLIP; + dev->tx_queue_len = 256; + dev->flags = IFF_NOARP; + printk("SLIP backend - SLIP IP = %s\n", spri->gate_addr); +} + +static unsigned short slip_protocol(struct sk_buff *skbuff) +{ + return htons(ETH_P_IP); +} + +static int slip_read(int fd, struct sk_buff *skb, struct uml_net_private *lp) +{ + return slip_user_read(fd, skb_mac_header(skb), skb->dev->mtu, + (struct slip_data *) &lp->user); +} + +static int slip_write(int fd, struct sk_buff *skb, struct uml_net_private *lp) +{ + return slip_user_write(fd, skb->data, skb->len, + (struct slip_data *) &lp->user); +} + +static const struct net_kern_info slip_kern_info = { + .init = slip_init, + .protocol = slip_protocol, + .read = slip_read, + .write = slip_write, +}; + +static int slip_setup(char *str, char **mac_out, void *data) +{ + struct slip_init *init = data; + + *init = ((struct slip_init) { .gate_addr = NULL }); + + if (str[0] != '\0') + init->gate_addr = str; + return 1; +} + +static struct transport slip_transport = { + .list = LIST_HEAD_INIT(slip_transport.list), + .name = "slip", + .setup = slip_setup, + .user = &slip_user_info, + .kern = &slip_kern_info, + .private_size = sizeof(struct slip_data), + .setup_size = sizeof(struct slip_init), +}; + +static int register_slip(void) +{ + register_transport(&slip_transport); + return 0; +} + +late_initcall(register_slip); diff --git a/arch/um/drivers/slip_user.c b/arch/um/drivers/slip_user.c new file mode 100644 index 000000000..55c290d92 --- /dev/null +++ b/arch/um/drivers/slip_user.c @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <sys/termios.h> +#include <sys/wait.h> +#include <net_user.h> +#include <os.h> +#include "slip.h" +#include <um_malloc.h> + +static int slip_user_init(void *data, void *dev) +{ + struct slip_data *pri = data; + + pri->dev = dev; + return 0; +} + +static int set_up_tty(int fd) +{ + int i; + struct termios tios; + + if (tcgetattr(fd, &tios) < 0) { + printk(UM_KERN_ERR "could not get initial terminal " + "attributes\n"); + return -1; + } + + tios.c_cflag = CS8 | CREAD | HUPCL | CLOCAL; + tios.c_iflag = IGNBRK | IGNPAR; + tios.c_oflag = 0; + tios.c_lflag = 0; + for (i = 0; i < NCCS; i++) + tios.c_cc[i] = 0; + tios.c_cc[VMIN] = 1; + tios.c_cc[VTIME] = 0; + + cfsetospeed(&tios, B38400); + cfsetispeed(&tios, B38400); + + if (tcsetattr(fd, TCSAFLUSH, &tios) < 0) { + printk(UM_KERN_ERR "failed to set terminal attributes\n"); + return -1; + } + return 0; +} + +struct slip_pre_exec_data { + int stdin; + int stdout; + int close_me; +}; + +static void slip_pre_exec(void *arg) +{ + struct slip_pre_exec_data *data = arg; + + if (data->stdin >= 0) + dup2(data->stdin, 0); + dup2(data->stdout, 1); + if (data->close_me >= 0) + close(data->close_me); +} + +static int slip_tramp(char **argv, int fd) +{ + struct slip_pre_exec_data pe_data; + char *output; + int pid, fds[2], err, output_len; + + err = os_pipe(fds, 1, 0); + if (err < 0) { + printk(UM_KERN_ERR "slip_tramp : pipe failed, err = %d\n", + -err); + goto out; + } + + err = 0; + pe_data.stdin = fd; + pe_data.stdout = fds[1]; + pe_data.close_me = fds[0]; + err = run_helper(slip_pre_exec, &pe_data, argv); + if (err < 0) + goto out_close; + pid = err; + + output_len = UM_KERN_PAGE_SIZE; + output = uml_kmalloc(output_len, UM_GFP_KERNEL); + if (output == NULL) { + printk(UM_KERN_ERR "slip_tramp : failed to allocate output " + "buffer\n"); + os_kill_process(pid, 1); + err = -ENOMEM; + goto out_close; + } + + close(fds[1]); + read_output(fds[0], output, output_len); + printk("%s", output); + + err = helper_wait(pid); + close(fds[0]); + + kfree(output); + return err; + +out_close: + close(fds[0]); + close(fds[1]); +out: + return err; +} + +static int slip_open(void *data) +{ + struct slip_data *pri = data; + char version_buf[sizeof("nnnnn\0")]; + char gate_buf[sizeof("nnn.nnn.nnn.nnn\0")]; + char *argv[] = { "uml_net", version_buf, "slip", "up", gate_buf, + NULL }; + int sfd, mfd, err; + + err = get_pty(); + if (err < 0) { + printk(UM_KERN_ERR "slip-open : Failed to open pty, err = %d\n", + -err); + goto out; + } + mfd = err; + + err = open(ptsname(mfd), O_RDWR, 0); + if (err < 0) { + printk(UM_KERN_ERR "Couldn't open tty for slip line, " + "err = %d\n", -err); + goto out_close; + } + sfd = err; + + if (set_up_tty(sfd)) + goto out_close2; + + pri->slave = sfd; + pri->slip.pos = 0; + pri->slip.esc = 0; + if (pri->gate_addr != NULL) { + sprintf(version_buf, "%d", UML_NET_VERSION); + strcpy(gate_buf, pri->gate_addr); + + err = slip_tramp(argv, sfd); + + if (err < 0) { + printk(UM_KERN_ERR "slip_tramp failed - err = %d\n", + -err); + goto out_close2; + } + err = os_get_ifname(pri->slave, pri->name); + if (err < 0) { + printk(UM_KERN_ERR "get_ifname failed, err = %d\n", + -err); + goto out_close2; + } + iter_addresses(pri->dev, open_addr, pri->name); + } + else { + err = os_set_slip(sfd); + if (err < 0) { + printk(UM_KERN_ERR "Failed to set slip discipline " + "encapsulation - err = %d\n", -err); + goto out_close2; + } + } + return mfd; +out_close2: + close(sfd); +out_close: + close(mfd); +out: + return err; +} + +static void slip_close(int fd, void *data) +{ + struct slip_data *pri = data; + char version_buf[sizeof("nnnnn\0")]; + char *argv[] = { "uml_net", version_buf, "slip", "down", pri->name, + NULL }; + int err; + + if (pri->gate_addr != NULL) + iter_addresses(pri->dev, close_addr, pri->name); + + sprintf(version_buf, "%d", UML_NET_VERSION); + + err = slip_tramp(argv, pri->slave); + + if (err != 0) + printk(UM_KERN_ERR "slip_tramp failed - errno = %d\n", -err); + close(fd); + close(pri->slave); + pri->slave = -1; +} + +int slip_user_read(int fd, void *buf, int len, struct slip_data *pri) +{ + return slip_proto_read(fd, buf, len, &pri->slip); +} + +int slip_user_write(int fd, void *buf, int len, struct slip_data *pri) +{ + return slip_proto_write(fd, buf, len, &pri->slip); +} + +static void slip_add_addr(unsigned char *addr, unsigned char *netmask, + void *data) +{ + struct slip_data *pri = data; + + if (pri->slave < 0) + return; + open_addr(addr, netmask, pri->name); +} + +static void slip_del_addr(unsigned char *addr, unsigned char *netmask, + void *data) +{ + struct slip_data *pri = data; + + if (pri->slave < 0) + return; + close_addr(addr, netmask, pri->name); +} + +const struct net_user_info slip_user_info = { + .init = slip_user_init, + .open = slip_open, + .close = slip_close, + .remove = NULL, + .add_address = slip_add_addr, + .delete_address = slip_del_addr, + .mtu = BUF_SIZE, + .max_packet = BUF_SIZE, +}; diff --git a/arch/um/drivers/slirp.h b/arch/um/drivers/slirp.h new file mode 100644 index 000000000..89ccf83b7 --- /dev/null +++ b/arch/um/drivers/slirp.h @@ -0,0 +1,33 @@ +#ifndef __UM_SLIRP_H +#define __UM_SLIRP_H + +#include "slip_common.h" + +#define SLIRP_MAX_ARGS 100 +/* + * XXX this next definition is here because I don't understand why this + * initializer doesn't work in slirp_kern.c: + * + * argv : { init->argv[ 0 ... SLIRP_MAX_ARGS-1 ] }, + * + * or why I can't typecast like this: + * + * argv : (char* [SLIRP_MAX_ARGS])(init->argv), + */ +struct arg_list_dummy_wrapper { char *argv[SLIRP_MAX_ARGS]; }; + +struct slirp_data { + void *dev; + struct arg_list_dummy_wrapper argw; + int pid; + int slave; + struct slip_proto slip; +}; + +extern const struct net_user_info slirp_user_info; + +extern int slirp_user_read(int fd, void *buf, int len, struct slirp_data *pri); +extern int slirp_user_write(int fd, void *buf, int len, + struct slirp_data *pri); + +#endif diff --git a/arch/um/drivers/slirp_kern.c b/arch/um/drivers/slirp_kern.c new file mode 100644 index 000000000..4ef11ca7c --- /dev/null +++ b/arch/um/drivers/slirp_kern.c @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL. + */ + +#include <linux/if_arp.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/string.h> +#include <net_kern.h> +#include <net_user.h> +#include "slirp.h" + +struct slirp_init { + struct arg_list_dummy_wrapper argw; /* XXX should be simpler... */ +}; + +void slirp_init(struct net_device *dev, void *data) +{ + struct uml_net_private *private; + struct slirp_data *spri; + struct slirp_init *init = data; + int i; + + private = netdev_priv(dev); + spri = (struct slirp_data *) private->user; + + spri->argw = init->argw; + spri->pid = -1; + spri->slave = -1; + spri->dev = dev; + + slip_proto_init(&spri->slip); + + dev->hard_header_len = 0; + dev->header_ops = NULL; + dev->addr_len = 0; + dev->type = ARPHRD_SLIP; + dev->tx_queue_len = 256; + dev->flags = IFF_NOARP; + printk("SLIRP backend - command line:"); + for (i = 0; spri->argw.argv[i] != NULL; i++) + printk(" '%s'",spri->argw.argv[i]); + printk("\n"); +} + +static unsigned short slirp_protocol(struct sk_buff *skbuff) +{ + return htons(ETH_P_IP); +} + +static int slirp_read(int fd, struct sk_buff *skb, struct uml_net_private *lp) +{ + return slirp_user_read(fd, skb_mac_header(skb), skb->dev->mtu, + (struct slirp_data *) &lp->user); +} + +static int slirp_write(int fd, struct sk_buff *skb, struct uml_net_private *lp) +{ + return slirp_user_write(fd, skb->data, skb->len, + (struct slirp_data *) &lp->user); +} + +const struct net_kern_info slirp_kern_info = { + .init = slirp_init, + .protocol = slirp_protocol, + .read = slirp_read, + .write = slirp_write, +}; + +static int slirp_setup(char *str, char **mac_out, void *data) +{ + struct slirp_init *init = data; + int i=0; + + *init = ((struct slirp_init) { .argw = { { "slirp", NULL } } }); + + str = split_if_spec(str, mac_out, NULL); + + if (str == NULL) /* no command line given after MAC addr */ + return 1; + + do { + if (i >= SLIRP_MAX_ARGS - 1) { + printk(KERN_WARNING "slirp_setup: truncating slirp " + "arguments\n"); + break; + } + init->argw.argv[i++] = str; + while(*str && *str!=',') { + if (*str == '_') + *str=' '; + str++; + } + if (*str != ',') + break; + *str++ = '\0'; + } while (1); + + init->argw.argv[i] = NULL; + return 1; +} + +static struct transport slirp_transport = { + .list = LIST_HEAD_INIT(slirp_transport.list), + .name = "slirp", + .setup = slirp_setup, + .user = &slirp_user_info, + .kern = &slirp_kern_info, + .private_size = sizeof(struct slirp_data), + .setup_size = sizeof(struct slirp_init), +}; + +static int register_slirp(void) +{ + register_transport(&slirp_transport); + return 0; +} + +late_initcall(register_slirp); diff --git a/arch/um/drivers/slirp_user.c b/arch/um/drivers/slirp_user.c new file mode 100644 index 000000000..c999d187a --- /dev/null +++ b/arch/um/drivers/slirp_user.c @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL. + */ + +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/wait.h> +#include <net_user.h> +#include <os.h> +#include "slirp.h" + +static int slirp_user_init(void *data, void *dev) +{ + struct slirp_data *pri = data; + + pri->dev = dev; + return 0; +} + +struct slirp_pre_exec_data { + int stdin; + int stdout; +}; + +static void slirp_pre_exec(void *arg) +{ + struct slirp_pre_exec_data *data = arg; + + if (data->stdin != -1) + dup2(data->stdin, 0); + if (data->stdout != -1) + dup2(data->stdout, 1); +} + +static int slirp_tramp(char **argv, int fd) +{ + struct slirp_pre_exec_data pe_data; + int pid; + + pe_data.stdin = fd; + pe_data.stdout = fd; + pid = run_helper(slirp_pre_exec, &pe_data, argv); + + return pid; +} + +static int slirp_open(void *data) +{ + struct slirp_data *pri = data; + int fds[2], pid, err; + + err = os_pipe(fds, 1, 1); + if (err) + return err; + + err = slirp_tramp(pri->argw.argv, fds[1]); + if (err < 0) { + printk(UM_KERN_ERR "slirp_tramp failed - errno = %d\n", -err); + goto out; + } + pid = err; + + pri->slave = fds[1]; + pri->slip.pos = 0; + pri->slip.esc = 0; + pri->pid = err; + + return fds[0]; +out: + close(fds[0]); + close(fds[1]); + return err; +} + +static void slirp_close(int fd, void *data) +{ + struct slirp_data *pri = data; + int err; + + close(fd); + close(pri->slave); + + pri->slave = -1; + + if (pri->pid<1) { + printk(UM_KERN_ERR "slirp_close: no child process to shut " + "down\n"); + return; + } + +#if 0 + if (kill(pri->pid, SIGHUP)<0) { + printk(UM_KERN_ERR "slirp_close: sending hangup to %d failed " + "(%d)\n", pri->pid, errno); + } +#endif + err = helper_wait(pri->pid); + if (err < 0) + return; + + pri->pid = -1; +} + +int slirp_user_read(int fd, void *buf, int len, struct slirp_data *pri) +{ + return slip_proto_read(fd, buf, len, &pri->slip); +} + +int slirp_user_write(int fd, void *buf, int len, struct slirp_data *pri) +{ + return slip_proto_write(fd, buf, len, &pri->slip); +} + +const struct net_user_info slirp_user_info = { + .init = slirp_user_init, + .open = slirp_open, + .close = slirp_close, + .remove = NULL, + .add_address = NULL, + .delete_address = NULL, + .mtu = BUF_SIZE, + .max_packet = BUF_SIZE, +}; diff --git a/arch/um/drivers/ssl.c b/arch/um/drivers/ssl.c new file mode 100644 index 000000000..b8d14fa52 --- /dev/null +++ b/arch/um/drivers/ssl.c @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2000, 2002 Jeff Dike (jdike@karaya.com) + * Licensed under the GPL + */ + +#include <linux/fs.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/major.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/console.h> +#include <asm/termbits.h> +#include <asm/irq.h> +#include "ssl.h" +#include "chan.h" +#include <init.h> +#include <irq_user.h> +#include "mconsole_kern.h" + +static const int ssl_version = 1; + +#define NR_PORTS 64 + +static void ssl_announce(char *dev_name, int dev) +{ + printk(KERN_INFO "Serial line %d assigned device '%s'\n", dev, + dev_name); +} + +/* Almost const, except that xterm_title may be changed in an initcall */ +static struct chan_opts opts = { + .announce = ssl_announce, + .xterm_title = "Serial Line #%d", + .raw = 1, +}; + +static int ssl_config(char *str, char **error_out); +static int ssl_get_config(char *dev, char *str, int size, char **error_out); +static int ssl_remove(int n, char **error_out); + + +/* Const, except for .mc.list */ +static struct line_driver driver = { + .name = "UML serial line", + .device_name = "ttyS", + .major = TTY_MAJOR, + .minor_start = 64, + .type = TTY_DRIVER_TYPE_SERIAL, + .subtype = 0, + .read_irq = SSL_IRQ, + .read_irq_name = "ssl", + .write_irq = SSL_WRITE_IRQ, + .write_irq_name = "ssl-write", + .mc = { + .list = LIST_HEAD_INIT(driver.mc.list), + .name = "ssl", + .config = ssl_config, + .get_config = ssl_get_config, + .id = line_id, + .remove = ssl_remove, + }, +}; + +/* The array is initialized by line_init, at initcall time. The + * elements are locked individually as needed. + */ +static char *conf[NR_PORTS]; +static char *def_conf = CONFIG_SSL_CHAN; +static struct line serial_lines[NR_PORTS]; + +static int ssl_config(char *str, char **error_out) +{ + return line_config(serial_lines, ARRAY_SIZE(serial_lines), str, &opts, + error_out); +} + +static int ssl_get_config(char *dev, char *str, int size, char **error_out) +{ + return line_get_config(dev, serial_lines, ARRAY_SIZE(serial_lines), str, + size, error_out); +} + +static int ssl_remove(int n, char **error_out) +{ + return line_remove(serial_lines, ARRAY_SIZE(serial_lines), n, + error_out); +} + +static int ssl_install(struct tty_driver *driver, struct tty_struct *tty) +{ + return line_install(driver, tty, &serial_lines[tty->index]); +} + +static const struct tty_operations ssl_ops = { + .open = line_open, + .close = line_close, + .write = line_write, + .put_char = line_put_char, + .write_room = line_write_room, + .chars_in_buffer = line_chars_in_buffer, + .flush_buffer = line_flush_buffer, + .flush_chars = line_flush_chars, + .set_termios = line_set_termios, + .throttle = line_throttle, + .unthrottle = line_unthrottle, + .install = ssl_install, + .hangup = line_hangup, +}; + +/* Changed by ssl_init and referenced by ssl_exit, which are both serialized + * by being an initcall and exitcall, respectively. + */ +static int ssl_init_done = 0; + +static void ssl_console_write(struct console *c, const char *string, + unsigned len) +{ + struct line *line = &serial_lines[c->index]; + unsigned long flags; + + spin_lock_irqsave(&line->lock, flags); + console_write_chan(line->chan_out, string, len); + spin_unlock_irqrestore(&line->lock, flags); +} + +static struct tty_driver *ssl_console_device(struct console *c, int *index) +{ + *index = c->index; + return driver.driver; +} + +static int ssl_console_setup(struct console *co, char *options) +{ + struct line *line = &serial_lines[co->index]; + + return console_open_chan(line, co); +} + +/* No locking for register_console call - relies on single-threaded initcalls */ +static struct console ssl_cons = { + .name = "ttyS", + .write = ssl_console_write, + .device = ssl_console_device, + .setup = ssl_console_setup, + .flags = CON_PRINTBUFFER|CON_ANYTIME, + .index = -1, +}; + +static int ssl_init(void) +{ + char *new_title; + int err; + int i; + + printk(KERN_INFO "Initializing software serial port version %d\n", + ssl_version); + + err = register_lines(&driver, &ssl_ops, serial_lines, + ARRAY_SIZE(serial_lines)); + if (err) + return err; + + new_title = add_xterm_umid(opts.xterm_title); + if (new_title != NULL) + opts.xterm_title = new_title; + + for (i = 0; i < NR_PORTS; i++) { + char *error; + char *s = conf[i]; + if (!s) + s = def_conf; + if (setup_one_line(serial_lines, i, s, &opts, &error)) + printk(KERN_ERR "setup_one_line failed for " + "device %d : %s\n", i, error); + } + + ssl_init_done = 1; + register_console(&ssl_cons); + return 0; +} +late_initcall(ssl_init); + +static void ssl_exit(void) +{ + if (!ssl_init_done) + return; + close_lines(serial_lines, ARRAY_SIZE(serial_lines)); +} +__uml_exitcall(ssl_exit); + +static int ssl_chan_setup(char *str) +{ + line_setup(conf, NR_PORTS, &def_conf, str, "serial line"); + return 1; +} + +__setup("ssl", ssl_chan_setup); +__channel_help(ssl_chan_setup, "ssl"); diff --git a/arch/um/drivers/ssl.h b/arch/um/drivers/ssl.h new file mode 100644 index 000000000..314d17725 --- /dev/null +++ b/arch/um/drivers/ssl.h @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2000 Jeff Dike (jdike@karaya.com) + * Licensed under the GPL + */ + +#ifndef __SSL_H__ +#define __SSL_H__ + +extern int ssl_read(int fd, int line); +extern void ssl_receive_char(int line, char ch); + +#endif + diff --git a/arch/um/drivers/stderr_console.c b/arch/um/drivers/stderr_console.c new file mode 100644 index 000000000..d07a97f8b --- /dev/null +++ b/arch/um/drivers/stderr_console.c @@ -0,0 +1,62 @@ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/console.h> + +#include "chan_user.h" + +/* ----------------------------------------------------------------------------- */ +/* trivial console driver -- simply dump everything to stderr */ + +/* + * Don't register by default -- as this registers very early in the + * boot process it becomes the default console. + * + * Initialized at init time. + */ +static int use_stderr_console = 0; + +static void stderr_console_write(struct console *console, const char *string, + unsigned len) +{ + generic_write(2 /* stderr */, string, len, NULL); +} + +static struct console stderr_console = { + .name = "stderr", + .write = stderr_console_write, + .flags = CON_PRINTBUFFER, +}; + +static int __init stderr_console_init(void) +{ + if (use_stderr_console) + register_console(&stderr_console); + return 0; +} +console_initcall(stderr_console_init); + +static int stderr_setup(char *str) +{ + if (!str) + return 0; + use_stderr_console = simple_strtoul(str,&str,0); + return 1; +} +__setup("stderr=", stderr_setup); + +/* The previous behavior of not unregistering led to /dev/console being + * impossible to open. My FC5 filesystem started having init die, and the + * system panicing because of this. Unregistering causes the real + * console to become the default console, and /dev/console can then be + * opened. Making this an initcall makes this happen late enough that + * there is no added value in dumping everything to stderr, and the + * normal console is good enough to show you all available output. + */ +static int __init unregister_stderr(void) +{ + unregister_console(&stderr_console); + + return 0; +} + +__initcall(unregister_stderr); diff --git a/arch/um/drivers/stdio_console.c b/arch/um/drivers/stdio_console.c new file mode 100644 index 000000000..7b361f36c --- /dev/null +++ b/arch/um/drivers/stdio_console.c @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2000, 2001 Jeff Dike (jdike@karaya.com) + * Licensed under the GPL + */ + +#include <linux/posix_types.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/types.h> +#include <linux/major.h> +#include <linux/kdev_t.h> +#include <linux/console.h> +#include <linux/string.h> +#include <linux/sched.h> +#include <linux/list.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/hardirq.h> +#include <asm/current.h> +#include <asm/irq.h> +#include "stdio_console.h" +#include "chan.h" +#include <irq_user.h> +#include "mconsole_kern.h" +#include <init.h> + +#define MAX_TTYS (16) + +static void stdio_announce(char *dev_name, int dev) +{ + printk(KERN_INFO "Virtual console %d assigned device '%s'\n", dev, + dev_name); +} + +/* Almost const, except that xterm_title may be changed in an initcall */ +static struct chan_opts opts = { + .announce = stdio_announce, + .xterm_title = "Virtual Console #%d", + .raw = 1, +}; + +static int con_config(char *str, char **error_out); +static int con_get_config(char *dev, char *str, int size, char **error_out); +static int con_remove(int n, char **con_remove); + + +/* Const, except for .mc.list */ +static struct line_driver driver = { + .name = "UML console", + .device_name = "tty", + .major = TTY_MAJOR, + .minor_start = 0, + .type = TTY_DRIVER_TYPE_CONSOLE, + .subtype = SYSTEM_TYPE_CONSOLE, + .read_irq = CONSOLE_IRQ, + .read_irq_name = "console", + .write_irq = CONSOLE_WRITE_IRQ, + .write_irq_name = "console-write", + .mc = { + .list = LIST_HEAD_INIT(driver.mc.list), + .name = "con", + .config = con_config, + .get_config = con_get_config, + .id = line_id, + .remove = con_remove, + }, +}; + +/* The array is initialized by line_init, at initcall time. The + * elements are locked individually as needed. + */ +static char *vt_conf[MAX_TTYS]; +static char *def_conf; +static struct line vts[MAX_TTYS]; + +static int con_config(char *str, char **error_out) +{ + return line_config(vts, ARRAY_SIZE(vts), str, &opts, error_out); +} + +static int con_get_config(char *dev, char *str, int size, char **error_out) +{ + return line_get_config(dev, vts, ARRAY_SIZE(vts), str, size, error_out); +} + +static int con_remove(int n, char **error_out) +{ + return line_remove(vts, ARRAY_SIZE(vts), n, error_out); +} + +/* Set in an initcall, checked in an exitcall */ +static int con_init_done = 0; + +static int con_install(struct tty_driver *driver, struct tty_struct *tty) +{ + return line_install(driver, tty, &vts[tty->index]); +} + +static const struct tty_operations console_ops = { + .open = line_open, + .install = con_install, + .close = line_close, + .write = line_write, + .put_char = line_put_char, + .write_room = line_write_room, + .chars_in_buffer = line_chars_in_buffer, + .flush_buffer = line_flush_buffer, + .flush_chars = line_flush_chars, + .set_termios = line_set_termios, + .throttle = line_throttle, + .unthrottle = line_unthrottle, + .hangup = line_hangup, +}; + +static void uml_console_write(struct console *console, const char *string, + unsigned len) +{ + struct line *line = &vts[console->index]; + unsigned long flags; + + spin_lock_irqsave(&line->lock, flags); + console_write_chan(line->chan_out, string, len); + spin_unlock_irqrestore(&line->lock, flags); +} + +static struct tty_driver *uml_console_device(struct console *c, int *index) +{ + *index = c->index; + return driver.driver; +} + +static int uml_console_setup(struct console *co, char *options) +{ + struct line *line = &vts[co->index]; + + return console_open_chan(line, co); +} + +/* No locking for register_console call - relies on single-threaded initcalls */ +static struct console stdiocons = { + .name = "tty", + .write = uml_console_write, + .device = uml_console_device, + .setup = uml_console_setup, + .flags = CON_PRINTBUFFER|CON_ANYTIME, + .index = -1, +}; + +static int stdio_init(void) +{ + char *new_title; + int err; + int i; + + err = register_lines(&driver, &console_ops, vts, + ARRAY_SIZE(vts)); + if (err) + return err; + + printk(KERN_INFO "Initialized stdio console driver\n"); + + new_title = add_xterm_umid(opts.xterm_title); + if(new_title != NULL) + opts.xterm_title = new_title; + + for (i = 0; i < MAX_TTYS; i++) { + char *error; + char *s = vt_conf[i]; + if (!s) + s = def_conf; + if (!s) + s = i ? CONFIG_CON_CHAN : CONFIG_CON_ZERO_CHAN; + if (setup_one_line(vts, i, s, &opts, &error)) + printk(KERN_ERR "setup_one_line failed for " + "device %d : %s\n", i, error); + } + + con_init_done = 1; + register_console(&stdiocons); + return 0; +} +late_initcall(stdio_init); + +static void console_exit(void) +{ + if (!con_init_done) + return; + close_lines(vts, ARRAY_SIZE(vts)); +} +__uml_exitcall(console_exit); + +static int console_chan_setup(char *str) +{ + line_setup(vt_conf, MAX_TTYS, &def_conf, str, "console"); + return 1; +} +__setup("con", console_chan_setup); +__channel_help(console_chan_setup, "con"); diff --git a/arch/um/drivers/stdio_console.h b/arch/um/drivers/stdio_console.h new file mode 100644 index 000000000..6d8275f71 --- /dev/null +++ b/arch/um/drivers/stdio_console.h @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2000 Jeff Dike (jdike@karaya.com) + * Licensed under the GPL + */ + +#ifndef __STDIO_CONSOLE_H +#define __STDIO_CONSOLE_H + +extern void save_console_flags(void); +#endif + diff --git a/arch/um/drivers/tty.c b/arch/um/drivers/tty.c new file mode 100644 index 000000000..eaa201bca --- /dev/null +++ b/arch/um/drivers/tty.c @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com) + * Licensed under the GPL + */ + +#include <errno.h> +#include <fcntl.h> +#include <termios.h> +#include "chan_user.h" +#include <os.h> +#include <um_malloc.h> + +struct tty_chan { + char *dev; + int raw; + struct termios tt; +}; + +static void *tty_chan_init(char *str, int device, const struct chan_opts *opts) +{ + struct tty_chan *data; + + if (*str != ':') { + printk(UM_KERN_ERR "tty_init : channel type 'tty' must specify " + "a device\n"); + return NULL; + } + str++; + + data = uml_kmalloc(sizeof(*data), UM_GFP_KERNEL); + if (data == NULL) + return NULL; + *data = ((struct tty_chan) { .dev = str, + .raw = opts->raw }); + + return data; +} + +static int tty_open(int input, int output, int primary, void *d, + char **dev_out) +{ + struct tty_chan *data = d; + int fd, err, mode = 0; + + if (input && output) + mode = O_RDWR; + else if (input) + mode = O_RDONLY; + else if (output) + mode = O_WRONLY; + + fd = open(data->dev, mode); + if (fd < 0) + return -errno; + + if (data->raw) { + CATCH_EINTR(err = tcgetattr(fd, &data->tt)); + if (err) + return err; + + err = raw(fd); + if (err) + return err; + } + + *dev_out = data->dev; + return fd; +} + +const struct chan_ops tty_ops = { + .type = "tty", + .init = tty_chan_init, + .open = tty_open, + .close = generic_close, + .read = generic_read, + .write = generic_write, + .console_write = generic_console_write, + .window_size = generic_window_size, + .free = generic_free, + .winch = 0, +}; diff --git a/arch/um/drivers/ubd.h b/arch/um/drivers/ubd.h new file mode 100644 index 000000000..3b48cd208 --- /dev/null +++ b/arch/um/drivers/ubd.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2000 Jeff Dike (jdike@karaya.com) + * Copyright (C) 2001 RidgeRun, Inc (glonnon@ridgerun.com) + * Licensed under the GPL + */ + +#ifndef __UM_UBD_USER_H +#define __UM_UBD_USER_H + +extern int start_io_thread(unsigned long sp, int *fds_out); +extern int io_thread(void *arg); +extern int kernel_fd; + +#endif + diff --git a/arch/um/drivers/ubd_kern.c b/arch/um/drivers/ubd_kern.c new file mode 100644 index 000000000..e8ab93c3e --- /dev/null +++ b/arch/um/drivers/ubd_kern.c @@ -0,0 +1,1505 @@ +/* + * Copyright (C) 2000 Jeff Dike (jdike@karaya.com) + * Licensed under the GPL + */ + +/* 2001-09-28...2002-04-17 + * Partition stuff by James_McMechan@hotmail.com + * old style ubd by setting UBD_SHIFT to 0 + * 2002-09-27...2002-10-18 massive tinkering for 2.5 + * partitions have changed in 2.5 + * 2003-01-29 more tinkering for 2.5.59-1 + * This should now address the sysfs problems and has + * the symlink for devfs to allow for booting with + * the common /dev/ubd/discX/... names rather than + * only /dev/ubdN/discN this version also has lots of + * clean ups preparing for ubd-many. + * James McMechan + */ + +#define UBD_SHIFT 4 + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/blkdev.h> +#include <linux/ata.h> +#include <linux/hdreg.h> +#include <linux/cdrom.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/platform_device.h> +#include <linux/scatterlist.h> +#include <asm/tlbflush.h> +#include <kern_util.h> +#include "mconsole_kern.h" +#include <init.h> +#include <irq_kern.h> +#include "ubd.h" +#include <os.h> +#include "cow.h" + +enum ubd_req { UBD_READ, UBD_WRITE, UBD_FLUSH }; + +struct io_thread_req { + struct request *req; + enum ubd_req op; + int fds[2]; + unsigned long offsets[2]; + unsigned long long offset; + unsigned long length; + char *buffer; + int sectorsize; + unsigned long sector_mask; + unsigned long long cow_offset; + unsigned long bitmap_words[2]; + int error; +}; + +static inline int ubd_test_bit(__u64 bit, unsigned char *data) +{ + __u64 n; + int bits, off; + + bits = sizeof(data[0]) * 8; + n = bit / bits; + off = bit % bits; + return (data[n] & (1 << off)) != 0; +} + +static inline void ubd_set_bit(__u64 bit, unsigned char *data) +{ + __u64 n; + int bits, off; + + bits = sizeof(data[0]) * 8; + n = bit / bits; + off = bit % bits; + data[n] |= (1 << off); +} +/*End stuff from ubd_user.h*/ + +#define DRIVER_NAME "uml-blkdev" + +static DEFINE_MUTEX(ubd_lock); +static DEFINE_MUTEX(ubd_mutex); /* replaces BKL, might not be needed */ + +static int ubd_open(struct block_device *bdev, fmode_t mode); +static void ubd_release(struct gendisk *disk, fmode_t mode); +static int ubd_ioctl(struct block_device *bdev, fmode_t mode, + unsigned int cmd, unsigned long arg); +static int ubd_getgeo(struct block_device *bdev, struct hd_geometry *geo); + +#define MAX_DEV (16) + +static const struct block_device_operations ubd_blops = { + .owner = THIS_MODULE, + .open = ubd_open, + .release = ubd_release, + .ioctl = ubd_ioctl, + .getgeo = ubd_getgeo, +}; + +/* Protected by ubd_lock */ +static int fake_major = UBD_MAJOR; +static struct gendisk *ubd_gendisk[MAX_DEV]; +static struct gendisk *fake_gendisk[MAX_DEV]; + +#ifdef CONFIG_BLK_DEV_UBD_SYNC +#define OPEN_FLAGS ((struct openflags) { .r = 1, .w = 1, .s = 1, .c = 0, \ + .cl = 1 }) +#else +#define OPEN_FLAGS ((struct openflags) { .r = 1, .w = 1, .s = 0, .c = 0, \ + .cl = 1 }) +#endif +static struct openflags global_openflags = OPEN_FLAGS; + +struct cow { + /* backing file name */ + char *file; + /* backing file fd */ + int fd; + unsigned long *bitmap; + unsigned long bitmap_len; + int bitmap_offset; + int data_offset; +}; + +#define MAX_SG 64 + +struct ubd { + struct list_head restart; + /* name (and fd, below) of the file opened for writing, either the + * backing or the cow file. */ + char *file; + int count; + int fd; + __u64 size; + struct openflags boot_openflags; + struct openflags openflags; + unsigned shared:1; + unsigned no_cow:1; + struct cow cow; + struct platform_device pdev; + struct request_queue *queue; + spinlock_t lock; + struct scatterlist sg[MAX_SG]; + struct request *request; + int start_sg, end_sg; + sector_t rq_pos; +}; + +#define DEFAULT_COW { \ + .file = NULL, \ + .fd = -1, \ + .bitmap = NULL, \ + .bitmap_offset = 0, \ + .data_offset = 0, \ +} + +#define DEFAULT_UBD { \ + .file = NULL, \ + .count = 0, \ + .fd = -1, \ + .size = -1, \ + .boot_openflags = OPEN_FLAGS, \ + .openflags = OPEN_FLAGS, \ + .no_cow = 0, \ + .shared = 0, \ + .cow = DEFAULT_COW, \ + .lock = __SPIN_LOCK_UNLOCKED(ubd_devs.lock), \ + .request = NULL, \ + .start_sg = 0, \ + .end_sg = 0, \ + .rq_pos = 0, \ +} + +/* Protected by ubd_lock */ +static struct ubd ubd_devs[MAX_DEV] = { [0 ... MAX_DEV - 1] = DEFAULT_UBD }; + +/* Only changed by fake_ide_setup which is a setup */ +static int fake_ide = 0; +static struct proc_dir_entry *proc_ide_root = NULL; +static struct proc_dir_entry *proc_ide = NULL; + +static void make_proc_ide(void) +{ + proc_ide_root = proc_mkdir("ide", NULL); + proc_ide = proc_mkdir("ide0", proc_ide_root); +} + +static int fake_ide_media_proc_show(struct seq_file *m, void *v) +{ + seq_puts(m, "disk\n"); + return 0; +} + +static int fake_ide_media_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, fake_ide_media_proc_show, NULL); +} + +static const struct file_operations fake_ide_media_proc_fops = { + .owner = THIS_MODULE, + .open = fake_ide_media_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void make_ide_entries(const char *dev_name) +{ + struct proc_dir_entry *dir, *ent; + char name[64]; + + if(proc_ide_root == NULL) make_proc_ide(); + + dir = proc_mkdir(dev_name, proc_ide); + if(!dir) return; + + ent = proc_create("media", S_IRUGO, dir, &fake_ide_media_proc_fops); + if(!ent) return; + snprintf(name, sizeof(name), "ide0/%s", dev_name); + proc_symlink(dev_name, proc_ide_root, name); +} + +static int fake_ide_setup(char *str) +{ + fake_ide = 1; + return 1; +} + +__setup("fake_ide", fake_ide_setup); + +__uml_help(fake_ide_setup, +"fake_ide\n" +" Create ide0 entries that map onto ubd devices.\n\n" +); + +static int parse_unit(char **ptr) +{ + char *str = *ptr, *end; + int n = -1; + + if(isdigit(*str)) { + n = simple_strtoul(str, &end, 0); + if(end == str) + return -1; + *ptr = end; + } + else if (('a' <= *str) && (*str <= 'z')) { + n = *str - 'a'; + str++; + *ptr = str; + } + return n; +} + +/* If *index_out == -1 at exit, the passed option was a general one; + * otherwise, the str pointer is used (and owned) inside ubd_devs array, so it + * should not be freed on exit. + */ +static int ubd_setup_common(char *str, int *index_out, char **error_out) +{ + struct ubd *ubd_dev; + struct openflags flags = global_openflags; + char *backing_file; + int n, err = 0, i; + + if(index_out) *index_out = -1; + n = *str; + if(n == '='){ + char *end; + int major; + + str++; + if(!strcmp(str, "sync")){ + global_openflags = of_sync(global_openflags); + goto out1; + } + + err = -EINVAL; + major = simple_strtoul(str, &end, 0); + if((*end != '\0') || (end == str)){ + *error_out = "Didn't parse major number"; + goto out1; + } + + mutex_lock(&ubd_lock); + if (fake_major != UBD_MAJOR) { + *error_out = "Can't assign a fake major twice"; + goto out1; + } + + fake_major = major; + + printk(KERN_INFO "Setting extra ubd major number to %d\n", + major); + err = 0; + out1: + mutex_unlock(&ubd_lock); + return err; + } + + n = parse_unit(&str); + if(n < 0){ + *error_out = "Couldn't parse device number"; + return -EINVAL; + } + if(n >= MAX_DEV){ + *error_out = "Device number out of range"; + return 1; + } + + err = -EBUSY; + mutex_lock(&ubd_lock); + + ubd_dev = &ubd_devs[n]; + if(ubd_dev->file != NULL){ + *error_out = "Device is already configured"; + goto out; + } + + if (index_out) + *index_out = n; + + err = -EINVAL; + for (i = 0; i < sizeof("rscd="); i++) { + switch (*str) { + case 'r': + flags.w = 0; + break; + case 's': + flags.s = 1; + break; + case 'd': + ubd_dev->no_cow = 1; + break; + case 'c': + ubd_dev->shared = 1; + break; + case '=': + str++; + goto break_loop; + default: + *error_out = "Expected '=' or flag letter " + "(r, s, c, or d)"; + goto out; + } + str++; + } + + if (*str == '=') + *error_out = "Too many flags specified"; + else + *error_out = "Missing '='"; + goto out; + +break_loop: + backing_file = strchr(str, ','); + + if (backing_file == NULL) + backing_file = strchr(str, ':'); + + if(backing_file != NULL){ + if(ubd_dev->no_cow){ + *error_out = "Can't specify both 'd' and a cow file"; + goto out; + } + else { + *backing_file = '\0'; + backing_file++; + } + } + err = 0; + ubd_dev->file = str; + ubd_dev->cow.file = backing_file; + ubd_dev->boot_openflags = flags; +out: + mutex_unlock(&ubd_lock); + return err; +} + +static int ubd_setup(char *str) +{ + char *error; + int err; + + err = ubd_setup_common(str, NULL, &error); + if(err) + printk(KERN_ERR "Failed to initialize device with \"%s\" : " + "%s\n", str, error); + return 1; +} + +__setup("ubd", ubd_setup); +__uml_help(ubd_setup, +"ubd<n><flags>=<filename>[(:|,)<filename2>]\n" +" This is used to associate a device with a file in the underlying\n" +" filesystem. When specifying two filenames, the first one is the\n" +" COW name and the second is the backing file name. As separator you can\n" +" use either a ':' or a ',': the first one allows writing things like;\n" +" ubd0=~/Uml/root_cow:~/Uml/root_backing_file\n" +" while with a ',' the shell would not expand the 2nd '~'.\n" +" When using only one filename, UML will detect whether to treat it like\n" +" a COW file or a backing file. To override this detection, add the 'd'\n" +" flag:\n" +" ubd0d=BackingFile\n" +" Usually, there is a filesystem in the file, but \n" +" that's not required. Swap devices containing swap files can be\n" +" specified like this. Also, a file which doesn't contain a\n" +" filesystem can have its contents read in the virtual \n" +" machine by running 'dd' on the device. <n> must be in the range\n" +" 0 to 7. Appending an 'r' to the number will cause that device\n" +" to be mounted read-only. For example ubd1r=./ext_fs. Appending\n" +" an 's' will cause data to be written to disk on the host immediately.\n" +" 'c' will cause the device to be treated as being shared between multiple\n" +" UMLs and file locking will be turned off - this is appropriate for a\n" +" cluster filesystem and inappropriate at almost all other times.\n\n" +); + +static int udb_setup(char *str) +{ + printk("udb%s specified on command line is almost certainly a ubd -> " + "udb TYPO\n", str); + return 1; +} + +__setup("udb", udb_setup); +__uml_help(udb_setup, +"udb\n" +" This option is here solely to catch ubd -> udb typos, which can be\n" +" to impossible to catch visually unless you specifically look for\n" +" them. The only result of any option starting with 'udb' is an error\n" +" in the boot output.\n\n" +); + +static void do_ubd_request(struct request_queue * q); + +/* Only changed by ubd_init, which is an initcall. */ +static int thread_fd = -1; +static LIST_HEAD(restart); + +/* XXX - move this inside ubd_intr. */ +/* Called without dev->lock held, and only in interrupt context. */ +static void ubd_handler(void) +{ + struct io_thread_req *req; + struct ubd *ubd; + struct list_head *list, *next_ele; + unsigned long flags; + int n; + + while(1){ + n = os_read_file(thread_fd, &req, + sizeof(struct io_thread_req *)); + if(n != sizeof(req)){ + if(n == -EAGAIN) + break; + printk(KERN_ERR "spurious interrupt in ubd_handler, " + "err = %d\n", -n); + return; + } + + blk_end_request(req->req, 0, req->length); + kfree(req); + } + reactivate_fd(thread_fd, UBD_IRQ); + + list_for_each_safe(list, next_ele, &restart){ + ubd = container_of(list, struct ubd, restart); + list_del_init(&ubd->restart); + spin_lock_irqsave(&ubd->lock, flags); + do_ubd_request(ubd->queue); + spin_unlock_irqrestore(&ubd->lock, flags); + } +} + +static irqreturn_t ubd_intr(int irq, void *dev) +{ + ubd_handler(); + return IRQ_HANDLED; +} + +/* Only changed by ubd_init, which is an initcall. */ +static int io_pid = -1; + +static void kill_io_thread(void) +{ + if(io_pid != -1) + os_kill_process(io_pid, 1); +} + +__uml_exitcall(kill_io_thread); + +static inline int ubd_file_size(struct ubd *ubd_dev, __u64 *size_out) +{ + char *file; + int fd; + int err; + + __u32 version; + __u32 align; + char *backing_file; + time_t mtime; + unsigned long long size; + int sector_size; + int bitmap_offset; + + if (ubd_dev->file && ubd_dev->cow.file) { + file = ubd_dev->cow.file; + + goto out; + } + + fd = os_open_file(ubd_dev->file, of_read(OPENFLAGS()), 0); + if (fd < 0) + return fd; + + err = read_cow_header(file_reader, &fd, &version, &backing_file, \ + &mtime, &size, §or_size, &align, &bitmap_offset); + os_close_file(fd); + + if(err == -EINVAL) + file = ubd_dev->file; + else + file = backing_file; + +out: + return os_file_size(file, size_out); +} + +static int read_cow_bitmap(int fd, void *buf, int offset, int len) +{ + int err; + + err = os_seek_file(fd, offset); + if (err < 0) + return err; + + err = os_read_file(fd, buf, len); + if (err < 0) + return err; + + return 0; +} + +static int backing_file_mismatch(char *file, __u64 size, time_t mtime) +{ + unsigned long modtime; + unsigned long long actual; + int err; + + err = os_file_modtime(file, &modtime); + if (err < 0) { + printk(KERN_ERR "Failed to get modification time of backing " + "file \"%s\", err = %d\n", file, -err); + return err; + } + + err = os_file_size(file, &actual); + if (err < 0) { + printk(KERN_ERR "Failed to get size of backing file \"%s\", " + "err = %d\n", file, -err); + return err; + } + + if (actual != size) { + /*__u64 can be a long on AMD64 and with %lu GCC complains; so + * the typecast.*/ + printk(KERN_ERR "Size mismatch (%llu vs %llu) of COW header " + "vs backing file\n", (unsigned long long) size, actual); + return -EINVAL; + } + if (modtime != mtime) { + printk(KERN_ERR "mtime mismatch (%ld vs %ld) of COW header vs " + "backing file\n", mtime, modtime); + return -EINVAL; + } + return 0; +} + +static int path_requires_switch(char *from_cmdline, char *from_cow, char *cow) +{ + struct uml_stat buf1, buf2; + int err; + + if (from_cmdline == NULL) + return 0; + if (!strcmp(from_cmdline, from_cow)) + return 0; + + err = os_stat_file(from_cmdline, &buf1); + if (err < 0) { + printk(KERN_ERR "Couldn't stat '%s', err = %d\n", from_cmdline, + -err); + return 0; + } + err = os_stat_file(from_cow, &buf2); + if (err < 0) { + printk(KERN_ERR "Couldn't stat '%s', err = %d\n", from_cow, + -err); + return 1; + } + if ((buf1.ust_dev == buf2.ust_dev) && (buf1.ust_ino == buf2.ust_ino)) + return 0; + + printk(KERN_ERR "Backing file mismatch - \"%s\" requested, " + "\"%s\" specified in COW header of \"%s\"\n", + from_cmdline, from_cow, cow); + return 1; +} + +static int open_ubd_file(char *file, struct openflags *openflags, int shared, + char **backing_file_out, int *bitmap_offset_out, + unsigned long *bitmap_len_out, int *data_offset_out, + int *create_cow_out) +{ + time_t mtime; + unsigned long long size; + __u32 version, align; + char *backing_file; + int fd, err, sectorsize, asked_switch, mode = 0644; + + fd = os_open_file(file, *openflags, mode); + if (fd < 0) { + if ((fd == -ENOENT) && (create_cow_out != NULL)) + *create_cow_out = 1; + if (!openflags->w || + ((fd != -EROFS) && (fd != -EACCES))) + return fd; + openflags->w = 0; + fd = os_open_file(file, *openflags, mode); + if (fd < 0) + return fd; + } + + if (shared) + printk(KERN_INFO "Not locking \"%s\" on the host\n", file); + else { + err = os_lock_file(fd, openflags->w); + if (err < 0) { + printk(KERN_ERR "Failed to lock '%s', err = %d\n", + file, -err); + goto out_close; + } + } + + /* Successful return case! */ + if (backing_file_out == NULL) + return fd; + + err = read_cow_header(file_reader, &fd, &version, &backing_file, &mtime, + &size, §orsize, &align, bitmap_offset_out); + if (err && (*backing_file_out != NULL)) { + printk(KERN_ERR "Failed to read COW header from COW file " + "\"%s\", errno = %d\n", file, -err); + goto out_close; + } + if (err) + return fd; + + asked_switch = path_requires_switch(*backing_file_out, backing_file, + file); + + /* Allow switching only if no mismatch. */ + if (asked_switch && !backing_file_mismatch(*backing_file_out, size, + mtime)) { + printk(KERN_ERR "Switching backing file to '%s'\n", + *backing_file_out); + err = write_cow_header(file, fd, *backing_file_out, + sectorsize, align, &size); + if (err) { + printk(KERN_ERR "Switch failed, errno = %d\n", -err); + goto out_close; + } + } else { + *backing_file_out = backing_file; + err = backing_file_mismatch(*backing_file_out, size, mtime); + if (err) + goto out_close; + } + + cow_sizes(version, size, sectorsize, align, *bitmap_offset_out, + bitmap_len_out, data_offset_out); + + return fd; + out_close: + os_close_file(fd); + return err; +} + +static int create_cow_file(char *cow_file, char *backing_file, + struct openflags flags, + int sectorsize, int alignment, int *bitmap_offset_out, + unsigned long *bitmap_len_out, int *data_offset_out) +{ + int err, fd; + + flags.c = 1; + fd = open_ubd_file(cow_file, &flags, 0, NULL, NULL, NULL, NULL, NULL); + if (fd < 0) { + err = fd; + printk(KERN_ERR "Open of COW file '%s' failed, errno = %d\n", + cow_file, -err); + goto out; + } + + err = init_cow_file(fd, cow_file, backing_file, sectorsize, alignment, + bitmap_offset_out, bitmap_len_out, + data_offset_out); + if (!err) + return fd; + os_close_file(fd); + out: + return err; +} + +static void ubd_close_dev(struct ubd *ubd_dev) +{ + os_close_file(ubd_dev->fd); + if(ubd_dev->cow.file == NULL) + return; + + os_close_file(ubd_dev->cow.fd); + vfree(ubd_dev->cow.bitmap); + ubd_dev->cow.bitmap = NULL; +} + +static int ubd_open_dev(struct ubd *ubd_dev) +{ + struct openflags flags; + char **back_ptr; + int err, create_cow, *create_ptr; + int fd; + + ubd_dev->openflags = ubd_dev->boot_openflags; + create_cow = 0; + create_ptr = (ubd_dev->cow.file != NULL) ? &create_cow : NULL; + back_ptr = ubd_dev->no_cow ? NULL : &ubd_dev->cow.file; + + fd = open_ubd_file(ubd_dev->file, &ubd_dev->openflags, ubd_dev->shared, + back_ptr, &ubd_dev->cow.bitmap_offset, + &ubd_dev->cow.bitmap_len, &ubd_dev->cow.data_offset, + create_ptr); + + if((fd == -ENOENT) && create_cow){ + fd = create_cow_file(ubd_dev->file, ubd_dev->cow.file, + ubd_dev->openflags, 1 << 9, PAGE_SIZE, + &ubd_dev->cow.bitmap_offset, + &ubd_dev->cow.bitmap_len, + &ubd_dev->cow.data_offset); + if(fd >= 0){ + printk(KERN_INFO "Creating \"%s\" as COW file for " + "\"%s\"\n", ubd_dev->file, ubd_dev->cow.file); + } + } + + if(fd < 0){ + printk("Failed to open '%s', errno = %d\n", ubd_dev->file, + -fd); + return fd; + } + ubd_dev->fd = fd; + + if(ubd_dev->cow.file != NULL){ + blk_queue_max_hw_sectors(ubd_dev->queue, 8 * sizeof(long)); + + err = -ENOMEM; + ubd_dev->cow.bitmap = vmalloc(ubd_dev->cow.bitmap_len); + if(ubd_dev->cow.bitmap == NULL){ + printk(KERN_ERR "Failed to vmalloc COW bitmap\n"); + goto error; + } + flush_tlb_kernel_vm(); + + err = read_cow_bitmap(ubd_dev->fd, ubd_dev->cow.bitmap, + ubd_dev->cow.bitmap_offset, + ubd_dev->cow.bitmap_len); + if(err < 0) + goto error; + + flags = ubd_dev->openflags; + flags.w = 0; + err = open_ubd_file(ubd_dev->cow.file, &flags, ubd_dev->shared, NULL, + NULL, NULL, NULL, NULL); + if(err < 0) goto error; + ubd_dev->cow.fd = err; + } + return 0; + error: + os_close_file(ubd_dev->fd); + return err; +} + +static void ubd_device_release(struct device *dev) +{ + struct ubd *ubd_dev = dev_get_drvdata(dev); + + blk_cleanup_queue(ubd_dev->queue); + *ubd_dev = ((struct ubd) DEFAULT_UBD); +} + +static int ubd_disk_register(int major, u64 size, int unit, + struct gendisk **disk_out) +{ + struct gendisk *disk; + + disk = alloc_disk(1 << UBD_SHIFT); + if(disk == NULL) + return -ENOMEM; + + disk->major = major; + disk->first_minor = unit << UBD_SHIFT; + disk->fops = &ubd_blops; + set_capacity(disk, size / 512); + if (major == UBD_MAJOR) + sprintf(disk->disk_name, "ubd%c", 'a' + unit); + else + sprintf(disk->disk_name, "ubd_fake%d", unit); + + /* sysfs register (not for ide fake devices) */ + if (major == UBD_MAJOR) { + ubd_devs[unit].pdev.id = unit; + ubd_devs[unit].pdev.name = DRIVER_NAME; + ubd_devs[unit].pdev.dev.release = ubd_device_release; + dev_set_drvdata(&ubd_devs[unit].pdev.dev, &ubd_devs[unit]); + platform_device_register(&ubd_devs[unit].pdev); + disk->driverfs_dev = &ubd_devs[unit].pdev.dev; + } + + disk->private_data = &ubd_devs[unit]; + disk->queue = ubd_devs[unit].queue; + add_disk(disk); + + *disk_out = disk; + return 0; +} + +#define ROUND_BLOCK(n) ((n + ((1 << 9) - 1)) & (-1 << 9)) + +static int ubd_add(int n, char **error_out) +{ + struct ubd *ubd_dev = &ubd_devs[n]; + int err = 0; + + if(ubd_dev->file == NULL) + goto out; + + err = ubd_file_size(ubd_dev, &ubd_dev->size); + if(err < 0){ + *error_out = "Couldn't determine size of device's file"; + goto out; + } + + ubd_dev->size = ROUND_BLOCK(ubd_dev->size); + + INIT_LIST_HEAD(&ubd_dev->restart); + sg_init_table(ubd_dev->sg, MAX_SG); + + err = -ENOMEM; + ubd_dev->queue = blk_init_queue(do_ubd_request, &ubd_dev->lock); + if (ubd_dev->queue == NULL) { + *error_out = "Failed to initialize device queue"; + goto out; + } + ubd_dev->queue->queuedata = ubd_dev; + blk_queue_flush(ubd_dev->queue, REQ_FLUSH); + + blk_queue_max_segments(ubd_dev->queue, MAX_SG); + err = ubd_disk_register(UBD_MAJOR, ubd_dev->size, n, &ubd_gendisk[n]); + if(err){ + *error_out = "Failed to register device"; + goto out_cleanup; + } + + if (fake_major != UBD_MAJOR) + ubd_disk_register(fake_major, ubd_dev->size, n, + &fake_gendisk[n]); + + /* + * Perhaps this should also be under the "if (fake_major)" above + * using the fake_disk->disk_name + */ + if (fake_ide) + make_ide_entries(ubd_gendisk[n]->disk_name); + + err = 0; +out: + return err; + +out_cleanup: + blk_cleanup_queue(ubd_dev->queue); + goto out; +} + +static int ubd_config(char *str, char **error_out) +{ + int n, ret; + + /* This string is possibly broken up and stored, so it's only + * freed if ubd_setup_common fails, or if only general options + * were set. + */ + str = kstrdup(str, GFP_KERNEL); + if (str == NULL) { + *error_out = "Failed to allocate memory"; + return -ENOMEM; + } + + ret = ubd_setup_common(str, &n, error_out); + if (ret) + goto err_free; + + if (n == -1) { + ret = 0; + goto err_free; + } + + mutex_lock(&ubd_lock); + ret = ubd_add(n, error_out); + if (ret) + ubd_devs[n].file = NULL; + mutex_unlock(&ubd_lock); + +out: + return ret; + +err_free: + kfree(str); + goto out; +} + +static int ubd_get_config(char *name, char *str, int size, char **error_out) +{ + struct ubd *ubd_dev; + int n, len = 0; + + n = parse_unit(&name); + if((n >= MAX_DEV) || (n < 0)){ + *error_out = "ubd_get_config : device number out of range"; + return -1; + } + + ubd_dev = &ubd_devs[n]; + mutex_lock(&ubd_lock); + + if(ubd_dev->file == NULL){ + CONFIG_CHUNK(str, size, len, "", 1); + goto out; + } + + CONFIG_CHUNK(str, size, len, ubd_dev->file, 0); + + if(ubd_dev->cow.file != NULL){ + CONFIG_CHUNK(str, size, len, ",", 0); + CONFIG_CHUNK(str, size, len, ubd_dev->cow.file, 1); + } + else CONFIG_CHUNK(str, size, len, "", 1); + + out: + mutex_unlock(&ubd_lock); + return len; +} + +static int ubd_id(char **str, int *start_out, int *end_out) +{ + int n; + + n = parse_unit(str); + *start_out = 0; + *end_out = MAX_DEV - 1; + return n; +} + +static int ubd_remove(int n, char **error_out) +{ + struct gendisk *disk = ubd_gendisk[n]; + struct ubd *ubd_dev; + int err = -ENODEV; + + mutex_lock(&ubd_lock); + + ubd_dev = &ubd_devs[n]; + + if(ubd_dev->file == NULL) + goto out; + + /* you cannot remove a open disk */ + err = -EBUSY; + if(ubd_dev->count > 0) + goto out; + + ubd_gendisk[n] = NULL; + if(disk != NULL){ + del_gendisk(disk); + put_disk(disk); + } + + if(fake_gendisk[n] != NULL){ + del_gendisk(fake_gendisk[n]); + put_disk(fake_gendisk[n]); + fake_gendisk[n] = NULL; + } + + err = 0; + platform_device_unregister(&ubd_dev->pdev); +out: + mutex_unlock(&ubd_lock); + return err; +} + +/* All these are called by mconsole in process context and without + * ubd-specific locks. The structure itself is const except for .list. + */ +static struct mc_device ubd_mc = { + .list = LIST_HEAD_INIT(ubd_mc.list), + .name = "ubd", + .config = ubd_config, + .get_config = ubd_get_config, + .id = ubd_id, + .remove = ubd_remove, +}; + +static int __init ubd_mc_init(void) +{ + mconsole_register_dev(&ubd_mc); + return 0; +} + +__initcall(ubd_mc_init); + +static int __init ubd0_init(void) +{ + struct ubd *ubd_dev = &ubd_devs[0]; + + mutex_lock(&ubd_lock); + if(ubd_dev->file == NULL) + ubd_dev->file = "root_fs"; + mutex_unlock(&ubd_lock); + + return 0; +} + +__initcall(ubd0_init); + +/* Used in ubd_init, which is an initcall */ +static struct platform_driver ubd_driver = { + .driver = { + .name = DRIVER_NAME, + }, +}; + +static int __init ubd_init(void) +{ + char *error; + int i, err; + + if (register_blkdev(UBD_MAJOR, "ubd")) + return -1; + + if (fake_major != UBD_MAJOR) { + char name[sizeof("ubd_nnn\0")]; + + snprintf(name, sizeof(name), "ubd_%d", fake_major); + if (register_blkdev(fake_major, "ubd")) + return -1; + } + platform_driver_register(&ubd_driver); + mutex_lock(&ubd_lock); + for (i = 0; i < MAX_DEV; i++){ + err = ubd_add(i, &error); + if(err) + printk(KERN_ERR "Failed to initialize ubd device %d :" + "%s\n", i, error); + } + mutex_unlock(&ubd_lock); + return 0; +} + +late_initcall(ubd_init); + +static int __init ubd_driver_init(void){ + unsigned long stack; + int err; + + /* Set by CONFIG_BLK_DEV_UBD_SYNC or ubd=sync.*/ + if(global_openflags.s){ + printk(KERN_INFO "ubd: Synchronous mode\n"); + /* Letting ubd=sync be like using ubd#s= instead of ubd#= is + * enough. So use anyway the io thread. */ + } + stack = alloc_stack(0, 0); + io_pid = start_io_thread(stack + PAGE_SIZE - sizeof(void *), + &thread_fd); + if(io_pid < 0){ + printk(KERN_ERR + "ubd : Failed to start I/O thread (errno = %d) - " + "falling back to synchronous I/O\n", -io_pid); + io_pid = -1; + return 0; + } + err = um_request_irq(UBD_IRQ, thread_fd, IRQ_READ, ubd_intr, + 0, "ubd", ubd_devs); + if(err != 0) + printk(KERN_ERR "um_request_irq failed - errno = %d\n", -err); + return 0; +} + +device_initcall(ubd_driver_init); + +static int ubd_open(struct block_device *bdev, fmode_t mode) +{ + struct gendisk *disk = bdev->bd_disk; + struct ubd *ubd_dev = disk->private_data; + int err = 0; + + mutex_lock(&ubd_mutex); + if(ubd_dev->count == 0){ + err = ubd_open_dev(ubd_dev); + if(err){ + printk(KERN_ERR "%s: Can't open \"%s\": errno = %d\n", + disk->disk_name, ubd_dev->file, -err); + goto out; + } + } + ubd_dev->count++; + set_disk_ro(disk, !ubd_dev->openflags.w); + + /* This should no more be needed. And it didn't work anyway to exclude + * read-write remounting of filesystems.*/ + /*if((mode & FMODE_WRITE) && !ubd_dev->openflags.w){ + if(--ubd_dev->count == 0) ubd_close_dev(ubd_dev); + err = -EROFS; + }*/ +out: + mutex_unlock(&ubd_mutex); + return err; +} + +static void ubd_release(struct gendisk *disk, fmode_t mode) +{ + struct ubd *ubd_dev = disk->private_data; + + mutex_lock(&ubd_mutex); + if(--ubd_dev->count == 0) + ubd_close_dev(ubd_dev); + mutex_unlock(&ubd_mutex); +} + +static void cowify_bitmap(__u64 io_offset, int length, unsigned long *cow_mask, + __u64 *cow_offset, unsigned long *bitmap, + __u64 bitmap_offset, unsigned long *bitmap_words, + __u64 bitmap_len) +{ + __u64 sector = io_offset >> 9; + int i, update_bitmap = 0; + + for(i = 0; i < length >> 9; i++){ + if(cow_mask != NULL) + ubd_set_bit(i, (unsigned char *) cow_mask); + if(ubd_test_bit(sector + i, (unsigned char *) bitmap)) + continue; + + update_bitmap = 1; + ubd_set_bit(sector + i, (unsigned char *) bitmap); + } + + if(!update_bitmap) + return; + + *cow_offset = sector / (sizeof(unsigned long) * 8); + + /* This takes care of the case where we're exactly at the end of the + * device, and *cow_offset + 1 is off the end. So, just back it up + * by one word. Thanks to Lynn Kerby for the fix and James McMechan + * for the original diagnosis. + */ + if (*cow_offset == (DIV_ROUND_UP(bitmap_len, + sizeof(unsigned long)) - 1)) + (*cow_offset)--; + + bitmap_words[0] = bitmap[*cow_offset]; + bitmap_words[1] = bitmap[*cow_offset + 1]; + + *cow_offset *= sizeof(unsigned long); + *cow_offset += bitmap_offset; +} + +static void cowify_req(struct io_thread_req *req, unsigned long *bitmap, + __u64 bitmap_offset, __u64 bitmap_len) +{ + __u64 sector = req->offset >> 9; + int i; + + if(req->length > (sizeof(req->sector_mask) * 8) << 9) + panic("Operation too long"); + + if(req->op == UBD_READ) { + for(i = 0; i < req->length >> 9; i++){ + if(ubd_test_bit(sector + i, (unsigned char *) bitmap)) + ubd_set_bit(i, (unsigned char *) + &req->sector_mask); + } + } + else cowify_bitmap(req->offset, req->length, &req->sector_mask, + &req->cow_offset, bitmap, bitmap_offset, + req->bitmap_words, bitmap_len); +} + +/* Called with dev->lock held */ +static void prepare_request(struct request *req, struct io_thread_req *io_req, + unsigned long long offset, int page_offset, + int len, struct page *page) +{ + struct gendisk *disk = req->rq_disk; + struct ubd *ubd_dev = disk->private_data; + + io_req->req = req; + io_req->fds[0] = (ubd_dev->cow.file != NULL) ? ubd_dev->cow.fd : + ubd_dev->fd; + io_req->fds[1] = ubd_dev->fd; + io_req->cow_offset = -1; + io_req->offset = offset; + io_req->length = len; + io_req->error = 0; + io_req->sector_mask = 0; + + io_req->op = (rq_data_dir(req) == READ) ? UBD_READ : UBD_WRITE; + io_req->offsets[0] = 0; + io_req->offsets[1] = ubd_dev->cow.data_offset; + io_req->buffer = page_address(page) + page_offset; + io_req->sectorsize = 1 << 9; + + if(ubd_dev->cow.file != NULL) + cowify_req(io_req, ubd_dev->cow.bitmap, + ubd_dev->cow.bitmap_offset, ubd_dev->cow.bitmap_len); + +} + +/* Called with dev->lock held */ +static void prepare_flush_request(struct request *req, + struct io_thread_req *io_req) +{ + struct gendisk *disk = req->rq_disk; + struct ubd *ubd_dev = disk->private_data; + + io_req->req = req; + io_req->fds[0] = (ubd_dev->cow.file != NULL) ? ubd_dev->cow.fd : + ubd_dev->fd; + io_req->op = UBD_FLUSH; +} + +static bool submit_request(struct io_thread_req *io_req, struct ubd *dev) +{ + int n = os_write_file(thread_fd, &io_req, + sizeof(io_req)); + if (n != sizeof(io_req)) { + if (n != -EAGAIN) + printk("write to io thread failed, " + "errno = %d\n", -n); + else if (list_empty(&dev->restart)) + list_add(&dev->restart, &restart); + + kfree(io_req); + return false; + } + return true; +} + +/* Called with dev->lock held */ +static void do_ubd_request(struct request_queue *q) +{ + struct io_thread_req *io_req; + struct request *req; + + while(1){ + struct ubd *dev = q->queuedata; + if(dev->request == NULL){ + struct request *req = blk_fetch_request(q); + if(req == NULL) + return; + + dev->request = req; + dev->rq_pos = blk_rq_pos(req); + dev->start_sg = 0; + dev->end_sg = blk_rq_map_sg(q, req, dev->sg); + } + + req = dev->request; + + if (req->cmd_flags & REQ_FLUSH) { + io_req = kmalloc(sizeof(struct io_thread_req), + GFP_ATOMIC); + if (io_req == NULL) { + if (list_empty(&dev->restart)) + list_add(&dev->restart, &restart); + return; + } + prepare_flush_request(req, io_req); + if (submit_request(io_req, dev) == false) + return; + } + + while(dev->start_sg < dev->end_sg){ + struct scatterlist *sg = &dev->sg[dev->start_sg]; + + io_req = kmalloc(sizeof(struct io_thread_req), + GFP_ATOMIC); + if(io_req == NULL){ + if(list_empty(&dev->restart)) + list_add(&dev->restart, &restart); + return; + } + prepare_request(req, io_req, + (unsigned long long)dev->rq_pos << 9, + sg->offset, sg->length, sg_page(sg)); + + if (submit_request(io_req, dev) == false) + return; + + dev->rq_pos += sg->length >> 9; + dev->start_sg++; + } + dev->end_sg = 0; + dev->request = NULL; + } +} + +static int ubd_getgeo(struct block_device *bdev, struct hd_geometry *geo) +{ + struct ubd *ubd_dev = bdev->bd_disk->private_data; + + geo->heads = 128; + geo->sectors = 32; + geo->cylinders = ubd_dev->size / (128 * 32 * 512); + return 0; +} + +static int ubd_ioctl(struct block_device *bdev, fmode_t mode, + unsigned int cmd, unsigned long arg) +{ + struct ubd *ubd_dev = bdev->bd_disk->private_data; + u16 ubd_id[ATA_ID_WORDS]; + + switch (cmd) { + struct cdrom_volctrl volume; + case HDIO_GET_IDENTITY: + memset(&ubd_id, 0, ATA_ID_WORDS * 2); + ubd_id[ATA_ID_CYLS] = ubd_dev->size / (128 * 32 * 512); + ubd_id[ATA_ID_HEADS] = 128; + ubd_id[ATA_ID_SECTORS] = 32; + if(copy_to_user((char __user *) arg, (char *) &ubd_id, + sizeof(ubd_id))) + return -EFAULT; + return 0; + + case CDROMVOLREAD: + if(copy_from_user(&volume, (char __user *) arg, sizeof(volume))) + return -EFAULT; + volume.channel0 = 255; + volume.channel1 = 255; + volume.channel2 = 255; + volume.channel3 = 255; + if(copy_to_user((char __user *) arg, &volume, sizeof(volume))) + return -EFAULT; + return 0; + } + return -EINVAL; +} + +static int update_bitmap(struct io_thread_req *req) +{ + int n; + + if(req->cow_offset == -1) + return 0; + + n = os_seek_file(req->fds[1], req->cow_offset); + if(n < 0){ + printk("do_io - bitmap lseek failed : err = %d\n", -n); + return 1; + } + + n = os_write_file(req->fds[1], &req->bitmap_words, + sizeof(req->bitmap_words)); + if(n != sizeof(req->bitmap_words)){ + printk("do_io - bitmap update failed, err = %d fd = %d\n", -n, + req->fds[1]); + return 1; + } + + return 0; +} + +static void do_io(struct io_thread_req *req) +{ + char *buf; + unsigned long len; + int n, nsectors, start, end, bit; + int err; + __u64 off; + + if (req->op == UBD_FLUSH) { + /* fds[0] is always either the rw image or our cow file */ + n = os_sync_file(req->fds[0]); + if (n != 0) { + printk("do_io - sync failed err = %d " + "fd = %d\n", -n, req->fds[0]); + req->error = 1; + } + return; + } + + nsectors = req->length / req->sectorsize; + start = 0; + do { + bit = ubd_test_bit(start, (unsigned char *) &req->sector_mask); + end = start; + while((end < nsectors) && + (ubd_test_bit(end, (unsigned char *) + &req->sector_mask) == bit)) + end++; + + off = req->offset + req->offsets[bit] + + start * req->sectorsize; + len = (end - start) * req->sectorsize; + buf = &req->buffer[start * req->sectorsize]; + + err = os_seek_file(req->fds[bit], off); + if(err < 0){ + printk("do_io - lseek failed : err = %d\n", -err); + req->error = 1; + return; + } + if(req->op == UBD_READ){ + n = 0; + do { + buf = &buf[n]; + len -= n; + n = os_read_file(req->fds[bit], buf, len); + if (n < 0) { + printk("do_io - read failed, err = %d " + "fd = %d\n", -n, req->fds[bit]); + req->error = 1; + return; + } + } while((n < len) && (n != 0)); + if (n < len) memset(&buf[n], 0, len - n); + } else { + n = os_write_file(req->fds[bit], buf, len); + if(n != len){ + printk("do_io - write failed err = %d " + "fd = %d\n", -n, req->fds[bit]); + req->error = 1; + return; + } + } + + start = end; + } while(start < nsectors); + + req->error = update_bitmap(req); +} + +/* Changed in start_io_thread, which is serialized by being called only + * from ubd_init, which is an initcall. + */ +int kernel_fd = -1; + +/* Only changed by the io thread. XXX: currently unused. */ +static int io_count = 0; + +int io_thread(void *arg) +{ + struct io_thread_req *req; + int n; + + os_fix_helper_signals(); + + while(1){ + n = os_read_file(kernel_fd, &req, + sizeof(struct io_thread_req *)); + if(n != sizeof(struct io_thread_req *)){ + if(n < 0) + printk("io_thread - read failed, fd = %d, " + "err = %d\n", kernel_fd, -n); + else { + printk("io_thread - short read, fd = %d, " + "length = %d\n", kernel_fd, n); + } + continue; + } + io_count++; + do_io(req); + n = os_write_file(kernel_fd, &req, + sizeof(struct io_thread_req *)); + if(n != sizeof(struct io_thread_req *)) + printk("io_thread - write failed, fd = %d, err = %d\n", + kernel_fd, -n); + } + + return 0; +} diff --git a/arch/um/drivers/ubd_user.c b/arch/um/drivers/ubd_user.c new file mode 100644 index 000000000..e376f9b9c --- /dev/null +++ b/arch/um/drivers/ubd_user.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2000, 2001, 2002 Jeff Dike (jdike@karaya.com) + * Copyright (C) 2001 Ridgerun,Inc (glonnon@ridgerun.com) + * Licensed under the GPL + */ + +#include <stddef.h> +#include <unistd.h> +#include <errno.h> +#include <sched.h> +#include <signal.h> +#include <string.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <sys/mman.h> +#include <sys/param.h> +#include <endian.h> +#include <byteswap.h> + +#include "ubd.h" +#include <os.h> + +int start_io_thread(unsigned long sp, int *fd_out) +{ + int pid, fds[2], err; + + err = os_pipe(fds, 1, 1); + if(err < 0){ + printk("start_io_thread - os_pipe failed, err = %d\n", -err); + goto out; + } + + kernel_fd = fds[0]; + *fd_out = fds[1]; + + err = os_set_fd_block(*fd_out, 0); + if (err) { + printk("start_io_thread - failed to set nonblocking I/O.\n"); + goto out_close; + } + + pid = clone(io_thread, (void *) sp, CLONE_FILES | CLONE_VM, NULL); + if(pid < 0){ + err = -errno; + printk("start_io_thread - clone failed : errno = %d\n", errno); + goto out_close; + } + + return(pid); + + out_close: + os_close_file(fds[0]); + os_close_file(fds[1]); + kernel_fd = -1; + *fd_out = -1; + out: + return err; +} diff --git a/arch/um/drivers/umcast.h b/arch/um/drivers/umcast.h new file mode 100644 index 000000000..c190c6440 --- /dev/null +++ b/arch/um/drivers/umcast.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL + */ + +#ifndef __DRIVERS_UMCAST_H +#define __DRIVERS_UMCAST_H + +#include <net_user.h> + +struct umcast_data { + char *addr; + unsigned short lport; + unsigned short rport; + void *listen_addr; + void *remote_addr; + int ttl; + int unicast; + void *dev; +}; + +extern const struct net_user_info umcast_user_info; + +extern int umcast_user_write(int fd, void *buf, int len, + struct umcast_data *pri); + +#endif diff --git a/arch/um/drivers/umcast_kern.c b/arch/um/drivers/umcast_kern.c new file mode 100644 index 000000000..f5ba6e377 --- /dev/null +++ b/arch/um/drivers/umcast_kern.c @@ -0,0 +1,188 @@ +/* + * user-mode-linux networking multicast transport + * Copyright (C) 2001 by Harald Welte <laforge@gnumonks.org> + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * + * based on the existing uml-networking code, which is + * Copyright (C) 2001 Lennert Buytenhek (buytenh@gnu.org) and + * James Leu (jleu@mindspring.net). + * Copyright (C) 2001 by various other people who didn't put their name here. + * + * Licensed under the GPL. + */ + +#include <linux/init.h> +#include <linux/netdevice.h> +#include "umcast.h" +#include <net_kern.h> + +struct umcast_init { + char *addr; + int lport; + int rport; + int ttl; + bool unicast; +}; + +static void umcast_init(struct net_device *dev, void *data) +{ + struct uml_net_private *pri; + struct umcast_data *dpri; + struct umcast_init *init = data; + + pri = netdev_priv(dev); + dpri = (struct umcast_data *) pri->user; + dpri->addr = init->addr; + dpri->lport = init->lport; + dpri->rport = init->rport; + dpri->unicast = init->unicast; + dpri->ttl = init->ttl; + dpri->dev = dev; + + if (dpri->unicast) { + printk(KERN_INFO "ucast backend address: %s:%u listen port: " + "%u\n", dpri->addr, dpri->rport, dpri->lport); + } else { + printk(KERN_INFO "mcast backend multicast address: %s:%u, " + "TTL:%u\n", dpri->addr, dpri->lport, dpri->ttl); + } +} + +static int umcast_read(int fd, struct sk_buff *skb, struct uml_net_private *lp) +{ + return net_recvfrom(fd, skb_mac_header(skb), + skb->dev->mtu + ETH_HEADER_OTHER); +} + +static int umcast_write(int fd, struct sk_buff *skb, struct uml_net_private *lp) +{ + return umcast_user_write(fd, skb->data, skb->len, + (struct umcast_data *) &lp->user); +} + +static const struct net_kern_info umcast_kern_info = { + .init = umcast_init, + .protocol = eth_protocol, + .read = umcast_read, + .write = umcast_write, +}; + +static int mcast_setup(char *str, char **mac_out, void *data) +{ + struct umcast_init *init = data; + char *port_str = NULL, *ttl_str = NULL, *remain; + char *last; + + *init = ((struct umcast_init) + { .addr = "239.192.168.1", + .lport = 1102, + .ttl = 1 }); + + remain = split_if_spec(str, mac_out, &init->addr, &port_str, &ttl_str, + NULL); + if (remain != NULL) { + printk(KERN_ERR "mcast_setup - Extra garbage on " + "specification : '%s'\n", remain); + return 0; + } + + if (port_str != NULL) { + init->lport = simple_strtoul(port_str, &last, 10); + if ((*last != '\0') || (last == port_str)) { + printk(KERN_ERR "mcast_setup - Bad port : '%s'\n", + port_str); + return 0; + } + } + + if (ttl_str != NULL) { + init->ttl = simple_strtoul(ttl_str, &last, 10); + if ((*last != '\0') || (last == ttl_str)) { + printk(KERN_ERR "mcast_setup - Bad ttl : '%s'\n", + ttl_str); + return 0; + } + } + + init->unicast = false; + init->rport = init->lport; + + printk(KERN_INFO "Configured mcast device: %s:%u-%u\n", init->addr, + init->lport, init->ttl); + + return 1; +} + +static int ucast_setup(char *str, char **mac_out, void *data) +{ + struct umcast_init *init = data; + char *lport_str = NULL, *rport_str = NULL, *remain; + char *last; + + *init = ((struct umcast_init) + { .addr = "", + .lport = 1102, + .rport = 1102 }); + + remain = split_if_spec(str, mac_out, &init->addr, + &lport_str, &rport_str, NULL); + if (remain != NULL) { + printk(KERN_ERR "ucast_setup - Extra garbage on " + "specification : '%s'\n", remain); + return 0; + } + + if (lport_str != NULL) { + init->lport = simple_strtoul(lport_str, &last, 10); + if ((*last != '\0') || (last == lport_str)) { + printk(KERN_ERR "ucast_setup - Bad listen port : " + "'%s'\n", lport_str); + return 0; + } + } + + if (rport_str != NULL) { + init->rport = simple_strtoul(rport_str, &last, 10); + if ((*last != '\0') || (last == rport_str)) { + printk(KERN_ERR "ucast_setup - Bad remote port : " + "'%s'\n", rport_str); + return 0; + } + } + + init->unicast = true; + + printk(KERN_INFO "Configured ucast device: :%u -> %s:%u\n", + init->lport, init->addr, init->rport); + + return 1; +} + +static struct transport mcast_transport = { + .list = LIST_HEAD_INIT(mcast_transport.list), + .name = "mcast", + .setup = mcast_setup, + .user = &umcast_user_info, + .kern = &umcast_kern_info, + .private_size = sizeof(struct umcast_data), + .setup_size = sizeof(struct umcast_init), +}; + +static struct transport ucast_transport = { + .list = LIST_HEAD_INIT(ucast_transport.list), + .name = "ucast", + .setup = ucast_setup, + .user = &umcast_user_info, + .kern = &umcast_kern_info, + .private_size = sizeof(struct umcast_data), + .setup_size = sizeof(struct umcast_init), +}; + +static int register_umcast(void) +{ + register_transport(&mcast_transport); + register_transport(&ucast_transport); + return 0; +} + +late_initcall(register_umcast); diff --git a/arch/um/drivers/umcast_user.c b/arch/um/drivers/umcast_user.c new file mode 100644 index 000000000..6074184bb --- /dev/null +++ b/arch/um/drivers/umcast_user.c @@ -0,0 +1,184 @@ +/* + * user-mode-linux networking multicast transport + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Copyright (C) 2001 by Harald Welte <laforge@gnumonks.org> + * + * based on the existing uml-networking code, which is + * Copyright (C) 2001 Lennert Buytenhek (buytenh@gnu.org) and + * James Leu (jleu@mindspring.net). + * Copyright (C) 2001 by various other people who didn't put their name here. + * + * Licensed under the GPL. + * + */ + +#include <unistd.h> +#include <errno.h> +#include <netinet/in.h> +#include "umcast.h" +#include <net_user.h> +#include <um_malloc.h> + +static struct sockaddr_in *new_addr(char *addr, unsigned short port) +{ + struct sockaddr_in *sin; + + sin = uml_kmalloc(sizeof(struct sockaddr_in), UM_GFP_KERNEL); + if (sin == NULL) { + printk(UM_KERN_ERR "new_addr: allocation of sockaddr_in " + "failed\n"); + return NULL; + } + sin->sin_family = AF_INET; + if (addr) + sin->sin_addr.s_addr = in_aton(addr); + else + sin->sin_addr.s_addr = INADDR_ANY; + sin->sin_port = htons(port); + return sin; +} + +static int umcast_user_init(void *data, void *dev) +{ + struct umcast_data *pri = data; + + pri->remote_addr = new_addr(pri->addr, pri->rport); + if (pri->unicast) + pri->listen_addr = new_addr(NULL, pri->lport); + else + pri->listen_addr = pri->remote_addr; + pri->dev = dev; + return 0; +} + +static void umcast_remove(void *data) +{ + struct umcast_data *pri = data; + + kfree(pri->listen_addr); + if (pri->unicast) + kfree(pri->remote_addr); + pri->listen_addr = pri->remote_addr = NULL; +} + +static int umcast_open(void *data) +{ + struct umcast_data *pri = data; + struct sockaddr_in *lsin = pri->listen_addr; + struct sockaddr_in *rsin = pri->remote_addr; + struct ip_mreq mreq; + int fd, yes = 1, err = -EINVAL; + + + if ((!pri->unicast && lsin->sin_addr.s_addr == 0) || + (rsin->sin_addr.s_addr == 0) || + (lsin->sin_port == 0) || (rsin->sin_port == 0)) + goto out; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + + if (fd < 0) { + err = -errno; + printk(UM_KERN_ERR "umcast_open : data socket failed, " + "errno = %d\n", errno); + goto out; + } + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) { + err = -errno; + printk(UM_KERN_ERR "umcast_open: SO_REUSEADDR failed, " + "errno = %d\n", errno); + goto out_close; + } + + if (!pri->unicast) { + /* set ttl according to config */ + if (setsockopt(fd, SOL_IP, IP_MULTICAST_TTL, &pri->ttl, + sizeof(pri->ttl)) < 0) { + err = -errno; + printk(UM_KERN_ERR "umcast_open: IP_MULTICAST_TTL " + "failed, error = %d\n", errno); + goto out_close; + } + + /* set LOOP, so data does get fed back to local sockets */ + if (setsockopt(fd, SOL_IP, IP_MULTICAST_LOOP, + &yes, sizeof(yes)) < 0) { + err = -errno; + printk(UM_KERN_ERR "umcast_open: IP_MULTICAST_LOOP " + "failed, error = %d\n", errno); + goto out_close; + } + } + + /* bind socket to the address */ + if (bind(fd, (struct sockaddr *) lsin, sizeof(*lsin)) < 0) { + err = -errno; + printk(UM_KERN_ERR "umcast_open : data bind failed, " + "errno = %d\n", errno); + goto out_close; + } + + if (!pri->unicast) { + /* subscribe to the multicast group */ + mreq.imr_multiaddr.s_addr = lsin->sin_addr.s_addr; + mreq.imr_interface.s_addr = 0; + if (setsockopt(fd, SOL_IP, IP_ADD_MEMBERSHIP, + &mreq, sizeof(mreq)) < 0) { + err = -errno; + printk(UM_KERN_ERR "umcast_open: IP_ADD_MEMBERSHIP " + "failed, error = %d\n", errno); + printk(UM_KERN_ERR "There appears not to be a " + "multicast-capable network interface on the " + "host.\n"); + printk(UM_KERN_ERR "eth0 should be configured in order " + "to use the multicast transport.\n"); + goto out_close; + } + } + + return fd; + + out_close: + close(fd); + out: + return err; +} + +static void umcast_close(int fd, void *data) +{ + struct umcast_data *pri = data; + + if (!pri->unicast) { + struct ip_mreq mreq; + struct sockaddr_in *lsin = pri->listen_addr; + + mreq.imr_multiaddr.s_addr = lsin->sin_addr.s_addr; + mreq.imr_interface.s_addr = 0; + if (setsockopt(fd, SOL_IP, IP_DROP_MEMBERSHIP, + &mreq, sizeof(mreq)) < 0) { + printk(UM_KERN_ERR "umcast_close: IP_DROP_MEMBERSHIP " + "failed, error = %d\n", errno); + } + } + + close(fd); +} + +int umcast_user_write(int fd, void *buf, int len, struct umcast_data *pri) +{ + struct sockaddr_in *data_addr = pri->remote_addr; + + return net_sendto(fd, buf, len, data_addr, sizeof(*data_addr)); +} + +const struct net_user_info umcast_user_info = { + .init = umcast_user_init, + .open = umcast_open, + .close = umcast_close, + .remove = umcast_remove, + .add_address = NULL, + .delete_address = NULL, + .mtu = ETH_MAX_PACKET, + .max_packet = ETH_MAX_PACKET + ETH_HEADER_OTHER, +}; diff --git a/arch/um/drivers/vde.h b/arch/um/drivers/vde.h new file mode 100644 index 000000000..fc3a05902 --- /dev/null +++ b/arch/um/drivers/vde.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2007 Luca Bigliardi (shammash@artha.org). + * Licensed under the GPL. + */ + +#ifndef __UM_VDE_H__ +#define __UM_VDE_H__ + +struct vde_data { + char *vde_switch; + char *descr; + void *args; + void *conn; + void *dev; +}; + +struct vde_init { + char *vde_switch; + char *descr; + int port; + char *group; + int mode; +}; + +extern const struct net_user_info vde_user_info; + +extern void vde_init_libstuff(struct vde_data *vpri, struct vde_init *init); + +extern int vde_user_read(void *conn, void *buf, int len); +extern int vde_user_write(void *conn, void *buf, int len); + +#endif diff --git a/arch/um/drivers/vde_kern.c b/arch/um/drivers/vde_kern.c new file mode 100644 index 000000000..6a365fadc --- /dev/null +++ b/arch/um/drivers/vde_kern.c @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2007 Luca Bigliardi (shammash@artha.org). + * Licensed under the GPL. + * + * Transport usage: + * ethN=vde,<vde_switch>,<mac addr>,<port>,<group>,<mode>,<description> + * + */ + +#include <linux/init.h> +#include <linux/netdevice.h> +#include <net_kern.h> +#include <net_user.h> +#include "vde.h" + +static void vde_init(struct net_device *dev, void *data) +{ + struct vde_init *init = data; + struct uml_net_private *pri; + struct vde_data *vpri; + + pri = netdev_priv(dev); + vpri = (struct vde_data *) pri->user; + + vpri->vde_switch = init->vde_switch; + vpri->descr = init->descr ? init->descr : "UML vde_transport"; + vpri->args = NULL; + vpri->conn = NULL; + vpri->dev = dev; + + printk("vde backend - %s, ", vpri->vde_switch ? + vpri->vde_switch : "(default socket)"); + + vde_init_libstuff(vpri, init); + + printk("\n"); +} + +static int vde_read(int fd, struct sk_buff *skb, struct uml_net_private *lp) +{ + struct vde_data *pri = (struct vde_data *) &lp->user; + + if (pri->conn != NULL) + return vde_user_read(pri->conn, skb_mac_header(skb), + skb->dev->mtu + ETH_HEADER_OTHER); + + printk(KERN_ERR "vde_read - we have no VDECONN to read from"); + return -EBADF; +} + +static int vde_write(int fd, struct sk_buff *skb, struct uml_net_private *lp) +{ + struct vde_data *pri = (struct vde_data *) &lp->user; + + if (pri->conn != NULL) + return vde_user_write((void *)pri->conn, skb->data, + skb->len); + + printk(KERN_ERR "vde_write - we have no VDECONN to write to"); + return -EBADF; +} + +static const struct net_kern_info vde_kern_info = { + .init = vde_init, + .protocol = eth_protocol, + .read = vde_read, + .write = vde_write, +}; + +static int vde_setup(char *str, char **mac_out, void *data) +{ + struct vde_init *init = data; + char *remain, *port_str = NULL, *mode_str = NULL, *last; + + *init = ((struct vde_init) + { .vde_switch = NULL, + .descr = NULL, + .port = 0, + .group = NULL, + .mode = 0 }); + + remain = split_if_spec(str, &init->vde_switch, mac_out, &port_str, + &init->group, &mode_str, &init->descr, NULL); + + if (remain != NULL) + printk(KERN_WARNING "vde_setup - Ignoring extra data :" + "'%s'\n", remain); + + if (port_str != NULL) { + init->port = simple_strtoul(port_str, &last, 10); + if ((*last != '\0') || (last == port_str)) { + printk(KERN_ERR "vde_setup - Bad port : '%s'\n", + port_str); + return 0; + } + } + + if (mode_str != NULL) { + init->mode = simple_strtoul(mode_str, &last, 8); + if ((*last != '\0') || (last == mode_str)) { + printk(KERN_ERR "vde_setup - Bad mode : '%s'\n", + mode_str); + return 0; + } + } + + printk(KERN_INFO "Configured vde device: %s\n", init->vde_switch ? + init->vde_switch : "(default socket)"); + + return 1; +} + +static struct transport vde_transport = { + .list = LIST_HEAD_INIT(vde_transport.list), + .name = "vde", + .setup = vde_setup, + .user = &vde_user_info, + .kern = &vde_kern_info, + .private_size = sizeof(struct vde_data), + .setup_size = sizeof(struct vde_init), +}; + +static int register_vde(void) +{ + register_transport(&vde_transport); + return 0; +} + +late_initcall(register_vde); diff --git a/arch/um/drivers/vde_user.c b/arch/um/drivers/vde_user.c new file mode 100644 index 000000000..64cb630d1 --- /dev/null +++ b/arch/um/drivers/vde_user.c @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2007 Luca Bigliardi (shammash@artha.org). + * Licensed under the GPL. + */ + +#include <stddef.h> +#include <errno.h> +#include <libvdeplug.h> +#include <net_user.h> +#include <um_malloc.h> +#include "vde.h" + +static int vde_user_init(void *data, void *dev) +{ + struct vde_data *pri = data; + VDECONN *conn = NULL; + int err = -EINVAL; + + pri->dev = dev; + + conn = vde_open(pri->vde_switch, pri->descr, pri->args); + + if (conn == NULL) { + err = -errno; + printk(UM_KERN_ERR "vde_user_init: vde_open failed, " + "errno = %d\n", errno); + return err; + } + + printk(UM_KERN_INFO "vde backend - connection opened\n"); + + pri->conn = conn; + + return 0; +} + +static int vde_user_open(void *data) +{ + struct vde_data *pri = data; + + if (pri->conn != NULL) + return vde_datafd(pri->conn); + + printk(UM_KERN_WARNING "vde_open - we have no VDECONN to open"); + return -EINVAL; +} + +static void vde_remove(void *data) +{ + struct vde_data *pri = data; + + if (pri->conn != NULL) { + printk(UM_KERN_INFO "vde backend - closing connection\n"); + vde_close(pri->conn); + pri->conn = NULL; + kfree(pri->args); + pri->args = NULL; + return; + } + + printk(UM_KERN_WARNING "vde_remove - we have no VDECONN to remove"); +} + +const struct net_user_info vde_user_info = { + .init = vde_user_init, + .open = vde_user_open, + .close = NULL, + .remove = vde_remove, + .add_address = NULL, + .delete_address = NULL, + .mtu = ETH_MAX_PACKET, + .max_packet = ETH_MAX_PACKET + ETH_HEADER_OTHER, +}; + +void vde_init_libstuff(struct vde_data *vpri, struct vde_init *init) +{ + struct vde_open_args *args; + + vpri->args = uml_kmalloc(sizeof(struct vde_open_args), UM_GFP_KERNEL); + if (vpri->args == NULL) { + printk(UM_KERN_ERR "vde_init_libstuff - vde_open_args " + "allocation failed"); + return; + } + + args = vpri->args; + + args->port = init->port; + args->group = init->group; + args->mode = init->mode ? init->mode : 0700; + + args->port ? printk("port %d", args->port) : + printk("undefined port"); +} + +int vde_user_read(void *conn, void *buf, int len) +{ + VDECONN *vconn = conn; + int rv; + + if (vconn == NULL) + return 0; + + rv = vde_recv(vconn, buf, len, 0); + if (rv < 0) { + if (errno == EAGAIN) + return 0; + return -errno; + } + else if (rv == 0) + return -ENOTCONN; + + return rv; +} + +int vde_user_write(void *conn, void *buf, int len) +{ + VDECONN *vconn = conn; + + if (vconn == NULL) + return 0; + + return vde_send(vconn, buf, len, 0); +} + diff --git a/arch/um/drivers/xterm.c b/arch/um/drivers/xterm.c new file mode 100644 index 000000000..20e30be44 --- /dev/null +++ b/arch/um/drivers/xterm.c @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL + */ + +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <termios.h> +#include "chan_user.h" +#include <os.h> +#include <um_malloc.h> +#include "xterm.h" + +struct xterm_chan { + int pid; + int helper_pid; + char *title; + int device; + int raw; + struct termios tt; +}; + +static void *xterm_init(char *str, int device, const struct chan_opts *opts) +{ + struct xterm_chan *data; + + data = uml_kmalloc(sizeof(*data), UM_GFP_KERNEL); + if (data == NULL) + return NULL; + *data = ((struct xterm_chan) { .pid = -1, + .helper_pid = -1, + .device = device, + .title = opts->xterm_title, + .raw = opts->raw } ); + return data; +} + +/* Only changed by xterm_setup, which is a setup */ +static char *terminal_emulator = "xterm"; +static char *title_switch = "-T"; +static char *exec_switch = "-e"; + +static int __init xterm_setup(char *line, int *add) +{ + *add = 0; + terminal_emulator = line; + + line = strchr(line, ','); + if (line == NULL) + return 0; + + *line++ = '\0'; + if (*line) + title_switch = line; + + line = strchr(line, ','); + if (line == NULL) + return 0; + + *line++ = '\0'; + if (*line) + exec_switch = line; + + return 0; +} + +__uml_setup("xterm=", xterm_setup, +"xterm=<terminal emulator>,<title switch>,<exec switch>\n" +" Specifies an alternate terminal emulator to use for the debugger,\n" +" consoles, and serial lines when they are attached to the xterm channel.\n" +" The values are the terminal emulator binary, the switch it uses to set\n" +" its title, and the switch it uses to execute a subprocess,\n" +" respectively. The title switch must have the form '<switch> title',\n" +" not '<switch>=title'. Similarly, the exec switch must have the form\n" +" '<switch> command arg1 arg2 ...'.\n" +" The default values are 'xterm=xterm,-T,-e'. Values for gnome-terminal\n" +" are 'xterm=gnome-terminal,-t,-x'.\n\n" +); + +static int xterm_open(int input, int output, int primary, void *d, + char **dev_out) +{ + struct xterm_chan *data = d; + int pid, fd, new, err; + char title[256], file[] = "/tmp/xterm-pipeXXXXXX"; + char *argv[] = { terminal_emulator, title_switch, title, exec_switch, + OS_LIB_PATH "/uml/port-helper", "-uml-socket", + file, NULL }; + + if (access(argv[4], X_OK) < 0) + argv[4] = "port-helper"; + + /* + * Check that DISPLAY is set, this doesn't guarantee the xterm + * will work but w/o it we can be pretty sure it won't. + */ + if (getenv("DISPLAY") == NULL) { + printk(UM_KERN_ERR "xterm_open: $DISPLAY not set.\n"); + return -ENODEV; + } + + /* + * This business of getting a descriptor to a temp file, + * deleting the file and closing the descriptor is just to get + * a known-unused name for the Unix socket that we really + * want. + */ + fd = mkstemp(file); + if (fd < 0) { + err = -errno; + printk(UM_KERN_ERR "xterm_open : mkstemp failed, errno = %d\n", + errno); + return err; + } + + if (unlink(file)) { + err = -errno; + printk(UM_KERN_ERR "xterm_open : unlink failed, errno = %d\n", + errno); + close(fd); + return err; + } + close(fd); + + fd = os_create_unix_socket(file, sizeof(file), 1); + if (fd < 0) { + printk(UM_KERN_ERR "xterm_open : create_unix_socket failed, " + "errno = %d\n", -fd); + return fd; + } + + sprintf(title, data->title, data->device); + pid = run_helper(NULL, NULL, argv); + if (pid < 0) { + err = pid; + printk(UM_KERN_ERR "xterm_open : run_helper failed, " + "errno = %d\n", -err); + goto out_close1; + } + + err = os_set_fd_block(fd, 0); + if (err < 0) { + printk(UM_KERN_ERR "xterm_open : failed to set descriptor " + "non-blocking, err = %d\n", -err); + goto out_kill; + } + + new = xterm_fd(fd, &data->helper_pid); + if (new < 0) { + err = new; + printk(UM_KERN_ERR "xterm_open : os_rcv_fd failed, err = %d\n", + -err); + goto out_kill; + } + + err = os_set_fd_block(new, 0); + if (err) { + printk(UM_KERN_ERR "xterm_open : failed to set xterm " + "descriptor non-blocking, err = %d\n", -err); + goto out_close2; + } + + CATCH_EINTR(err = tcgetattr(new, &data->tt)); + if (err) { + new = err; + goto out_close2; + } + + if (data->raw) { + err = raw(new); + if (err) { + new = err; + goto out_close2; + } + } + + unlink(file); + data->pid = pid; + *dev_out = NULL; + + return new; + + out_close2: + close(new); + out_kill: + os_kill_process(pid, 1); + out_close1: + close(fd); + + return err; +} + +static void xterm_close(int fd, void *d) +{ + struct xterm_chan *data = d; + + if (data->pid != -1) + os_kill_process(data->pid, 1); + data->pid = -1; + + if (data->helper_pid != -1) + os_kill_process(data->helper_pid, 0); + data->helper_pid = -1; + + os_close_file(fd); +} + +const struct chan_ops xterm_ops = { + .type = "xterm", + .init = xterm_init, + .open = xterm_open, + .close = xterm_close, + .read = generic_read, + .write = generic_write, + .console_write = generic_console_write, + .window_size = generic_window_size, + .free = generic_free, + .winch = 1, +}; diff --git a/arch/um/drivers/xterm.h b/arch/um/drivers/xterm.h new file mode 100644 index 000000000..56b9c4aba --- /dev/null +++ b/arch/um/drivers/xterm.h @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2002 Jeff Dike (jdike@karaya.com) + * Licensed under the GPL + */ + +#ifndef __XTERM_H__ +#define __XTERM_H__ + +extern int xterm_fd(int socket, int *pid_out); + +#endif + diff --git a/arch/um/drivers/xterm_kern.c b/arch/um/drivers/xterm_kern.c new file mode 100644 index 000000000..e8f9957bf --- /dev/null +++ b/arch/um/drivers/xterm_kern.c @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Licensed under the GPL + */ + +#include <linux/slab.h> +#include <linux/completion.h> +#include <linux/irqreturn.h> +#include <asm/irq.h> +#include <irq_kern.h> +#include <os.h> + +struct xterm_wait { + struct completion ready; + int fd; + int pid; + int new_fd; +}; + +static irqreturn_t xterm_interrupt(int irq, void *data) +{ + struct xterm_wait *xterm = data; + int fd; + + fd = os_rcv_fd(xterm->fd, &xterm->pid); + if (fd == -EAGAIN) + return IRQ_NONE; + + xterm->new_fd = fd; + complete(&xterm->ready); + + return IRQ_HANDLED; +} + +int xterm_fd(int socket, int *pid_out) +{ + struct xterm_wait *data; + int err, ret; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (data == NULL) { + printk(KERN_ERR "xterm_fd : failed to allocate xterm_wait\n"); + return -ENOMEM; + } + + /* This is a locked semaphore... */ + *data = ((struct xterm_wait) { .fd = socket, + .pid = -1, + .new_fd = -1 }); + init_completion(&data->ready); + + err = um_request_irq(XTERM_IRQ, socket, IRQ_READ, xterm_interrupt, + IRQF_SHARED, "xterm", data); + if (err) { + printk(KERN_ERR "xterm_fd : failed to get IRQ for xterm, " + "err = %d\n", err); + ret = err; + goto out; + } + + /* ... so here we wait for an xterm interrupt. + * + * XXX Note, if the xterm doesn't work for some reason (eg. DISPLAY + * isn't set) this will hang... */ + wait_for_completion(&data->ready); + + um_free_irq(XTERM_IRQ, data); + + ret = data->new_fd; + *pid_out = data->pid; + out: + kfree(data); + + return ret; +} |