Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+190
View File
@@ -0,0 +1,190 @@
# SPDX-License-Identifier: GPL-2.0
menu "UML Character Devices"
config STDERR_CONSOLE
bool "stderr console"
default y
help
console driver which dumps all printk messages to stderr.
config SSL
bool "Virtual serial line"
help
The User-Mode Linux environment allows you to create virtual serial
lines on the UML that are usually made to show up on the host as
ttys or ptys.
See <http://user-mode-linux.sourceforge.net/old/input.html> for more
information and command line examples of how to use this facility.
Unless you have a specific reason for disabling this, say Y.
config NULL_CHAN
bool "null channel support"
help
This option enables support for attaching UML consoles and serial
lines to a device similar to /dev/null. Data written to it disappears
and there is never any data to be read.
config PORT_CHAN
bool "port channel support"
help
This option enables support for attaching UML consoles and serial
lines to host portals. They may be accessed with 'telnet <host>
<port number>'. Any number of consoles and serial lines may be
attached to a single portal, although what UML device you get when
you telnet to that portal will be unpredictable.
It is safe to say 'Y' here.
config PTY_CHAN
bool "pty channel support"
help
This option enables support for attaching UML consoles and serial
lines to host pseudo-terminals. Access to both traditional
pseudo-terminals (/dev/pty*) and pts pseudo-terminals are controlled
with this option. The assignment of UML devices to host devices
will be announced in the kernel message log.
It is safe to say 'Y' here.
config TTY_CHAN
bool "tty channel support"
help
This option enables support for attaching UML consoles and serial
lines to host terminals. Access to both virtual consoles
(/dev/tty*) and the slave side of pseudo-terminals (/dev/ttyp* and
/dev/pts/*) are controlled by this option.
It is safe to say 'Y' here.
config XTERM_CHAN
bool "xterm channel support"
help
This option enables support for attaching UML consoles and serial
lines to xterms. Each UML device so assigned will be brought up in
its own xterm.
It is safe to say 'Y' here.
config XTERM_CHAN_DEFAULT_EMULATOR
string "xterm channel default terminal emulator"
depends on XTERM_CHAN
default "xterm"
help
This option allows changing the default terminal emulator.
config NOCONFIG_CHAN
bool
default !(XTERM_CHAN && TTY_CHAN && PTY_CHAN && PORT_CHAN && NULL_CHAN)
config CON_ZERO_CHAN
string "Default main console channel initialization"
default "fd:0,fd:1"
help
This is the string describing the channel to which the main console
will be attached by default. This value can be overridden from the
command line. The default value is "fd:0,fd:1", which attaches the
main console to stdin and stdout.
It is safe to leave this unchanged.
config CON_CHAN
string "Default console channel initialization"
default "xterm"
help
This is the string describing the channel to which all consoles
except the main console will be attached by default. This value can
be overridden from the command line. The default value is "xterm",
which brings them up in xterms.
It is safe to leave this unchanged, although you may wish to change
this if you expect the UML that you build to be run in environments
which don't have X or xterm available.
config SSL_CHAN
string "Default serial line channel initialization"
default "pty"
help
This is the string describing the channel to which the serial lines
will be attached by default. This value can be overridden from the
command line. The default value is "pty", which attaches them to
traditional pseudo-terminals.
It is safe to leave this unchanged, although you may wish to change
this if you expect the UML that you build to be run in environments
which don't have a set of /dev/pty* devices.
config UML_SOUND
tristate "Sound support"
depends on SOUND
select SOUND_OSS_CORE
help
This option enables UML sound support. If enabled, it will pull in
the UML hostaudio relay, which acts as a intermediary
between the host's dsp and mixer devices and the UML sound system.
It is safe to say 'Y' here.
endmenu
menu "UML Network Devices"
depends on NET
config UML_NET_VECTOR
bool "Vector I/O high performance network devices"
select MAY_HAVE_RUNTIME_DEPS
help
This User-Mode Linux network driver uses multi-message send
and receive functions. The host running the UML guest must have
a linux kernel version above 3.0 and a libc version > 2.13.
This driver provides tap, raw, gre and l2tpv3 network transports.
For more information, including explanations of the networking
and sample configurations, see
<file:Documentation/virt/uml/user_mode_linux_howto_v2.rst>.
endmenu
config VIRTIO_UML
bool "UML driver for virtio devices"
select VIRTIO
help
This driver provides support for virtio based paravirtual device
drivers over vhost-user sockets.
config UML_RTC
bool "UML RTC driver"
depends on RTC_CLASS
# there's no use in this if PM_SLEEP isn't enabled ...
depends on PM_SLEEP
help
When PM_SLEEP is configured, it may be desirable to wake up using
rtcwake, especially in time-travel mode. This driver enables that
by providing a fake RTC clock that causes a wakeup at the right
time.
config UML_PCI
bool
select FORCE_PCI
select IRQ_MSI_LIB
select UML_IOMEM_EMULATION
select UML_DMA_EMULATION
select PCI_MSI
select PCI_LOCKLESS_CONFIG
config UML_PCI_OVER_VIRTIO
bool "Enable PCI over VIRTIO device simulation"
# in theory, just VIRTIO is enough, but that causes recursion
depends on VIRTIO_UML
select UML_PCI
config UML_PCI_OVER_VIRTIO_DEVICE_ID
int "set the virtio device ID for PCI emulation"
default -1
depends on UML_PCI_OVER_VIRTIO
help
There's no official device ID assigned (yet), set the one you
wish to use for experimentation here. The default of -1 is
not valid and will cause the driver to fail at probe.
config UML_PCI_OVER_VFIO
bool "Enable VFIO-based PCI passthrough"
select UML_PCI
help
This driver provides support for VFIO-based PCI passthrough.
Currently, only MSI-X capable devices are supported, and it
is assumed that drivers will use MSI-X.
+55
View File
@@ -0,0 +1,55 @@
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (C) 2000, 2002, 2003 Jeff Dike (jdike@karaya.com)
#
# pcap is broken in 2.5 because kbuild doesn't allow pcap.a to be linked
# in to pcap.o
vector-objs := vector_kern.o vector_user.o vector_transports.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-builtin-$(CONFIG_UML_WATCHDOG) := harddog_user.o harddog_user_exp.o
rtc-objs := rtc_kern.o rtc_user.o
vfio_uml-objs := vfio_kern.o vfio_user.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_VECTOR) += vector.o
obj-$(CONFIG_MCONSOLE) += mconsole.o
obj-$(CONFIG_BLK_DEV_UBD) += ubd.o
obj-$(CONFIG_UML_SOUND) += 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-y += $(harddog-builtin-y) $(harddog-builtin-m)
obj-$(CONFIG_BLK_DEV_COW_COMMON) += cow_user.o
obj-$(CONFIG_UML_RANDOM) += random.o
obj-$(CONFIG_VIRTIO_UML) += virtio_uml.o
obj-$(CONFIG_UML_RTC) += rtc.o
obj-$(CONFIG_UML_PCI) += virt-pci.o
obj-$(CONFIG_UML_PCI_OVER_VIRTIO) += virtio_pcidev.o
obj-$(CONFIG_UML_PCI_OVER_VFIO) += vfio_uml.o
# pcap_user.o must be added explicitly.
USER_OBJS := fd.o null.o pty.o tty.o xterm.o vector_user.o
CFLAGS_null.o = -DDEV_NULL=$(DEV_NULL_PATH)
CFLAGS_xterm.o += '-DCONFIG_XTERM_CHAN_DEFAULT_EMULATOR="$(CONFIG_XTERM_CHAN_DEFAULT_EMULATOR)"'
include $(srctree)/arch/um/scripts/Makefile.rules
+49
View File
@@ -0,0 +1,49 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2000, 2001 Jeff Dike (jdike@karaya.com)
*/
#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_in;
int fd_out; /* only different to fd_in if blocking output is needed */
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 u8 *buf, size_t 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 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
+610
View File
@@ -0,0 +1,610 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2000 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com)
*/
#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, u8 *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 u8 *buf, size_t 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 inline bool need_output_blocking(void)
{
return time_travel_mode == TT_MODE_INFCPU ||
time_travel_mode == TT_MODE_EXTERNAL;
}
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)
goto out_close;
chan->fd_in = fd;
chan->fd_out = fd;
/*
* In time-travel modes infinite-CPU and external we need to guarantee
* that any writes to the output succeed immdiately from the point of
* the VM. The best way to do this is to put the FD in blocking mode
* and simply wait/retry until everything is written.
* As every write is guaranteed to complete, we also do not need to
* request an IRQ for the output.
*
* Note that input cannot happen in a time synchronized way. We permit
* it, but time passes very quickly if anything waits for a read.
*/
if (chan->output && need_output_blocking()) {
err = os_dup_file(chan->fd_out);
if (err < 0)
goto out_close;
chan->fd_out = err;
err = os_set_fd_block(chan->fd_out, 1);
if (err) {
os_close_file(chan->fd_out);
goto out_close;
}
}
chan->opened = 1;
return 0;
out_close:
(*chan->ops->close)(fd, chan->data);
return err;
}
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_in, 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->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_in, chan->input,
chan->output && !need_output_blocking(),
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_RAW_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;
raw_spin_lock_irqsave(&irqs_to_free_lock, flags);
list_splice_init(&irqs_to_free, &list);
raw_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->read_irq, chan);
if (chan->output && chan->enabled &&
!need_output_blocking())
um_free_irq(chan->line->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) {
raw_spin_lock_irqsave(&irqs_to_free_lock, flags);
list_add(&chan->free_list, &irqs_to_free);
raw_spin_unlock_irqrestore(&irqs_to_free_lock, flags);
} else {
if (chan->input && chan->enabled)
um_free_irq(chan->line->read_irq, chan);
if (chan->output && chan->enabled &&
!need_output_blocking())
um_free_irq(chan->line->write_irq, chan);
chan->enabled = 0;
}
if (chan->fd_out != chan->fd_in)
os_close_file(chan->fd_out);
if (chan->ops->close != NULL)
(*chan->ops->close)(chan->fd_in, chan->data);
chan->opened = 0;
chan->fd_in = -1;
chan->fd_out = -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_in, irq);
}
int write_chan(struct chan *chan, const u8 *buf, size_t len, int write_irq)
{
int n, ret = 0;
if (len == 0 || !chan || !chan->ops->write)
return 0;
n = chan->ops->write(chan->fd_out, buf, len, chan->data);
if (chan->primary) {
ret = n;
}
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_out, 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_in, 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_in, 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_in);
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", &not_configged_ops },
#endif
#ifdef CONFIG_PORT_CHAN
{ "port", &port_ops },
#else
{ "port", &not_configged_ops },
#endif
#ifdef CONFIG_PTY_CHAN
{ "pty", &pty_ops },
{ "pts", &pts_ops },
#else
{ "pty", &not_configged_ops },
{ "pts", &not_configged_ops },
#endif
#ifdef CONFIG_TTY_CHAN
{ "tty", &tty_ops },
#else
{ "tty", &not_configged_ops },
#endif
#ifdef CONFIG_XTERM_CHAN
{ "xterm", &xterm_ops },
#else
{ "xterm", &not_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_obj(*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_in = -1,
.fd_out = -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;
u8 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_in, &c, chan->data);
if (err > 0)
tty_insert_flip_char(port, c, TTY_NORMAL);
} while (err > 0);
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);
}
+323
View File
@@ -0,0 +1,323 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2000 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com)
*/
#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, __u8 *c_out, void *unused)
{
int n;
CATCH_EINTR(n = read(fd, c_out, sizeof(*c_out)));
if (n > 0)
return n;
else if (n == 0)
return -EIO;
else if (errno == EAGAIN)
return 0;
return -errno;
}
/* XXX Trivial wrapper around write */
int generic_write(int fd, const __u8 *buf, size_t n, void *unused)
{
int written = 0;
int err;
/* The FD may be in blocking mode, as such, need to retry short writes,
* they may have been interrupted by a signal.
*/
do {
errno = 0;
err = write(fd, buf + written, n - written);
if (err > 0) {
written += err;
continue;
}
} while (err < 0 && errno == EINTR);
if (written > 0)
return written;
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 __noreturn int winch_thread(void *arg)
{
struct winch_data *data = arg;
sigset_t sigs;
int pty_fd, pipe_fd;
int count;
char c = 1;
os_set_pdeathsig();
pty_fd = data->pty_fd;
pipe_fd = data->pipe_fd;
count = write(pipe_fd, &c, sizeof(c));
if (count != sizeof(c))
os_info("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) {
os_info("winch_thread : sigprocmask failed, errno = %d\n",
errno);
goto wait_kill;
}
/* In sigsuspend(), block anything else than SIGWINCH. */
sigdelset(&sigs, SIGWINCH);
if (setsid() < 0) {
os_info("winch_thread : setsid failed, errno = %d\n",
errno);
goto wait_kill;
}
if (ioctl(pty_fd, TIOCSCTTY, 0) < 0) {
os_info("winch_thread : TIOCSCTTY failed on "
"fd %d err = %d\n", pty_fd, errno);
goto wait_kill;
}
if (tcsetpgrp(pty_fd, os_getpid()) < 0) {
os_info("winch_thread : tcsetpgrp failed on fd %d err = %d\n",
pty_fd, errno);
goto wait_kill;
}
/*
* 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))
os_info("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))
os_info("winch_thread : write failed, err = %d\n",
errno);
}
wait_kill:
c = 2;
count = write(pipe_fd, &c, sizeof(c));
while (1)
pause();
}
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, pid;
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.
*/
pid = run_helper_thread(winch_thread, &data, CLONE_FILES, stack_out);
if (pid < 0) {
err = pid;
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;
}
err = os_set_fd_block(*fd_out, 0);
if (err) {
printk(UM_KERN_ERR "winch_tramp: failed to set thread_fd "
"non-blocking.\n");
goto out_close;
}
return pid;
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);
}
}
+54
View File
@@ -0,0 +1,54 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2000, 2001 Jeff Dike (jdike@karaya.com)
*/
#ifndef __CHAN_USER_H__
#define __CHAN_USER_H__
#include <init.h>
#include <linux/types.h>
struct chan_opts {
void (*const announce)(char *dev_name, int dev);
char *xterm_title;
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, __u8 *, void *);
int (*write)(int, const __u8 *, size_t, 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, __u8 *c_out, void *unused);
extern int generic_write(int fd, const __u8 *buf, size_t 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
+26
View File
@@ -0,0 +1,26 @@
/* SPDX-License-Identifier: GPL-2.0 */
#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, long long *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
+41
View File
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0 */
#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
+450
View File
@@ -0,0 +1,450 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com)
*/
/*
* _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"
/*
* arch/um/Makefile remaps strrchr to kernel_strrchr; call the kernel
* name directly to avoid glibc >= 2.43's C23 strrchr macro.
*/
char *kernel_strrchr(const char *, int);
#define PATH_LEN_V1 256
/* unsigned time_t works until year 2106 */
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 = kernel_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;
long 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,
long long *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
* 2106+. 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;
}
+95
View File
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2001 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com)
*/
#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,
};
+9
View File
@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef UM_WATCHDOG_H
#define UM_WATCHDOG_H
int start_watchdog(int *in_fd_ret, int *out_fd_ret, char *sock);
void stop_watchdog(int in_fd, int out_fd);
int ping_watchdog(int fd);
#endif /* UM_WATCHDOG_H */
+174
View File
@@ -0,0 +1,174 @@
/* 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 <linux/uaccess.h>
#include "mconsole.h"
#include "harddog.h"
MODULE_DESCRIPTION("UML hardware watchdog");
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
*/
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 stream_open(inode, file);
err:
spin_unlock(&lock);
mutex_unlock(&harddog_mutex);
return err;
}
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;
}
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,
.compat_ioctl = compat_ptr_ioctl,
.open = harddog_open,
.release = harddog_release,
};
static struct miscdevice harddog_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &harddog_fops,
};
module_misc_device(harddog_miscdev);
+128
View File
@@ -0,0 +1,128 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
*/
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <os.h>
#include "harddog.h"
struct dog_data {
int stdin_fd;
int stdout_fd;
int close_me[2];
};
static void pre_exec(void *d)
{
struct dog_data *data = d;
dup2(data->stdin_fd, 0);
dup2(data->stdout_fd, 1);
dup2(data->stdout_fd, 2);
close(data->stdin_fd);
close(data->stdout_fd);
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_fd = out_fds[0];
data.stdout_fd = 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;
}
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/export.h>
#include "harddog.h"
#if IS_MODULE(CONFIG_UML_WATCHDOG)
EXPORT_SYMBOL(start_watchdog);
EXPORT_SYMBOL(stop_watchdog);
EXPORT_SYMBOL(ping_watchdog);
#endif
@@ -0,0 +1,353 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2002 Steve Schmidtke
*/
#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 <linux/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)
{
*add = 0;
dsp = name;
return 0;
}
__uml_setup("dsp=", set_dsp, "dsp=<dsp device>\n" DSP_HELP);
static int set_mixer(char *name, int *add)
{
*add = 0;
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 = memdup_user(buffer, count);
if (IS_ERR(kbuf))
return PTR_ERR(kbuf);
err = os_write_file(state->fd, kbuf, count);
if (err < 0)
goto out;
*ppos += err;
out:
kfree(kbuf);
return err;
}
static __poll_t hostaudio_poll(struct file *file,
struct poll_table_struct *wait)
{
#ifdef DEBUG
printk(KERN_DEBUG "hostaudio: poll called (unimplemented)\n");
#endif
return 0;
}
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
kernel_param_lock(THIS_MODULE);
printk(KERN_DEBUG "hostaudio: open called (host: %s)\n", dsp);
kernel_param_unlock(THIS_MODULE);
#endif
state = kmalloc_obj(struct hostaudio_state);
if (state == NULL)
return -ENOMEM;
if (file->f_mode & FMODE_READ)
r = 1;
if (file->f_mode & FMODE_WRITE)
w = 1;
kernel_param_lock(THIS_MODULE);
mutex_lock(&hostaudio_mutex);
ret = os_open_file(dsp, of_set_rw(OPENFLAGS(), r, w), 0);
mutex_unlock(&hostaudio_mutex);
kernel_param_unlock(THIS_MODULE);
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_obj(struct hostmixer_state);
if (state == NULL)
return -ENOMEM;
if (file->f_mode & FMODE_READ)
r = 1;
if (file->f_mode & FMODE_WRITE)
w = 1;
kernel_param_lock(THIS_MODULE);
mutex_lock(&hostaudio_mutex);
ret = os_open_file(mixer, of_set_rw(OPENFLAGS(), r, w), 0);
mutex_unlock(&hostaudio_mutex);
kernel_param_unlock(THIS_MODULE);
if (ret < 0) {
kernel_param_lock(THIS_MODULE);
printk(KERN_ERR "hostaudio_open_mixdev failed to open '%s', "
"err = %d\n", dsp, -ret);
kernel_param_unlock(THIS_MODULE);
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,
.read = hostaudio_read,
.write = hostaudio_write,
.poll = hostaudio_poll,
.unlocked_ioctl = hostaudio_ioctl,
.compat_ioctl = compat_ptr_ioctl,
.mmap = NULL,
.open = hostaudio_open,
.release = hostaudio_release,
};
static const struct file_operations hostmixer_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = hostmixer_ioctl_mixdev,
.open = hostmixer_open_mixdev,
.release = hostmixer_release,
};
static 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(THIS_MODULE);
printk(KERN_INFO "UML Audio Relay (host dsp = %s, host mixer = %s)\n",
dsp, mixer);
kernel_param_unlock(THIS_MODULE);
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);
+771
View File
@@ -0,0 +1,771 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
*/
#include <linux/irqreturn.h>
#include <linux/kd.h>
#include <linux/sched/signal.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 unsigned 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;
}
unsigned int line_write_room(struct tty_struct *tty)
{
struct line *line = tty->driver_data;
unsigned long flags;
unsigned int room;
spin_lock_irqsave(&line->lock, flags);
room = write_room(line);
spin_unlock_irqrestore(&line->lock, flags);
return room;
}
unsigned int line_chars_in_buffer(struct tty_struct *tty)
{
struct line *line = tty->driver_data;
unsigned long flags;
unsigned 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 u8 *buf, size_t 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->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->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);
}
ssize_t line_write(struct tty_struct *tty, const u8 *buf, size_t 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->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_throttle(struct tty_struct *tty)
{
struct line *line = tty->driver_data;
deactivate_chan(line->chan_in, line->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->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) && (err != -EAGAIN)) {
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;
if (input) {
err = um_request_irq(UM_IRQ_ALLOC, fd, IRQ_READ,
line_interrupt, 0,
driver->read_irq_name, data);
if (err < 0)
return err;
line->read_irq = err;
}
if (output) {
err = um_request_irq(UM_IRQ_ALLOC, fd, IRQ_WRITE,
line_write_interrupt, 0,
driver->write_irq_name, data);
if (err < 0)
return err;
line->write_irq = err;
}
return 0;
}
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;
}
*error_out = "configured as 'none'";
} 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) {
*error_out = "failed to parse channel pair";
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;
int err;
int i;
driver = tty_alloc_driver(nlines, TTY_DRIVER_REAL_RAW |
TTY_DRIVER_DYNAMIC_DEV);
if (IS_ERR(driver))
return PTR_ERR(driver);
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->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);
tty_driver_kref_put(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);
__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);
/* A read of 2 means the winch thread failed and has warned */
if (err < 0 || (err == 1 && c == 2)) {
if (err != -EAGAIN) {
winch->fd = -1;
list_del(&winch->list);
os_close_file(fd);
if (err < 0) {
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:
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_obj(*winch);
if (winch == NULL) {
printk(KERN_ERR "register_winch_irq - kmalloc failed\n");
goto cleanup;
}
*winch = ((struct winch) { .fd = fd,
.tty_fd = tty_fd,
.pid = pid,
.port = port,
.stack = stack });
spin_lock(&winch_handler_lock);
list_add(&winch->list, &winch_handlers);
spin_unlock(&winch_handler_lock);
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");
spin_lock(&winch_handler_lock);
list_del(&winch->list);
spin_unlock(&winch_handler_lock);
goto out_free;
}
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) {
list_del(&winch->list);
spin_unlock(&winch_handler_lock);
free_winch(winch);
break;
}
tty_kref_put(wtty);
}
spin_unlock(&winch_handler_lock);
}
static void winch_cleanup(void)
{
struct winch *winch;
spin_lock(&winch_handler_lock);
while ((winch = list_first_entry_or_null(&winch_handlers,
struct winch, list))) {
list_del(&winch->list);
spin_unlock(&winch_handler_lock);
free_winch(winch);
spin_lock(&winch_handler_lock);
}
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;
}
+96
View File
@@ -0,0 +1,96 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2001, 2002 Jeff Dike (jdike@karaya.com)
*/
#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 char *read_irq_name;
const char *write_irq_name;
struct mc_device mc;
struct tty_driver *driver;
};
struct line {
struct tty_port port;
int valid;
int read_irq, write_irq;
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.*/
u8 *buffer;
u8 *head;
u8 *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 ssize_t line_write(struct tty_struct *tty, const u8 *buf, size_t len);
extern unsigned 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 unsigned 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
+98
View File
@@ -0,0 +1,98 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2001 Lennert Buytenhek (buytenh@gnu.org)
* Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
*/
#ifndef __MCONSOLE_H__
#define __MCONSOLE_H__
#ifdef __UM_HOST__
#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
+871
View File
@@ -0,0 +1,871 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2001 Lennert Buytenhek (buytenh@gnu.org)
* Copyright (C) 2001 - 2008 Jeff Dike (jdike@{addtoit,linux.intel}.com)
*/
#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/panic_notifier.h>
#include <linux/reboot.h>
#include <linux/sched/debug.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 <linux/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 struct vfsmount *proc_mnt = NULL;
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_obj(*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);
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 = proc_mnt;
char *buf;
int len;
struct file *file;
int first_chunk = 1;
char *ptr = req->request.data;
loff_t pos = 0;
ptr += strlen("proc");
ptr = skip_spaces(ptr);
if (!mnt) {
mconsole_reply(req, "Proc not available", 1, 0);
goto out;
}
file = file_open_root_mnt(mnt, ptr, O_RDONLY, 0);
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 {
len = kernel_read(file, buf, PAGE_SIZE - 1, &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)
{
block_signals();
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);
mconsole_reply(req, "", 0, 0);
unblock_signals();
}
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;
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] __nonstring;
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));
memcpy(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, KERN_INFO);
}
/*
* 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);
}
static int __init mount_proc(void)
{
struct file_system_type *proc_fs_type;
struct vfsmount *mnt;
proc_fs_type = get_fs_type("proc");
if (!proc_fs_type)
return -ENODEV;
mnt = kern_mount(proc_fs_type);
put_filesystem(proc_fs_type);
if (IS_ERR(mnt))
return PTR_ERR(mnt);
proc_mnt = mnt;
return 0;
}
/*
* 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];
mount_proc();
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 < 0) {
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 = memdup_user_nul(buffer, count);
if (IS_ERR(buf))
return PTR_ERR(buf);
mconsole_notify(notify_socket, MCONSOLE_USER_NOTIFY, buf, count);
kfree(buf);
return count;
}
static const struct proc_ops mconsole_proc_ops = {
.proc_write = mconsole_proc_write,
.proc_lseek = 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_ops);
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(&notify_spinlock);
}
void unlock_notify(void)
{
spin_unlock(&notify_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 NOTIFY_DONE;
}
static struct notifier_block panic_exit_notifier = {
.notifier_call = notify_panic,
.priority = INT_MAX, /* run as soon as possible */
};
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);
@@ -0,0 +1,52 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2001, 2002 Jeff Dike (jdike@karaya.com)
*/
#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
+221
View File
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2001 Lennert Buytenhek (buytenh@gnu.org)
* Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
*/
#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;
}
#ifndef MIN
#define MIN(a,b) ((a)<(b) ? (a):(b))
#endif
#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;
}
+51
View File
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2002 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com)
*/
#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, __u8 *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,
};
+20
View File
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2001 Jeff Dike (jdike@karaya.com)
*/
#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
+305
View File
@@ -0,0 +1,305 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2001 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com)
*/
#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 n_fds = 1, fd = -1;
ssize_t ret;
ret = os_rcv_fd_msg(conn->socket[0], &fd, n_fds, &conn->helper_pid,
sizeof(conn->helper_pid));
if (ret != sizeof(conn->helper_pid)) {
if (ret == -EAGAIN)
return IRQ_NONE;
printk(KERN_ERR "pipe_interrupt : os_rcv_fd_msg returned %zd\n",
ret);
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_obj(*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) < 0) {
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;
while (port_accept(port))
;
port->has_connection = 0;
}
local_irq_restore(flags);
}
static 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_obj(struct port_list);
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) < 0) {
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_obj(struct port_dev);
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);
+217
View File
@@ -0,0 +1,217 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2001 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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 *env;
char *argv[] = { "in.telnetd", "-L",
OS_LIB_PATH "/uml/port-helper", NULL };
struct port_pre_exec_data data;
if ((env = getenv("UML_PORT_HELPER")))
argv[2] = env;
new = accept(fd, NULL, 0);
if (new < 0)
return -errno;
err = os_access(argv[2], X_OK);
if (err < 0) {
printk(UM_KERN_ERR "port_connection : error accessing port-helper "
"executable at %s: %s\n", argv[2], strerror(-err));
if (env == NULL)
printk(UM_KERN_ERR "Set UML_PORT_HELPER environment "
"variable to path to uml-utilities port-helper "
"binary\n");
goto out_close;
}
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;
}
+165
View File
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
*/
#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,
};
+121
View File
@@ -0,0 +1,121 @@
/* 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/signal.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/miscdevice.h>
#include <linux/hw_random.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <init.h>
#include <irq_kern.h>
#include <os.h>
/*
* core module information
*/
#define RNG_MODULE_NAME "hw_random"
/* 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 struct hwrng hwrng;
static DECLARE_COMPLETION(have_data);
static int rng_dev_read(struct hwrng *rng, void *buf, size_t max, bool block)
{
int ret;
for (;;) {
ret = os_read_file(random_fd, buf, max);
if (block && ret == -EAGAIN) {
add_sigio_fd(random_fd);
ret = wait_for_completion_killable(&have_data);
ignore_sigio_fd(random_fd);
deactivate_fd(random_fd, RANDOM_IRQ);
if (ret < 0)
break;
} else {
break;
}
}
return ret != -EAGAIN ? ret : 0;
}
static irqreturn_t random_interrupt(int irq, void *data)
{
complete(&have_data);
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 < 0)
goto err_out_cleanup_hw;
sigio_broken();
hwrng.name = RNG_MODULE_NAME;
hwrng.read = rng_dev_read;
err = hwrng_register(&hwrng);
if (err) {
pr_err(RNG_MODULE_NAME " registering failed (%d)\n", err);
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 cleanup(void)
{
free_irq_by_fd(random_fd);
os_close_file(random_fd);
}
static void __exit rng_cleanup(void)
{
hwrng_unregister(&hwrng);
os_close_file(random_fd);
}
module_init (rng_init);
module_exit (rng_cleanup);
__uml_exitcall(cleanup);
MODULE_DESCRIPTION("UML Host Random Number Generator (RNG) driver");
MODULE_LICENSE("GPL");
+15
View File
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2020 Intel Corporation
* Author: Johannes Berg <johannes@sipsolutions.net>
*/
#ifndef __UM_RTC_H__
#define __UM_RTC_H__
int uml_rtc_start(bool timetravel);
int uml_rtc_enable_alarm(unsigned long long delta_seconds);
void uml_rtc_disable_alarm(void);
void uml_rtc_stop(bool timetravel);
void uml_rtc_send_timetravel_alarm(void);
#endif /* __UM_RTC_H__ */
+213
View File
@@ -0,0 +1,213 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020 Intel Corporation
* Author: Johannes Berg <johannes@sipsolutions.net>
*/
#include <linux/platform_device.h>
#include <linux/time-internal.h>
#include <linux/suspend.h>
#include <linux/err.h>
#include <linux/rtc.h>
#include <kern_util.h>
#include <irq_kern.h>
#include <os.h>
#include "rtc.h"
static time64_t uml_rtc_alarm_time;
static bool uml_rtc_alarm_enabled;
static struct rtc_device *uml_rtc;
static int uml_rtc_irq_fd, uml_rtc_irq;
#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT
static void uml_rtc_time_travel_alarm(struct time_travel_event *ev)
{
uml_rtc_send_timetravel_alarm();
}
static struct time_travel_event uml_rtc_alarm_event = {
.fn = uml_rtc_time_travel_alarm,
};
#endif
static int uml_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
struct timespec64 ts;
/* Use this to get correct time in time-travel mode */
read_persistent_clock64(&ts);
rtc_time64_to_tm(timespec64_to_ktime(ts) / NSEC_PER_SEC, tm);
return 0;
}
static int uml_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
rtc_time64_to_tm(uml_rtc_alarm_time, &alrm->time);
alrm->enabled = uml_rtc_alarm_enabled;
return 0;
}
static int uml_rtc_alarm_irq_enable(struct device *dev, unsigned int enable)
{
struct timespec64 ts;
unsigned long long secs;
if (!enable && !uml_rtc_alarm_enabled)
return 0;
uml_rtc_alarm_enabled = enable;
read_persistent_clock64(&ts);
secs = uml_rtc_alarm_time - ts.tv_sec;
if (time_travel_mode == TT_MODE_OFF) {
if (!enable) {
uml_rtc_disable_alarm();
return 0;
}
/* enable or update */
return uml_rtc_enable_alarm(secs);
} else {
time_travel_del_event(&uml_rtc_alarm_event);
if (enable)
time_travel_add_event_rel(&uml_rtc_alarm_event,
secs * NSEC_PER_SEC -
ts.tv_nsec);
}
return 0;
}
static int uml_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
uml_rtc_alarm_irq_enable(dev, 0);
uml_rtc_alarm_time = rtc_tm_to_time64(&alrm->time);
uml_rtc_alarm_irq_enable(dev, alrm->enabled);
return 0;
}
static const struct rtc_class_ops uml_rtc_ops = {
.read_time = uml_rtc_read_time,
.read_alarm = uml_rtc_read_alarm,
.alarm_irq_enable = uml_rtc_alarm_irq_enable,
.set_alarm = uml_rtc_set_alarm,
};
static irqreturn_t uml_rtc_interrupt(int irq, void *data)
{
unsigned long long c = 0;
/* alarm triggered, it's now off */
uml_rtc_alarm_enabled = false;
os_read_file(uml_rtc_irq_fd, &c, sizeof(c));
WARN_ON(c == 0);
pm_system_wakeup();
rtc_update_irq(uml_rtc, 1, RTC_IRQF | RTC_AF);
return IRQ_HANDLED;
}
static int uml_rtc_setup(void)
{
int err;
err = uml_rtc_start(time_travel_mode != TT_MODE_OFF);
if (WARN(err < 0, "err = %d\n", err))
return err;
uml_rtc_irq_fd = err;
err = um_request_irq(UM_IRQ_ALLOC, uml_rtc_irq_fd, IRQ_READ,
uml_rtc_interrupt, 0, "rtc", NULL);
if (err < 0) {
uml_rtc_stop(time_travel_mode != TT_MODE_OFF);
return err;
}
irq_set_irq_wake(err, 1);
uml_rtc_irq = err;
return 0;
}
static void uml_rtc_cleanup(void)
{
um_free_irq(uml_rtc_irq, NULL);
uml_rtc_stop(time_travel_mode != TT_MODE_OFF);
}
static int uml_rtc_probe(struct platform_device *pdev)
{
int err;
err = uml_rtc_setup();
if (err)
return err;
uml_rtc = devm_rtc_allocate_device(&pdev->dev);
if (IS_ERR(uml_rtc)) {
err = PTR_ERR(uml_rtc);
goto cleanup;
}
uml_rtc->ops = &uml_rtc_ops;
device_init_wakeup(&pdev->dev, 1);
err = devm_rtc_register_device(uml_rtc);
if (err)
goto cleanup;
return 0;
cleanup:
uml_rtc_cleanup();
return err;
}
static void uml_rtc_remove(struct platform_device *pdev)
{
device_init_wakeup(&pdev->dev, 0);
uml_rtc_cleanup();
}
static struct platform_driver uml_rtc_driver = {
.probe = uml_rtc_probe,
.remove = uml_rtc_remove,
.driver = {
.name = "uml-rtc",
},
};
static int __init uml_rtc_init(void)
{
struct platform_device *pdev;
int err;
err = platform_driver_register(&uml_rtc_driver);
if (err)
return err;
pdev = platform_device_alloc("uml-rtc", 0);
if (!pdev) {
err = -ENOMEM;
goto unregister;
}
err = platform_device_add(pdev);
if (err)
goto unregister;
return 0;
unregister:
platform_device_put(pdev);
platform_driver_unregister(&uml_rtc_driver);
return err;
}
device_initcall(uml_rtc_init);
+81
View File
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020 Intel Corporation
* Author: Johannes Berg <johannes@sipsolutions.net>
*/
#include <stdbool.h>
#include <os.h>
#include <errno.h>
#include <sched.h>
#include <unistd.h>
#include <kern_util.h>
#include <sys/select.h>
#include <stdio.h>
#include <sys/timerfd.h>
#include "rtc.h"
static int uml_rtc_irq_fds[2];
void uml_rtc_send_timetravel_alarm(void)
{
unsigned long long c = 1;
CATCH_EINTR(write(uml_rtc_irq_fds[1], &c, sizeof(c)));
}
int uml_rtc_start(bool timetravel)
{
int err;
if (timetravel) {
err = os_pipe(uml_rtc_irq_fds, 1, 1);
if (err)
goto fail;
} else {
uml_rtc_irq_fds[0] = timerfd_create(CLOCK_REALTIME, TFD_CLOEXEC);
if (uml_rtc_irq_fds[0] < 0) {
err = -errno;
goto fail;
}
/* apparently timerfd won't send SIGIO, use workaround */
sigio_broken();
err = add_sigio_fd(uml_rtc_irq_fds[0]);
if (err < 0) {
close(uml_rtc_irq_fds[0]);
goto fail;
}
}
return uml_rtc_irq_fds[0];
fail:
uml_rtc_stop(timetravel);
return err;
}
int uml_rtc_enable_alarm(unsigned long long delta_seconds)
{
struct itimerspec it = {
.it_value = {
.tv_sec = delta_seconds,
},
};
if (timerfd_settime(uml_rtc_irq_fds[0], 0, &it, NULL))
return -errno;
return 0;
}
void uml_rtc_disable_alarm(void)
{
uml_rtc_enable_alarm(0);
}
void uml_rtc_stop(bool timetravel)
{
if (timetravel)
os_close_file(uml_rtc_irq_fds[1]);
else
ignore_sigio_fd(uml_rtc_irq_fds[0]);
os_close_file(uml_rtc_irq_fds[0]);
}
+205
View File
@@ -0,0 +1,205 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2000, 2002 Jeff Dike (jdike@karaya.com)
*/
#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 "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_name = "ssl",
.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,
.write_room = line_write_room,
.chars_in_buffer = line_chars_in_buffer,
.flush_buffer = line_flush_buffer,
.flush_chars = line_flush_chars,
.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;
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");
static int ssl_non_raw_setup(char *str)
{
opts.raw = 0;
return 1;
}
__setup("ssl-non-raw", ssl_non_raw_setup);
__uml_help(ssl_non_raw_setup,
"ssl-non-raw\n"
" Set serial lines to non-raw mode.\n\n"
);
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: GPL-2.0
#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);
+198
View File
@@ -0,0 +1,198 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2000, 2001 Jeff Dike (jdike@karaya.com)
*/
#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_name = "console",
.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;
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,
.write_room = line_write_room,
.chars_in_buffer = line_chars_in_buffer,
.flush_buffer = line_flush_buffer,
.flush_chars = line_flush_chars,
.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)
{
if (!strncmp(str, "sole=", 5)) /* console= option specifies tty */
return 0;
line_setup(vt_conf, MAX_TTYS, &def_conf, str, "console");
return 1;
}
__setup("con", console_chan_setup);
__channel_help(console_chan_setup, "con");
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2000 Jeff Dike (jdike@karaya.com)
*/
#ifndef __STDIO_CONSOLE_H
#define __STDIO_CONSOLE_H
extern void save_console_flags(void);
#endif
+81
View File
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2001 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com)
*/
#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,
};
+22
View File
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2000 Jeff Dike (jdike@karaya.com)
* Copyright (C) 2001 RidgeRun, Inc (glonnon@ridgerun.com)
*/
#ifndef __UM_UBD_USER_H
#define __UM_UBD_USER_H
#include <os.h>
int start_io_thread(struct os_helper_thread **td_out, int *fd_out);
void *io_thread(void *arg);
extern int kernel_fd;
extern int ubd_read_poll(int timeout);
extern int ubd_write_poll(int timeout);
#define UBD_REQ_BUFFER_SIZE 64
#endif
File diff suppressed because it is too large Load Diff
+78
View File
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2016 Anton Ivanov (aivanov@brocade.com)
* Copyright (C) 2000, 2001, 2002 Jeff Dike (jdike@karaya.com)
* Copyright (C) 2001 Ridgerun,Inc (glonnon@ridgerun.com)
*/
#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>
#include <poll.h>
static struct pollfd kernel_pollfd;
int start_io_thread(struct os_helper_thread **td_out, int *fd_out)
{
int 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];
kernel_pollfd.fd = kernel_fd;
kernel_pollfd.events = POLLIN;
*fd_out = fds[1];
err = os_set_fd_block(*fd_out, 0);
err |= os_set_fd_block(kernel_fd, 0);
if (err) {
printk("start_io_thread - failed to set nonblocking I/O.\n");
goto out_close;
}
err = os_run_helper_thread(td_out, io_thread, NULL);
if (err < 0) {
printk("%s - failed to run helper thread, err = %d\n",
__func__, -err);
goto out_close;
}
return 0;
out_close:
os_close_file(fds[0]);
os_close_file(fds[1]);
kernel_fd = -1;
*fd_out = -1;
out:
return err;
}
int ubd_read_poll(int timeout)
{
kernel_pollfd.events = POLLIN;
return poll(&kernel_pollfd, 1, timeout);
}
int ubd_write_poll(int timeout)
{
kernel_pollfd.events = POLLOUT;
return poll(&kernel_pollfd, 1, timeout);
}
File diff suppressed because it is too large Load Diff
+139
View File
@@ -0,0 +1,139 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2002 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
*/
#ifndef __UM_VECTOR_KERN_H
#define __UM_VECTOR_KERN_H
#include <linux/netdevice.h>
#include <linux/platform_device.h>
#include <linux/skbuff.h>
#include <linux/socket.h>
#include <linux/list.h>
#include <linux/ctype.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <asm/atomic.h>
#include "vector_user.h"
/* Queue structure specially adapted for multiple enqueue/dequeue
* in a mmsgrecv/mmsgsend context
*/
/* Dequeue method */
#define QUEUE_SENDMSG 0
#define QUEUE_SENDMMSG 1
#define VECTOR_RX 1
#define VECTOR_TX (1 << 1)
#define VECTOR_BPF (1 << 2)
#define VECTOR_QDISC_BYPASS (1 << 3)
#define VECTOR_BPF_FLASH (1 << 4)
#define ETH_MAX_PACKET 1500
#define ETH_HEADER_OTHER 32 /* just in case someone decides to go mad on QnQ */
#define MAX_FILTER_PROG (2 << 16)
struct vector_queue {
struct mmsghdr *mmsg_vector;
void **skbuff_vector;
/* backlink to device which owns us */
struct net_device *dev;
spinlock_t head_lock;
spinlock_t tail_lock;
atomic_t queue_depth;
int head, tail, max_depth, max_iov_frags;
short options;
};
struct vector_estats {
uint64_t rx_queue_max;
uint64_t rx_queue_running_average;
uint64_t tx_queue_max;
uint64_t tx_queue_running_average;
uint64_t rx_encaps_errors;
uint64_t tx_timeout_count;
uint64_t tx_restart_queue;
uint64_t tx_kicks;
uint64_t tx_flow_control_xon;
uint64_t tx_flow_control_xoff;
uint64_t rx_csum_offload_good;
uint64_t rx_csum_offload_errors;
uint64_t sg_ok;
uint64_t sg_linearized;
};
#define VERIFY_HEADER_NOK -1
#define VERIFY_HEADER_OK 0
#define VERIFY_CSUM_OK 1
struct vector_private {
struct list_head list;
struct net_device *dev;
struct napi_struct napi ____cacheline_aligned;
int unit;
/* Timeout timer in TX */
struct timer_list tl;
/* Scheduled "remove device" work */
struct work_struct reset_tx;
struct vector_fds *fds;
struct vector_queue *rx_queue;
struct vector_queue *tx_queue;
int rx_irq;
int tx_irq;
struct arglist *parsed;
void *transport_data; /* transport specific params if needed */
int max_packet;
int req_size; /* different from max packet - used for TSO */
int headroom;
int options;
/* remote address if any - some transports will leave this as null */
int header_size;
int rx_header_size;
int coalesce;
void *header_rxbuffer;
void *header_txbuffer;
int (*form_header)(uint8_t *header,
struct sk_buff *skb, struct vector_private *vp);
int (*verify_header)(uint8_t *header,
struct sk_buff *skb, struct vector_private *vp);
spinlock_t stats_lock;
bool rexmit_scheduled;
bool opened;
bool in_write_poll;
bool in_error;
/* guest allowed to use ethtool flash to load bpf */
bool bpf_via_flash;
/* ethtool stats */
struct vector_estats estats;
struct sock_fprog *bpf;
char user[];
};
extern int build_transport_data(struct vector_private *vp);
#endif
@@ -0,0 +1,494 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2017 - Cambridge Greys Limited
* Copyright (C) 2011 - 2014 Cisco Systems Inc
*/
#include <linux/etherdevice.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <asm/byteorder.h>
#include <uapi/linux/ip.h>
#include <uapi/linux/virtio_net.h>
#include <linux/virtio_net.h>
#include <linux/virtio_byteorder.h>
#include <linux/netdev_features.h>
#include "vector_user.h"
#include "vector_kern.h"
#define GOOD_LINEAR 512
#define GSO_ERROR "Incoming GSO frames and GRO disabled on the interface"
struct gre_minimal_header {
uint16_t header;
uint16_t arptype;
};
struct uml_gre_data {
uint32_t rx_key;
uint32_t tx_key;
uint32_t sequence;
bool ipv6;
bool has_sequence;
bool pin_sequence;
bool checksum;
bool key;
struct gre_minimal_header expected_header;
uint32_t checksum_offset;
uint32_t key_offset;
uint32_t sequence_offset;
};
struct uml_l2tpv3_data {
uint64_t rx_cookie;
uint64_t tx_cookie;
uint64_t rx_session;
uint64_t tx_session;
uint32_t counter;
bool udp;
bool ipv6;
bool has_counter;
bool pin_counter;
bool cookie;
bool cookie_is_64;
uint32_t cookie_offset;
uint32_t session_offset;
uint32_t counter_offset;
};
static int l2tpv3_form_header(uint8_t *header,
struct sk_buff *skb, struct vector_private *vp)
{
struct uml_l2tpv3_data *td = vp->transport_data;
uint32_t *counter;
if (td->udp)
*(uint32_t *) header = cpu_to_be32(L2TPV3_DATA_PACKET);
(*(uint32_t *) (header + td->session_offset)) = td->tx_session;
if (td->cookie) {
if (td->cookie_is_64)
(*(uint64_t *)(header + td->cookie_offset)) =
td->tx_cookie;
else
(*(uint32_t *)(header + td->cookie_offset)) =
td->tx_cookie;
}
if (td->has_counter) {
counter = (uint32_t *)(header + td->counter_offset);
if (td->pin_counter) {
*counter = 0;
} else {
td->counter++;
*counter = cpu_to_be32(td->counter);
}
}
return 0;
}
static int gre_form_header(uint8_t *header,
struct sk_buff *skb, struct vector_private *vp)
{
struct uml_gre_data *td = vp->transport_data;
uint32_t *sequence;
*((uint32_t *) header) = *((uint32_t *) &td->expected_header);
if (td->key)
(*(uint32_t *) (header + td->key_offset)) = td->tx_key;
if (td->has_sequence) {
sequence = (uint32_t *)(header + td->sequence_offset);
if (td->pin_sequence)
*sequence = 0;
else
*sequence = cpu_to_be32(++td->sequence);
}
return 0;
}
static int raw_form_header(uint8_t *header,
struct sk_buff *skb, struct vector_private *vp)
{
struct virtio_net_hdr *vheader = (struct virtio_net_hdr *) header;
virtio_net_hdr_from_skb(
skb,
vheader,
virtio_legacy_is_little_endian(),
false,
0
);
return 0;
}
static int l2tpv3_verify_header(
uint8_t *header, struct sk_buff *skb, struct vector_private *vp)
{
struct uml_l2tpv3_data *td = vp->transport_data;
uint32_t *session;
uint64_t cookie;
if ((!td->udp) && (!td->ipv6))
header += sizeof(struct iphdr) /* fix for ipv4 raw */;
/* we do not do a strict check for "data" packets as per
* the RFC spec because the pure IP spec does not have
* that anyway.
*/
if (td->cookie) {
if (td->cookie_is_64)
cookie = *(uint64_t *)(header + td->cookie_offset);
else
cookie = *(uint32_t *)(header + td->cookie_offset);
if (cookie != td->rx_cookie) {
if (net_ratelimit())
netdev_err(vp->dev, "uml_l2tpv3: unknown cookie id");
return -1;
}
}
session = (uint32_t *) (header + td->session_offset);
if (*session != td->rx_session) {
if (net_ratelimit())
netdev_err(vp->dev, "uml_l2tpv3: session mismatch");
return -1;
}
return 0;
}
static int gre_verify_header(
uint8_t *header, struct sk_buff *skb, struct vector_private *vp)
{
uint32_t key;
struct uml_gre_data *td = vp->transport_data;
if (!td->ipv6)
header += sizeof(struct iphdr) /* fix for ipv4 raw */;
if (*((uint32_t *) header) != *((uint32_t *) &td->expected_header)) {
if (net_ratelimit())
netdev_err(vp->dev, "header type disagreement, expecting %0x, got %0x",
*((uint32_t *) &td->expected_header),
*((uint32_t *) header)
);
return -1;
}
if (td->key) {
key = (*(uint32_t *)(header + td->key_offset));
if (key != td->rx_key) {
if (net_ratelimit())
netdev_err(vp->dev, "unknown key id %0x, expecting %0x",
key, td->rx_key);
return -1;
}
}
return 0;
}
static int raw_verify_header(
uint8_t *header, struct sk_buff *skb, struct vector_private *vp)
{
struct virtio_net_hdr *vheader = (struct virtio_net_hdr *) header;
if ((vheader->gso_type != VIRTIO_NET_HDR_GSO_NONE) &&
(vp->req_size != 65536)) {
if (net_ratelimit())
netdev_err(
vp->dev,
GSO_ERROR
);
}
if ((vheader->flags & VIRTIO_NET_HDR_F_DATA_VALID) > 0)
return 1;
virtio_net_hdr_to_skb(skb, vheader, virtio_legacy_is_little_endian());
return 0;
}
static bool get_uint_param(
struct arglist *def, char *param, unsigned int *result)
{
char *arg = uml_vector_fetch_arg(def, param);
if (arg != NULL) {
if (kstrtoint(arg, 0, result) == 0)
return true;
}
return false;
}
static bool get_ulong_param(
struct arglist *def, char *param, unsigned long *result)
{
char *arg = uml_vector_fetch_arg(def, param);
if (arg != NULL) {
if (kstrtoul(arg, 0, result) == 0)
return true;
return true;
}
return false;
}
static int build_gre_transport_data(struct vector_private *vp)
{
struct uml_gre_data *td;
int temp_int;
int temp_rx;
int temp_tx;
vp->transport_data = kmalloc_obj(struct uml_gre_data);
if (vp->transport_data == NULL)
return -ENOMEM;
td = vp->transport_data;
td->sequence = 0;
td->expected_header.arptype = GRE_IRB;
td->expected_header.header = 0;
vp->form_header = &gre_form_header;
vp->verify_header = &gre_verify_header;
vp->header_size = 4;
td->key_offset = 4;
td->sequence_offset = 4;
td->checksum_offset = 4;
td->ipv6 = false;
if (get_uint_param(vp->parsed, "v6", &temp_int)) {
if (temp_int > 0)
td->ipv6 = true;
}
td->key = false;
if (get_uint_param(vp->parsed, "rx_key", &temp_rx)) {
if (get_uint_param(vp->parsed, "tx_key", &temp_tx)) {
td->key = true;
td->expected_header.header |= GRE_MODE_KEY;
td->rx_key = cpu_to_be32(temp_rx);
td->tx_key = cpu_to_be32(temp_tx);
vp->header_size += 4;
td->sequence_offset += 4;
} else {
return -EINVAL;
}
}
td->sequence = false;
if (get_uint_param(vp->parsed, "sequence", &temp_int)) {
if (temp_int > 0) {
vp->header_size += 4;
td->has_sequence = true;
td->expected_header.header |= GRE_MODE_SEQUENCE;
if (get_uint_param(
vp->parsed, "pin_sequence", &temp_int)) {
if (temp_int > 0)
td->pin_sequence = true;
}
}
}
vp->rx_header_size = vp->header_size;
if (!td->ipv6)
vp->rx_header_size += sizeof(struct iphdr);
return 0;
}
static int build_l2tpv3_transport_data(struct vector_private *vp)
{
struct uml_l2tpv3_data *td;
int temp_int, temp_rxs, temp_txs;
unsigned long temp_rx;
unsigned long temp_tx;
vp->transport_data = kmalloc_obj(struct uml_l2tpv3_data);
if (vp->transport_data == NULL)
return -ENOMEM;
td = vp->transport_data;
vp->form_header = &l2tpv3_form_header;
vp->verify_header = &l2tpv3_verify_header;
td->counter = 0;
vp->header_size = 4;
td->session_offset = 0;
td->cookie_offset = 4;
td->counter_offset = 4;
td->ipv6 = false;
if (get_uint_param(vp->parsed, "v6", &temp_int)) {
if (temp_int > 0)
td->ipv6 = true;
}
if (get_uint_param(vp->parsed, "rx_session", &temp_rxs)) {
if (get_uint_param(vp->parsed, "tx_session", &temp_txs)) {
td->tx_session = cpu_to_be32(temp_txs);
td->rx_session = cpu_to_be32(temp_rxs);
} else {
return -EINVAL;
}
} else {
return -EINVAL;
}
td->cookie_is_64 = false;
if (get_uint_param(vp->parsed, "cookie64", &temp_int)) {
if (temp_int > 0)
td->cookie_is_64 = true;
}
td->cookie = false;
if (get_ulong_param(vp->parsed, "rx_cookie", &temp_rx)) {
if (get_ulong_param(vp->parsed, "tx_cookie", &temp_tx)) {
td->cookie = true;
if (td->cookie_is_64) {
td->rx_cookie = cpu_to_be64(temp_rx);
td->tx_cookie = cpu_to_be64(temp_tx);
vp->header_size += 8;
td->counter_offset += 8;
} else {
td->rx_cookie = cpu_to_be32(temp_rx);
td->tx_cookie = cpu_to_be32(temp_tx);
vp->header_size += 4;
td->counter_offset += 4;
}
} else {
return -EINVAL;
}
}
td->has_counter = false;
if (get_uint_param(vp->parsed, "counter", &temp_int)) {
if (temp_int > 0) {
td->has_counter = true;
vp->header_size += 4;
if (get_uint_param(
vp->parsed, "pin_counter", &temp_int)) {
if (temp_int > 0)
td->pin_counter = true;
}
}
}
if (get_uint_param(vp->parsed, "udp", &temp_int)) {
if (temp_int > 0) {
td->udp = true;
vp->header_size += 4;
td->counter_offset += 4;
td->session_offset += 4;
td->cookie_offset += 4;
}
}
vp->rx_header_size = vp->header_size;
if ((!td->ipv6) && (!td->udp))
vp->rx_header_size += sizeof(struct iphdr);
return 0;
}
static int build_raw_transport_data(struct vector_private *vp)
{
if (uml_raw_enable_vnet_headers(vp->fds->rx_fd)) {
if (!uml_raw_enable_vnet_headers(vp->fds->tx_fd))
return -1;
vp->form_header = &raw_form_header;
vp->verify_header = &raw_verify_header;
vp->header_size = sizeof(struct virtio_net_hdr);
vp->rx_header_size = sizeof(struct virtio_net_hdr);
vp->dev->hw_features |= (NETIF_F_TSO | NETIF_F_GRO);
vp->dev->features |=
(NETIF_F_RXCSUM | NETIF_F_HW_CSUM |
NETIF_F_TSO | NETIF_F_GRO);
netdev_info(
vp->dev,
"raw: using vnet headers for tso and tx/rx checksum"
);
}
return 0;
}
static int build_hybrid_transport_data(struct vector_private *vp)
{
if (uml_raw_enable_vnet_headers(vp->fds->rx_fd)) {
vp->form_header = &raw_form_header;
vp->verify_header = &raw_verify_header;
vp->header_size = sizeof(struct virtio_net_hdr);
vp->rx_header_size = sizeof(struct virtio_net_hdr);
vp->dev->hw_features |=
(NETIF_F_TSO | NETIF_F_GSO | NETIF_F_GRO);
vp->dev->features |=
(NETIF_F_RXCSUM | NETIF_F_HW_CSUM |
NETIF_F_TSO | NETIF_F_GSO | NETIF_F_GRO);
netdev_info(
vp->dev,
"tap/raw hybrid: using vnet headers for tso and tx/rx checksum"
);
} else {
return 0; /* do not try to enable tap too if raw failed */
}
if (uml_tap_enable_vnet_headers(vp->fds->tx_fd))
return 0;
return -1;
}
static int build_tap_transport_data(struct vector_private *vp)
{
/* "Pure" tap uses the same fd for rx and tx */
if (uml_tap_enable_vnet_headers(vp->fds->tx_fd)) {
vp->form_header = &raw_form_header;
vp->verify_header = &raw_verify_header;
vp->header_size = sizeof(struct virtio_net_hdr);
vp->rx_header_size = sizeof(struct virtio_net_hdr);
vp->dev->hw_features |=
(NETIF_F_TSO | NETIF_F_GSO | NETIF_F_GRO);
vp->dev->features |=
(NETIF_F_RXCSUM | NETIF_F_HW_CSUM |
NETIF_F_TSO | NETIF_F_GSO | NETIF_F_GRO);
netdev_info(
vp->dev,
"tap: using vnet headers for tso and tx/rx checksum"
);
return 0;
}
return -1;
}
static int build_bess_transport_data(struct vector_private *vp)
{
vp->form_header = NULL;
vp->verify_header = NULL;
vp->header_size = 0;
vp->rx_header_size = 0;
return 0;
}
int build_transport_data(struct vector_private *vp)
{
char *transport = uml_vector_fetch_arg(vp->parsed, "transport");
if (strncmp(transport, TRANS_GRE, TRANS_GRE_LEN) == 0)
return build_gre_transport_data(vp);
if (strncmp(transport, TRANS_L2TPV3, TRANS_L2TPV3_LEN) == 0)
return build_l2tpv3_transport_data(vp);
if (strncmp(transport, TRANS_RAW, TRANS_RAW_LEN) == 0)
return build_raw_transport_data(vp);
if (strncmp(transport, TRANS_TAP, TRANS_TAP_LEN) == 0)
return build_tap_transport_data(vp);
if (strncmp(transport, TRANS_HYBRID, TRANS_HYBRID_LEN) == 0)
return build_hybrid_transport_data(vp);
if (strncmp(transport, TRANS_BESS, TRANS_BESS_LEN) == 0)
return build_bess_transport_data(vp);
return 0;
}
+941
View File
@@ -0,0 +1,941 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
*/
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/if_tun.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/ip.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <sys/wait.h>
#include <sys/uio.h>
#include <linux/virtio_net.h>
#include <netdb.h>
#include <stdlib.h>
#include <os.h>
#include <limits.h>
#include <um_malloc.h>
#include "vector_user.h"
#define ID_GRE 0
#define ID_L2TPV3 1
#define ID_BESS 2
#define ID_MAX 2
#define TOKEN_IFNAME "ifname"
#define TOKEN_SCRIPT "ifup"
#define TRANS_RAW "raw"
#define TRANS_RAW_LEN strlen(TRANS_RAW)
#define TRANS_FD "fd"
#define TRANS_FD_LEN strlen(TRANS_FD)
#define TRANS_VDE "vde"
#define TRANS_VDE_LEN strlen(TRANS_VDE)
#define VNET_HDR_FAIL "could not enable vnet headers on fd %d"
#define TUN_GET_F_FAIL "tapraw: TUNGETFEATURES failed: %s"
#define L2TPV3_BIND_FAIL "l2tpv3_open : could not bind socket err=%i"
#define UNIX_BIND_FAIL "unix_open : could not bind socket err=%i"
#define BPF_ATTACH_FAIL "Failed to attach filter size %d prog %px to %d, err %d\n"
#define BPF_DETACH_FAIL "Failed to detach filter size %d prog %px to %d, err %d\n"
#define MAX_UN_LEN 107
static const char padchar[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
static const char *template = "tapXXXXXX";
/* This is very ugly and brute force lookup, but it is done
* only once at initialization so not worth doing hashes or
* anything more intelligent
*/
char *uml_vector_fetch_arg(struct arglist *ifspec, char *token)
{
int i;
for (i = 0; i < ifspec->numargs; i++) {
if (strcmp(ifspec->tokens[i], token) == 0)
return ifspec->values[i];
}
return NULL;
}
struct arglist *uml_parse_vector_ifspec(char *arg)
{
struct arglist *result;
int pos, len;
bool parsing_token = true, next_starts = true;
if (arg == NULL)
return NULL;
result = uml_kmalloc(sizeof(struct arglist), UM_GFP_KERNEL);
if (result == NULL)
return NULL;
result->numargs = 0;
len = strlen(arg);
for (pos = 0; pos < len; pos++) {
if (next_starts) {
if (parsing_token) {
result->tokens[result->numargs] = arg + pos;
} else {
result->values[result->numargs] = arg + pos;
result->numargs++;
}
next_starts = false;
}
if (*(arg + pos) == '=') {
if (parsing_token)
parsing_token = false;
else
goto cleanup;
next_starts = true;
(*(arg + pos)) = '\0';
}
if (*(arg + pos) == ',') {
parsing_token = true;
next_starts = true;
(*(arg + pos)) = '\0';
}
}
return result;
cleanup:
printk(UM_KERN_ERR "vector_setup - Couldn't parse '%s'\n", arg);
kfree(result);
return NULL;
}
/*
* Socket/FD configuration functions. These return an structure
* of rx and tx descriptors to cover cases where these are not
* the same (f.e. read via raw socket and write via tap).
*/
#define PATH_NET_TUN "/dev/net/tun"
static int create_tap_fd(char *iface)
{
struct ifreq ifr;
int fd = -1;
int err = -ENOMEM, offload;
fd = open(PATH_NET_TUN, O_RDWR);
if (fd < 0) {
printk(UM_KERN_ERR "uml_tap: failed to open tun device\n");
goto tap_fd_cleanup;
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR;
strscpy(ifr.ifr_name, iface);
err = ioctl(fd, TUNSETIFF, (void *) &ifr);
if (err != 0) {
printk(UM_KERN_ERR "uml_tap: failed to select tap interface\n");
goto tap_fd_cleanup;
}
offload = TUN_F_CSUM | TUN_F_TSO4 | TUN_F_TSO6;
ioctl(fd, TUNSETOFFLOAD, offload);
return fd;
tap_fd_cleanup:
if (fd >= 0)
os_close_file(fd);
return err;
}
static int create_raw_fd(char *iface, int flags, int proto)
{
struct ifreq ifr;
int fd = -1;
struct sockaddr_ll sock;
int err = -ENOMEM;
fd = socket(AF_PACKET, SOCK_RAW, flags);
if (fd == -1) {
err = -errno;
goto raw_fd_cleanup;
}
memset(&ifr, 0, sizeof(ifr));
strscpy(ifr.ifr_name, iface);
if (ioctl(fd, SIOCGIFINDEX, (void *) &ifr) < 0) {
err = -errno;
goto raw_fd_cleanup;
}
sock.sll_family = AF_PACKET;
sock.sll_protocol = htons(proto);
sock.sll_ifindex = ifr.ifr_ifindex;
if (bind(fd,
(struct sockaddr *) &sock, sizeof(struct sockaddr_ll)) < 0) {
err = -errno;
goto raw_fd_cleanup;
}
return fd;
raw_fd_cleanup:
printk(UM_KERN_ERR "user_init_raw: init failed, error %d", err);
if (fd >= 0)
os_close_file(fd);
return err;
}
static struct vector_fds *user_init_tap_fds(struct arglist *ifspec)
{
int fd = -1, i;
char *iface;
struct vector_fds *result = NULL;
bool dynamic = false;
char dynamic_ifname[IFNAMSIZ];
char *argv[] = {NULL, NULL, NULL, NULL};
iface = uml_vector_fetch_arg(ifspec, TOKEN_IFNAME);
if (iface == NULL) {
dynamic = true;
iface = dynamic_ifname;
srand(getpid());
}
result = uml_kmalloc(sizeof(struct vector_fds), UM_GFP_KERNEL);
if (result == NULL) {
printk(UM_KERN_ERR "uml_tap: failed to allocate file descriptors\n");
goto tap_cleanup;
}
result->rx_fd = -1;
result->tx_fd = -1;
result->remote_addr = NULL;
result->remote_addr_size = 0;
/* TAP */
do {
if (dynamic) {
strcpy(iface, template);
for (i = 0; i < strlen(iface); i++) {
if (iface[i] == 'X') {
iface[i] = padchar[rand() % strlen(padchar)];
}
}
}
fd = create_tap_fd(iface);
if ((fd < 0) && (!dynamic)) {
printk(UM_KERN_ERR "uml_tap: failed to create tun interface\n");
goto tap_cleanup;
}
result->tx_fd = fd;
result->rx_fd = fd;
} while (fd < 0);
argv[0] = uml_vector_fetch_arg(ifspec, TOKEN_SCRIPT);
if (argv[0]) {
argv[1] = iface;
run_helper(NULL, NULL, argv);
}
return result;
tap_cleanup:
printk(UM_KERN_ERR "user_init_tap: init failed, error %d", fd);
kfree(result);
return NULL;
}
static struct vector_fds *user_init_hybrid_fds(struct arglist *ifspec)
{
char *iface;
struct vector_fds *result = NULL;
char *argv[] = {NULL, NULL, NULL, NULL};
iface = uml_vector_fetch_arg(ifspec, TOKEN_IFNAME);
if (iface == NULL) {
printk(UM_KERN_ERR "uml_tap: failed to parse interface spec\n");
goto hybrid_cleanup;
}
result = uml_kmalloc(sizeof(struct vector_fds), UM_GFP_KERNEL);
if (result == NULL) {
printk(UM_KERN_ERR "uml_tap: failed to allocate file descriptors\n");
goto hybrid_cleanup;
}
result->rx_fd = -1;
result->tx_fd = -1;
result->remote_addr = NULL;
result->remote_addr_size = 0;
/* TAP */
result->tx_fd = create_tap_fd(iface);
if (result->tx_fd < 0) {
printk(UM_KERN_ERR "uml_tap: failed to create tun interface: %i\n", result->tx_fd);
goto hybrid_cleanup;
}
/* RAW */
result->rx_fd = create_raw_fd(iface, ETH_P_ALL, ETH_P_ALL);
if (result->rx_fd == -1) {
printk(UM_KERN_ERR
"uml_tap: failed to create paired raw socket: %i\n", result->rx_fd);
goto hybrid_cleanup;
}
argv[0] = uml_vector_fetch_arg(ifspec, TOKEN_SCRIPT);
if (argv[0]) {
argv[1] = iface;
run_helper(NULL, NULL, argv);
}
return result;
hybrid_cleanup:
printk(UM_KERN_ERR "user_init_hybrid: init failed");
kfree(result);
return NULL;
}
static struct vector_fds *user_init_unix_fds(struct arglist *ifspec, int id)
{
int fd = -1;
int socktype;
char *src, *dst;
struct vector_fds *result = NULL;
struct sockaddr_un *local_addr = NULL, *remote_addr = NULL;
src = uml_vector_fetch_arg(ifspec, "src");
dst = uml_vector_fetch_arg(ifspec, "dst");
result = uml_kmalloc(sizeof(struct vector_fds), UM_GFP_KERNEL);
if (result == NULL) {
printk(UM_KERN_ERR "unix open:cannot allocate remote addr");
goto unix_cleanup;
}
remote_addr = uml_kmalloc(sizeof(struct sockaddr_un), UM_GFP_KERNEL);
if (remote_addr == NULL) {
printk(UM_KERN_ERR "unix open:cannot allocate remote addr");
goto unix_cleanup;
}
switch (id) {
case ID_BESS:
socktype = SOCK_SEQPACKET;
if ((src != NULL) && (strlen(src) <= MAX_UN_LEN)) {
local_addr = uml_kmalloc(sizeof(struct sockaddr_un), UM_GFP_KERNEL);
if (local_addr == NULL) {
printk(UM_KERN_ERR "bess open:cannot allocate local addr");
goto unix_cleanup;
}
local_addr->sun_family = AF_UNIX;
memcpy(local_addr->sun_path, src, strlen(src) + 1);
}
if ((dst == NULL) || (strlen(dst) > MAX_UN_LEN))
goto unix_cleanup;
remote_addr->sun_family = AF_UNIX;
memcpy(remote_addr->sun_path, dst, strlen(dst) + 1);
break;
default:
printk(KERN_ERR "Unsupported unix socket type\n");
return NULL;
}
fd = socket(AF_UNIX, socktype, 0);
if (fd == -1) {
printk(UM_KERN_ERR
"unix open: could not open socket, error = %d",
-errno
);
goto unix_cleanup;
}
if (local_addr != NULL) {
if (bind(fd, (struct sockaddr *) local_addr, sizeof(struct sockaddr_un))) {
printk(UM_KERN_ERR UNIX_BIND_FAIL, errno);
goto unix_cleanup;
}
}
switch (id) {
case ID_BESS:
if (connect(fd, (const struct sockaddr *) remote_addr, sizeof(struct sockaddr_un)) < 0) {
printk(UM_KERN_ERR "bess open:cannot connect to %s %i", remote_addr->sun_path, -errno);
goto unix_cleanup;
}
break;
}
result->rx_fd = fd;
result->tx_fd = fd;
result->remote_addr_size = sizeof(struct sockaddr_un);
result->remote_addr = remote_addr;
return result;
unix_cleanup:
if (fd >= 0)
os_close_file(fd);
kfree(remote_addr);
kfree(result);
return NULL;
}
static int strtofd(const char *nptr)
{
long fd;
char *endptr;
if (nptr == NULL)
return -1;
errno = 0;
fd = strtol(nptr, &endptr, 10);
if (nptr == endptr ||
errno != 0 ||
*endptr != '\0' ||
fd < 0 ||
fd > INT_MAX) {
return -1;
}
return fd;
}
static struct vector_fds *user_init_fd_fds(struct arglist *ifspec)
{
int fd = -1;
char *fdarg = NULL;
struct vector_fds *result = NULL;
fdarg = uml_vector_fetch_arg(ifspec, "fd");
fd = strtofd(fdarg);
if (fd == -1) {
printk(UM_KERN_ERR "fd open: bad or missing fd argument");
goto fd_cleanup;
}
result = uml_kmalloc(sizeof(struct vector_fds), UM_GFP_KERNEL);
if (result == NULL) {
printk(UM_KERN_ERR "fd open: allocation failed");
goto fd_cleanup;
}
result->rx_fd = fd;
result->tx_fd = fd;
result->remote_addr_size = 0;
result->remote_addr = NULL;
return result;
fd_cleanup:
if (fd >= 0)
os_close_file(fd);
kfree(result);
return NULL;
}
/* enough char to store an int type */
#define ENOUGH(type) ((CHAR_BIT * sizeof(type) - 1) / 3 + 2)
#define ENOUGH_OCTAL(type) ((CHAR_BIT * sizeof(type) + 2) / 3)
/* vde_plug --descr xx --port2 xx --mod2 xx --group2 xx seqpacket://NN vnl (NULL) */
#define VDE_MAX_ARGC 12
#define VDE_SEQPACKET_HEAD "seqpacket://"
#define VDE_SEQPACKET_HEAD_LEN (sizeof(VDE_SEQPACKET_HEAD) - 1)
#define VDE_DEFAULT_DESCRIPTION "UML"
static struct vector_fds *user_init_vde_fds(struct arglist *ifspec)
{
char seqpacketvnl[VDE_SEQPACKET_HEAD_LEN + ENOUGH(int) + 1];
char *argv[VDE_MAX_ARGC] = {"vde_plug"};
int argc = 1;
int rv;
int sv[2];
struct vector_fds *result = NULL;
char *vnl = uml_vector_fetch_arg(ifspec,"vnl");
char *descr = uml_vector_fetch_arg(ifspec,"descr");
char *port = uml_vector_fetch_arg(ifspec,"port");
char *mode = uml_vector_fetch_arg(ifspec,"mode");
char *group = uml_vector_fetch_arg(ifspec,"group");
if (descr == NULL) descr = VDE_DEFAULT_DESCRIPTION;
argv[argc++] = "--descr";
argv[argc++] = descr;
if (port != NULL) {
argv[argc++] = "--port2";
argv[argc++] = port;
}
if (mode != NULL) {
argv[argc++] = "--mod2";
argv[argc++] = mode;
}
if (group != NULL) {
argv[argc++] = "--group2";
argv[argc++] = group;
}
argv[argc++] = seqpacketvnl;
argv[argc++] = vnl;
argv[argc++] = NULL;
rv = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sv);
if (rv < 0) {
printk(UM_KERN_ERR "vde: seqpacket socketpair err %d", -errno);
return NULL;
}
rv = os_set_exec_close(sv[0]);
if (rv < 0) {
printk(UM_KERN_ERR "vde: seqpacket socketpair cloexec err %d", -errno);
goto vde_cleanup_sv;
}
snprintf(seqpacketvnl, sizeof(seqpacketvnl), VDE_SEQPACKET_HEAD "%d", sv[1]);
run_helper(NULL, NULL, argv);
close(sv[1]);
result = uml_kmalloc(sizeof(struct vector_fds), UM_GFP_KERNEL);
if (result == NULL) {
printk(UM_KERN_ERR "fd open: allocation failed");
goto vde_cleanup;
}
result->rx_fd = sv[0];
result->tx_fd = sv[0];
result->remote_addr_size = 0;
result->remote_addr = NULL;
return result;
vde_cleanup_sv:
close(sv[1]);
vde_cleanup:
close(sv[0]);
return NULL;
}
static struct vector_fds *user_init_raw_fds(struct arglist *ifspec)
{
int rxfd = -1, txfd = -1;
int err = -ENOMEM;
char *iface;
struct vector_fds *result = NULL;
char *argv[] = {NULL, NULL, NULL, NULL};
iface = uml_vector_fetch_arg(ifspec, TOKEN_IFNAME);
if (iface == NULL)
goto raw_cleanup;
rxfd = create_raw_fd(iface, ETH_P_ALL, ETH_P_ALL);
if (rxfd == -1) {
err = -errno;
goto raw_cleanup;
}
txfd = create_raw_fd(iface, 0, ETH_P_IP); /* Turn off RX on this fd */
if (txfd == -1) {
err = -errno;
goto raw_cleanup;
}
result = uml_kmalloc(sizeof(struct vector_fds), UM_GFP_KERNEL);
if (result != NULL) {
result->rx_fd = rxfd;
result->tx_fd = txfd;
result->remote_addr = NULL;
result->remote_addr_size = 0;
}
argv[0] = uml_vector_fetch_arg(ifspec, TOKEN_SCRIPT);
if (argv[0]) {
argv[1] = iface;
run_helper(NULL, NULL, argv);
}
return result;
raw_cleanup:
printk(UM_KERN_ERR "user_init_raw: init failed, error %d", err);
kfree(result);
return NULL;
}
bool uml_raw_enable_qdisc_bypass(int fd)
{
int optval = 1;
if (setsockopt(fd,
SOL_PACKET, PACKET_QDISC_BYPASS,
&optval, sizeof(optval)) != 0) {
return false;
}
return true;
}
bool uml_raw_enable_vnet_headers(int fd)
{
int optval = 1;
if (setsockopt(fd,
SOL_PACKET, PACKET_VNET_HDR,
&optval, sizeof(optval)) != 0) {
printk(UM_KERN_INFO VNET_HDR_FAIL, fd);
return false;
}
return true;
}
bool uml_tap_enable_vnet_headers(int fd)
{
unsigned int features;
int len = sizeof(struct virtio_net_hdr);
if (ioctl(fd, TUNGETFEATURES, &features) == -1) {
printk(UM_KERN_INFO TUN_GET_F_FAIL, strerror(errno));
return false;
}
if ((features & IFF_VNET_HDR) == 0) {
printk(UM_KERN_INFO "tapraw: No VNET HEADER support");
return false;
}
ioctl(fd, TUNSETVNETHDRSZ, &len);
return true;
}
static struct vector_fds *user_init_socket_fds(struct arglist *ifspec, int id)
{
int err = -ENOMEM;
int fd = -1, gairet;
struct addrinfo srchints;
struct addrinfo dsthints;
bool v6, udp;
char *value;
char *src, *dst, *srcport, *dstport;
struct addrinfo *gairesult = NULL;
struct vector_fds *result = NULL;
value = uml_vector_fetch_arg(ifspec, "v6");
v6 = false;
udp = false;
if (value != NULL) {
if (strtol((const char *) value, NULL, 10) > 0)
v6 = true;
}
value = uml_vector_fetch_arg(ifspec, "udp");
if (value != NULL) {
if (strtol((const char *) value, NULL, 10) > 0)
udp = true;
}
src = uml_vector_fetch_arg(ifspec, "src");
dst = uml_vector_fetch_arg(ifspec, "dst");
srcport = uml_vector_fetch_arg(ifspec, "srcport");
dstport = uml_vector_fetch_arg(ifspec, "dstport");
memset(&dsthints, 0, sizeof(dsthints));
if (v6)
dsthints.ai_family = AF_INET6;
else
dsthints.ai_family = AF_INET;
switch (id) {
case ID_GRE:
dsthints.ai_socktype = SOCK_RAW;
dsthints.ai_protocol = IPPROTO_GRE;
break;
case ID_L2TPV3:
if (udp) {
dsthints.ai_socktype = SOCK_DGRAM;
dsthints.ai_protocol = 0;
} else {
dsthints.ai_socktype = SOCK_RAW;
dsthints.ai_protocol = IPPROTO_L2TP;
}
break;
default:
printk(KERN_ERR "Unsupported socket type\n");
return NULL;
}
memcpy(&srchints, &dsthints, sizeof(struct addrinfo));
gairet = getaddrinfo(src, srcport, &dsthints, &gairesult);
if ((gairet != 0) || (gairesult == NULL)) {
printk(UM_KERN_ERR
"socket_open : could not resolve src, error = %s",
gai_strerror(gairet)
);
return NULL;
}
fd = socket(gairesult->ai_family,
gairesult->ai_socktype, gairesult->ai_protocol);
if (fd == -1) {
printk(UM_KERN_ERR
"socket_open : could not open socket, error = %d",
-errno
);
goto cleanup;
}
if (bind(fd,
(struct sockaddr *) gairesult->ai_addr,
gairesult->ai_addrlen)) {
printk(UM_KERN_ERR L2TPV3_BIND_FAIL, errno);
goto cleanup;
}
if (gairesult != NULL)
freeaddrinfo(gairesult);
gairesult = NULL;
gairet = getaddrinfo(dst, dstport, &dsthints, &gairesult);
if ((gairet != 0) || (gairesult == NULL)) {
printk(UM_KERN_ERR
"socket_open : could not resolve dst, error = %s",
gai_strerror(gairet)
);
return NULL;
}
result = uml_kmalloc(sizeof(struct vector_fds), UM_GFP_KERNEL);
if (result != NULL) {
result->rx_fd = fd;
result->tx_fd = fd;
result->remote_addr = uml_kmalloc(
gairesult->ai_addrlen, UM_GFP_KERNEL);
if (result->remote_addr == NULL)
goto cleanup;
result->remote_addr_size = gairesult->ai_addrlen;
memcpy(
result->remote_addr,
gairesult->ai_addr,
gairesult->ai_addrlen
);
}
freeaddrinfo(gairesult);
return result;
cleanup:
if (gairesult != NULL)
freeaddrinfo(gairesult);
printk(UM_KERN_ERR "user_init_socket: init failed, error %d", err);
if (fd >= 0)
os_close_file(fd);
if (result != NULL) {
kfree(result->remote_addr);
kfree(result);
}
return NULL;
}
struct vector_fds *uml_vector_user_open(
int unit,
struct arglist *parsed
)
{
char *transport;
if (parsed == NULL) {
printk(UM_KERN_ERR "no parsed config for unit %d\n", unit);
return NULL;
}
transport = uml_vector_fetch_arg(parsed, "transport");
if (transport == NULL) {
printk(UM_KERN_ERR "missing transport for unit %d\n", unit);
return NULL;
}
if (strncmp(transport, TRANS_RAW, TRANS_RAW_LEN) == 0)
return user_init_raw_fds(parsed);
if (strncmp(transport, TRANS_HYBRID, TRANS_HYBRID_LEN) == 0)
return user_init_hybrid_fds(parsed);
if (strncmp(transport, TRANS_TAP, TRANS_TAP_LEN) == 0)
return user_init_tap_fds(parsed);
if (strncmp(transport, TRANS_GRE, TRANS_GRE_LEN) == 0)
return user_init_socket_fds(parsed, ID_GRE);
if (strncmp(transport, TRANS_L2TPV3, TRANS_L2TPV3_LEN) == 0)
return user_init_socket_fds(parsed, ID_L2TPV3);
if (strncmp(transport, TRANS_BESS, TRANS_BESS_LEN) == 0)
return user_init_unix_fds(parsed, ID_BESS);
if (strncmp(transport, TRANS_FD, TRANS_FD_LEN) == 0)
return user_init_fd_fds(parsed);
if (strncmp(transport, TRANS_VDE, TRANS_VDE_LEN) == 0)
return user_init_vde_fds(parsed);
return NULL;
}
int uml_vector_sendmsg(int fd, void *hdr, int flags)
{
int n;
CATCH_EINTR(n = sendmsg(fd, (struct msghdr *) hdr, flags));
if ((n < 0) && (errno == EAGAIN))
return 0;
if (n >= 0)
return n;
else
return -errno;
}
int uml_vector_recvmsg(int fd, void *hdr, int flags)
{
int n;
struct msghdr *msg = (struct msghdr *) hdr;
CATCH_EINTR(n = readv(fd, msg->msg_iov, msg->msg_iovlen));
if ((n < 0) && (errno == EAGAIN))
return 0;
if (n >= 0)
return n;
else
return -errno;
}
int uml_vector_writev(int fd, void *hdr, int iovcount)
{
int n;
CATCH_EINTR(n = writev(fd, (struct iovec *) hdr, iovcount));
if ((n < 0) && ((errno == EAGAIN) || (errno == ENOBUFS)))
return 0;
if (n >= 0)
return n;
else
return -errno;
}
int uml_vector_sendmmsg(
int fd,
void *msgvec,
unsigned int vlen,
unsigned int flags)
{
int n;
CATCH_EINTR(n = sendmmsg(fd, (struct mmsghdr *) msgvec, vlen, flags));
if ((n < 0) && ((errno == EAGAIN) || (errno == ENOBUFS)))
return 0;
if (n >= 0)
return n;
else
return -errno;
}
int uml_vector_recvmmsg(
int fd,
void *msgvec,
unsigned int vlen,
unsigned int flags)
{
int n;
CATCH_EINTR(
n = recvmmsg(fd, (struct mmsghdr *) msgvec, vlen, flags, 0));
if ((n < 0) && (errno == EAGAIN))
return 0;
if (n >= 0)
return n;
else
return -errno;
}
int uml_vector_attach_bpf(int fd, void *bpf)
{
struct sock_fprog *prog = bpf;
int err = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, bpf, sizeof(struct sock_fprog));
if (err < 0)
printk(KERN_ERR BPF_ATTACH_FAIL, prog->len, prog->filter, fd, -errno);
return err;
}
int uml_vector_detach_bpf(int fd, void *bpf)
{
struct sock_fprog *prog = bpf;
int err = setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, bpf, sizeof(struct sock_fprog));
if (err < 0)
printk(KERN_ERR BPF_DETACH_FAIL, prog->len, prog->filter, fd, -errno);
return err;
}
void *uml_vector_default_bpf(const void *mac)
{
struct sock_filter *bpf;
uint32_t *mac1 = (uint32_t *)(mac + 2);
uint16_t *mac2 = (uint16_t *) mac;
struct sock_fprog *bpf_prog;
bpf_prog = uml_kmalloc(sizeof(struct sock_fprog), UM_GFP_KERNEL);
if (bpf_prog) {
bpf_prog->len = DEFAULT_BPF_LEN;
bpf_prog->filter = NULL;
} else {
return NULL;
}
bpf = uml_kmalloc(
sizeof(struct sock_filter) * DEFAULT_BPF_LEN, UM_GFP_KERNEL);
if (bpf) {
bpf_prog->filter = bpf;
/* ld [8] */
bpf[0] = (struct sock_filter){ 0x20, 0, 0, 0x00000008 };
/* jeq #0xMAC[2-6] jt 2 jf 5*/
bpf[1] = (struct sock_filter){ 0x15, 0, 3, ntohl(*mac1)};
/* ldh [6] */
bpf[2] = (struct sock_filter){ 0x28, 0, 0, 0x00000006 };
/* jeq #0xMAC[0-1] jt 4 jf 5 */
bpf[3] = (struct sock_filter){ 0x15, 0, 1, ntohs(*mac2)};
/* ret #0 */
bpf[4] = (struct sock_filter){ 0x6, 0, 0, 0x00000000 };
/* ret #0x40000 */
bpf[5] = (struct sock_filter){ 0x6, 0, 0, 0x00040000 };
} else {
kfree(bpf_prog);
bpf_prog = NULL;
}
return bpf_prog;
}
/* Note - this function requires a valid mac being passed as an arg */
void *uml_vector_user_bpf(char *filename)
{
struct sock_filter *bpf;
struct sock_fprog *bpf_prog;
struct stat statbuf;
int res, ffd = -1;
if (filename == NULL)
return NULL;
if (stat(filename, &statbuf) < 0) {
printk(KERN_ERR "Error %d reading bpf file", -errno);
return false;
}
bpf_prog = uml_kmalloc(sizeof(struct sock_fprog), UM_GFP_KERNEL);
if (bpf_prog == NULL) {
printk(KERN_ERR "Failed to allocate bpf prog buffer");
return NULL;
}
bpf_prog->len = statbuf.st_size / sizeof(struct sock_filter);
bpf_prog->filter = NULL;
ffd = os_open_file(filename, of_read(OPENFLAGS()), 0);
if (ffd < 0) {
printk(KERN_ERR "Error %d opening bpf file", -errno);
goto bpf_failed;
}
bpf = uml_kmalloc(statbuf.st_size, UM_GFP_KERNEL);
if (bpf == NULL) {
printk(KERN_ERR "Failed to allocate bpf buffer");
goto bpf_failed;
}
bpf_prog->filter = bpf;
res = os_read_file(ffd, bpf, statbuf.st_size);
if (res < statbuf.st_size) {
printk(KERN_ERR "Failed to read bpf program %s, error %d", filename, res);
kfree(bpf);
goto bpf_failed;
}
os_close_file(ffd);
return bpf_prog;
bpf_failed:
if (ffd > 0)
os_close_file(ffd);
kfree(bpf_prog);
return NULL;
}
+107
View File
@@ -0,0 +1,107 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2002 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
*/
#ifndef __UM_VECTOR_USER_H
#define __UM_VECTOR_USER_H
#define MAXVARGS 20
#define TOKEN_IFNAME "ifname"
#define TRANS_RAW "raw"
#define TRANS_RAW_LEN strlen(TRANS_RAW)
#define TRANS_TAP "tap"
#define TRANS_TAP_LEN strlen(TRANS_TAP)
#define TRANS_GRE "gre"
#define TRANS_GRE_LEN strlen(TRANS_GRE)
#define TRANS_L2TPV3 "l2tpv3"
#define TRANS_L2TPV3_LEN strlen(TRANS_L2TPV3)
#define TRANS_HYBRID "hybrid"
#define TRANS_HYBRID_LEN strlen(TRANS_HYBRID)
#define TRANS_BESS "bess"
#define TRANS_BESS_LEN strlen(TRANS_BESS)
#define DEFAULT_BPF_LEN 6
#ifndef IPPROTO_GRE
#define IPPROTO_GRE 0x2F
#endif
#define GRE_MODE_CHECKSUM cpu_to_be16(8 << 12) /* checksum */
#define GRE_MODE_RESERVED cpu_to_be16(4 << 12) /* unused */
#define GRE_MODE_KEY cpu_to_be16(2 << 12) /* KEY present */
#define GRE_MODE_SEQUENCE cpu_to_be16(1 << 12) /* sequence */
#define GRE_IRB cpu_to_be16(0x6558)
#define L2TPV3_DATA_PACKET 0x30000
/* IANA-assigned IP protocol ID for L2TPv3 */
#ifndef IPPROTO_L2TP
#define IPPROTO_L2TP 0x73
#endif
struct arglist {
int numargs;
char *tokens[MAXVARGS];
char *values[MAXVARGS];
};
/* Separating read and write FDs allows us to have different
* rx and tx method. Example - read tap via raw socket using
* recvmmsg, write using legacy tap write calls
*/
struct vector_fds {
int rx_fd;
int tx_fd;
void *remote_addr;
int remote_addr_size;
};
#define VECTOR_READ 1
extern struct arglist *uml_parse_vector_ifspec(char *arg);
extern struct vector_fds *uml_vector_user_open(
int unit,
struct arglist *parsed
);
extern char *uml_vector_fetch_arg(
struct arglist *ifspec,
char *token
);
extern int uml_vector_recvmsg(int fd, void *hdr, int flags);
extern int uml_vector_sendmsg(int fd, void *hdr, int flags);
extern int uml_vector_writev(int fd, void *hdr, int iovcount);
extern int uml_vector_sendmmsg(
int fd, void *msgvec,
unsigned int vlen,
unsigned int flags
);
extern int uml_vector_recvmmsg(
int fd,
void *msgvec,
unsigned int vlen,
unsigned int flags
);
extern void *uml_vector_default_bpf(const void *mac);
extern void *uml_vector_user_bpf(char *filename);
extern int uml_vector_attach_bpf(int fd, void *bpf);
extern int uml_vector_detach_bpf(int fd, void *bpf);
extern bool uml_raw_enable_qdisc_bypass(int fd);
extern bool uml_raw_enable_vnet_headers(int fd);
extern bool uml_tap_enable_vnet_headers(int fd);
#endif
+707
View File
@@ -0,0 +1,707 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2025 Ant Group
* Author: Tiwei Bie <tiwei.btw@antgroup.com>
*/
#define pr_fmt(fmt) "vfio-uml: " fmt
#include <linux/module.h>
#include <linux/logic_iomem.h>
#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/string.h>
#include <linux/unaligned.h>
#include <irq_kern.h>
#include <init.h>
#include <os.h>
#include "mconsole_kern.h"
#include "virt-pci.h"
#include "vfio_user.h"
#define to_vdev(_pdev) container_of(_pdev, struct uml_vfio_device, pdev)
struct uml_vfio_intr_ctx {
struct uml_vfio_device *dev;
int irq;
};
struct uml_vfio_device {
const char *name;
int group;
struct um_pci_device pdev;
struct uml_vfio_user_device udev;
struct uml_vfio_intr_ctx *intr_ctx;
int msix_cap;
int msix_bar;
int msix_offset;
int msix_size;
u32 *msix_data;
struct list_head list;
};
struct uml_vfio_group {
int id;
int fd;
int users;
struct list_head list;
};
static struct {
int fd;
int users;
} uml_vfio_container = { .fd = -1 };
static DEFINE_MUTEX(uml_vfio_container_mtx);
static LIST_HEAD(uml_vfio_groups);
static DEFINE_MUTEX(uml_vfio_groups_mtx);
static LIST_HEAD(uml_vfio_devices);
static DEFINE_MUTEX(uml_vfio_devices_mtx);
static int uml_vfio_set_container(int group_fd)
{
int err;
guard(mutex)(&uml_vfio_container_mtx);
err = uml_vfio_user_set_container(uml_vfio_container.fd, group_fd);
if (err)
return err;
uml_vfio_container.users++;
if (uml_vfio_container.users > 1)
return 0;
err = uml_vfio_user_setup_iommu(uml_vfio_container.fd);
if (err) {
uml_vfio_user_unset_container(uml_vfio_container.fd, group_fd);
uml_vfio_container.users--;
}
return err;
}
static void uml_vfio_unset_container(int group_fd)
{
guard(mutex)(&uml_vfio_container_mtx);
uml_vfio_user_unset_container(uml_vfio_container.fd, group_fd);
uml_vfio_container.users--;
}
static int uml_vfio_open_group(int group_id)
{
struct uml_vfio_group *group;
int err;
guard(mutex)(&uml_vfio_groups_mtx);
list_for_each_entry(group, &uml_vfio_groups, list) {
if (group->id == group_id) {
group->users++;
return group->fd;
}
}
group = kzalloc_obj(*group);
if (!group)
return -ENOMEM;
group->fd = uml_vfio_user_open_group(group_id);
if (group->fd < 0) {
err = group->fd;
goto free_group;
}
err = uml_vfio_set_container(group->fd);
if (err)
goto close_group;
group->id = group_id;
group->users = 1;
list_add(&group->list, &uml_vfio_groups);
return group->fd;
close_group:
os_close_file(group->fd);
free_group:
kfree(group);
return err;
}
static int uml_vfio_release_group(int group_fd)
{
struct uml_vfio_group *group;
guard(mutex)(&uml_vfio_groups_mtx);
list_for_each_entry(group, &uml_vfio_groups, list) {
if (group->fd == group_fd) {
group->users--;
if (group->users == 0) {
uml_vfio_unset_container(group_fd);
os_close_file(group_fd);
list_del(&group->list);
kfree(group);
}
return 0;
}
}
return -ENOENT;
}
static irqreturn_t uml_vfio_interrupt(int unused, void *opaque)
{
struct uml_vfio_intr_ctx *ctx = opaque;
struct uml_vfio_device *dev = ctx->dev;
int index = ctx - dev->intr_ctx;
int irqfd = dev->udev.irqfd[index];
int irq = dev->msix_data[index];
uint64_t v;
int r;
do {
r = os_read_file(irqfd, &v, sizeof(v));
if (r == sizeof(v))
generic_handle_irq(irq);
} while (r == sizeof(v) || r == -EINTR);
WARN(r != -EAGAIN, "read returned %d\n", r);
return IRQ_HANDLED;
}
static int uml_vfio_activate_irq(struct uml_vfio_device *dev, int index)
{
struct uml_vfio_intr_ctx *ctx = &dev->intr_ctx[index];
int err, irqfd;
if (ctx->irq >= 0)
return 0;
irqfd = uml_vfio_user_activate_irq(&dev->udev, index);
if (irqfd < 0)
return irqfd;
ctx->irq = um_request_irq(UM_IRQ_ALLOC, irqfd, IRQ_READ,
uml_vfio_interrupt, 0,
"vfio-uml", ctx);
if (ctx->irq < 0) {
err = ctx->irq;
goto deactivate;
}
err = add_sigio_fd(irqfd);
if (err)
goto free_irq;
return 0;
free_irq:
um_free_irq(ctx->irq, ctx);
ctx->irq = -1;
deactivate:
uml_vfio_user_deactivate_irq(&dev->udev, index);
return err;
}
static int uml_vfio_deactivate_irq(struct uml_vfio_device *dev, int index)
{
struct uml_vfio_intr_ctx *ctx = &dev->intr_ctx[index];
if (ctx->irq >= 0) {
ignore_sigio_fd(dev->udev.irqfd[index]);
um_free_irq(ctx->irq, ctx);
uml_vfio_user_deactivate_irq(&dev->udev, index);
ctx->irq = -1;
}
return 0;
}
static int uml_vfio_update_msix_cap(struct uml_vfio_device *dev,
unsigned int offset, int size,
unsigned long val)
{
/*
* Here, we handle only the operations we care about,
* ignoring the rest.
*/
if (size == 2 && offset == dev->msix_cap + PCI_MSIX_FLAGS) {
switch (val & ~PCI_MSIX_FLAGS_QSIZE) {
case PCI_MSIX_FLAGS_ENABLE:
case 0:
return uml_vfio_user_update_irqs(&dev->udev);
}
}
return 0;
}
static int uml_vfio_update_msix_table(struct uml_vfio_device *dev,
unsigned int offset, int size,
unsigned long val)
{
int index;
/*
* Here, we handle only the operations we care about,
* ignoring the rest.
*/
offset -= dev->msix_offset + PCI_MSIX_ENTRY_DATA;
if (size != 4 || offset % PCI_MSIX_ENTRY_SIZE != 0)
return 0;
index = offset / PCI_MSIX_ENTRY_SIZE;
if (index >= dev->udev.irq_count)
return -EINVAL;
dev->msix_data[index] = val;
return val ? uml_vfio_activate_irq(dev, index) :
uml_vfio_deactivate_irq(dev, index);
}
static unsigned long __uml_vfio_cfgspace_read(struct uml_vfio_device *dev,
unsigned int offset, int size)
{
u8 data[8];
memset(data, 0xff, sizeof(data));
if (uml_vfio_user_cfgspace_read(&dev->udev, offset, data, size))
return ULONG_MAX;
switch (size) {
case 1:
return data[0];
case 2:
return le16_to_cpup((void *)data);
case 4:
return le32_to_cpup((void *)data);
#ifdef CONFIG_64BIT
case 8:
return le64_to_cpup((void *)data);
#endif
default:
return ULONG_MAX;
}
}
static unsigned long uml_vfio_cfgspace_read(struct um_pci_device *pdev,
unsigned int offset, int size)
{
struct uml_vfio_device *dev = to_vdev(pdev);
return __uml_vfio_cfgspace_read(dev, offset, size);
}
static void __uml_vfio_cfgspace_write(struct uml_vfio_device *dev,
unsigned int offset, int size,
unsigned long val)
{
u8 data[8];
switch (size) {
case 1:
data[0] = (u8)val;
break;
case 2:
put_unaligned_le16(val, (void *)data);
break;
case 4:
put_unaligned_le32(val, (void *)data);
break;
#ifdef CONFIG_64BIT
case 8:
put_unaligned_le64(val, (void *)data);
break;
#endif
}
WARN_ON(uml_vfio_user_cfgspace_write(&dev->udev, offset, data, size));
}
static void uml_vfio_cfgspace_write(struct um_pci_device *pdev,
unsigned int offset, int size,
unsigned long val)
{
struct uml_vfio_device *dev = to_vdev(pdev);
if (offset < dev->msix_cap + PCI_CAP_MSIX_SIZEOF &&
offset + size > dev->msix_cap)
WARN_ON(uml_vfio_update_msix_cap(dev, offset, size, val));
__uml_vfio_cfgspace_write(dev, offset, size, val);
}
static void uml_vfio_bar_copy_from(struct um_pci_device *pdev, int bar,
void *buffer, unsigned int offset, int size)
{
struct uml_vfio_device *dev = to_vdev(pdev);
memset(buffer, 0xff, size);
uml_vfio_user_bar_read(&dev->udev, bar, offset, buffer, size);
}
static unsigned long uml_vfio_bar_read(struct um_pci_device *pdev, int bar,
unsigned int offset, int size)
{
u8 data[8];
uml_vfio_bar_copy_from(pdev, bar, data, offset, size);
switch (size) {
case 1:
return data[0];
case 2:
return le16_to_cpup((void *)data);
case 4:
return le32_to_cpup((void *)data);
#ifdef CONFIG_64BIT
case 8:
return le64_to_cpup((void *)data);
#endif
default:
return ULONG_MAX;
}
}
static void uml_vfio_bar_copy_to(struct um_pci_device *pdev, int bar,
unsigned int offset, const void *buffer,
int size)
{
struct uml_vfio_device *dev = to_vdev(pdev);
uml_vfio_user_bar_write(&dev->udev, bar, offset, buffer, size);
}
static void uml_vfio_bar_write(struct um_pci_device *pdev, int bar,
unsigned int offset, int size,
unsigned long val)
{
struct uml_vfio_device *dev = to_vdev(pdev);
u8 data[8];
if (bar == dev->msix_bar && offset + size > dev->msix_offset &&
offset < dev->msix_offset + dev->msix_size)
WARN_ON(uml_vfio_update_msix_table(dev, offset, size, val));
switch (size) {
case 1:
data[0] = (u8)val;
break;
case 2:
put_unaligned_le16(val, (void *)data);
break;
case 4:
put_unaligned_le32(val, (void *)data);
break;
#ifdef CONFIG_64BIT
case 8:
put_unaligned_le64(val, (void *)data);
break;
#endif
}
uml_vfio_bar_copy_to(pdev, bar, offset, data, size);
}
static void uml_vfio_bar_set(struct um_pci_device *pdev, int bar,
unsigned int offset, u8 value, int size)
{
struct uml_vfio_device *dev = to_vdev(pdev);
int i;
for (i = 0; i < size; i++)
uml_vfio_user_bar_write(&dev->udev, bar, offset + i, &value, 1);
}
static const struct um_pci_ops uml_vfio_um_pci_ops = {
.cfgspace_read = uml_vfio_cfgspace_read,
.cfgspace_write = uml_vfio_cfgspace_write,
.bar_read = uml_vfio_bar_read,
.bar_write = uml_vfio_bar_write,
.bar_copy_from = uml_vfio_bar_copy_from,
.bar_copy_to = uml_vfio_bar_copy_to,
.bar_set = uml_vfio_bar_set,
};
static u8 uml_vfio_find_capability(struct uml_vfio_device *dev, u8 cap)
{
u8 id, pos;
u16 ent;
int ttl = 48; /* PCI_FIND_CAP_TTL */
pos = __uml_vfio_cfgspace_read(dev, PCI_CAPABILITY_LIST, sizeof(pos));
while (pos && ttl--) {
ent = __uml_vfio_cfgspace_read(dev, pos, sizeof(ent));
id = ent & 0xff;
if (id == 0xff)
break;
if (id == cap)
return pos;
pos = ent >> 8;
}
return 0;
}
static int uml_vfio_read_msix_table(struct uml_vfio_device *dev)
{
unsigned int off;
u16 flags;
u32 tbl;
off = uml_vfio_find_capability(dev, PCI_CAP_ID_MSIX);
if (!off)
return -ENOTSUPP;
dev->msix_cap = off;
tbl = __uml_vfio_cfgspace_read(dev, off + PCI_MSIX_TABLE, sizeof(tbl));
flags = __uml_vfio_cfgspace_read(dev, off + PCI_MSIX_FLAGS, sizeof(flags));
dev->msix_bar = tbl & PCI_MSIX_TABLE_BIR;
dev->msix_offset = tbl & PCI_MSIX_TABLE_OFFSET;
dev->msix_size = ((flags & PCI_MSIX_FLAGS_QSIZE) + 1) * PCI_MSIX_ENTRY_SIZE;
dev->msix_data = kzalloc(dev->msix_size, GFP_KERNEL);
if (!dev->msix_data)
return -ENOMEM;
return 0;
}
static void uml_vfio_open_device(struct uml_vfio_device *dev)
{
struct uml_vfio_intr_ctx *ctx;
int err, group_id, i;
group_id = uml_vfio_user_get_group_id(dev->name);
if (group_id < 0) {
pr_err("Failed to get group id (%s), error %d\n",
dev->name, group_id);
goto free_dev;
}
dev->group = uml_vfio_open_group(group_id);
if (dev->group < 0) {
pr_err("Failed to open group %d (%s), error %d\n",
group_id, dev->name, dev->group);
goto free_dev;
}
err = uml_vfio_user_setup_device(&dev->udev, dev->group, dev->name);
if (err) {
pr_err("Failed to setup device (%s), error %d\n",
dev->name, err);
goto release_group;
}
err = uml_vfio_read_msix_table(dev);
if (err) {
pr_err("Failed to read MSI-X table (%s), error %d\n",
dev->name, err);
goto teardown_udev;
}
dev->intr_ctx = kmalloc_objs(struct uml_vfio_intr_ctx,
dev->udev.irq_count);
if (!dev->intr_ctx) {
pr_err("Failed to allocate interrupt context (%s)\n",
dev->name);
goto free_msix;
}
for (i = 0; i < dev->udev.irq_count; i++) {
ctx = &dev->intr_ctx[i];
ctx->dev = dev;
ctx->irq = -1;
}
dev->pdev.ops = &uml_vfio_um_pci_ops;
err = um_pci_device_register(&dev->pdev);
if (err) {
pr_err("Failed to register UM PCI device (%s), error %d\n",
dev->name, err);
goto free_intr_ctx;
}
return;
free_intr_ctx:
kfree(dev->intr_ctx);
free_msix:
kfree(dev->msix_data);
teardown_udev:
uml_vfio_user_teardown_device(&dev->udev);
release_group:
uml_vfio_release_group(dev->group);
free_dev:
list_del(&dev->list);
kfree(dev->name);
kfree(dev);
}
static void uml_vfio_release_device(struct uml_vfio_device *dev)
{
int i;
for (i = 0; i < dev->udev.irq_count; i++)
uml_vfio_deactivate_irq(dev, i);
uml_vfio_user_update_irqs(&dev->udev);
um_pci_device_unregister(&dev->pdev);
kfree(dev->intr_ctx);
kfree(dev->msix_data);
uml_vfio_user_teardown_device(&dev->udev);
uml_vfio_release_group(dev->group);
list_del(&dev->list);
kfree(dev->name);
kfree(dev);
}
static struct uml_vfio_device *uml_vfio_find_device(const char *device)
{
struct uml_vfio_device *dev;
list_for_each_entry(dev, &uml_vfio_devices, list) {
if (!strcmp(dev->name, device))
return dev;
}
return NULL;
}
static struct uml_vfio_device *uml_vfio_add_device(const char *device)
{
struct uml_vfio_device *dev;
int fd;
guard(mutex)(&uml_vfio_devices_mtx);
if (uml_vfio_container.fd < 0) {
fd = uml_vfio_user_open_container();
if (fd < 0)
return ERR_PTR(fd);
uml_vfio_container.fd = fd;
}
if (uml_vfio_find_device(device))
return ERR_PTR(-EEXIST);
dev = kzalloc_obj(*dev);
if (!dev)
return ERR_PTR(-ENOMEM);
dev->name = kstrdup(device, GFP_KERNEL);
if (!dev->name) {
kfree(dev);
return ERR_PTR(-ENOMEM);
}
list_add_tail(&dev->list, &uml_vfio_devices);
return dev;
}
static int uml_vfio_cmdline_set(const char *device, const struct kernel_param *kp)
{
struct uml_vfio_device *dev;
dev = uml_vfio_add_device(device);
if (IS_ERR(dev))
return PTR_ERR(dev);
return 0;
}
static int uml_vfio_cmdline_get(char *buffer, const struct kernel_param *kp)
{
return 0;
}
static const struct kernel_param_ops uml_vfio_cmdline_param_ops = {
.set = uml_vfio_cmdline_set,
.get = uml_vfio_cmdline_get,
};
device_param_cb(device, &uml_vfio_cmdline_param_ops, NULL, 0400);
__uml_help(uml_vfio_cmdline_param_ops,
"vfio_uml.device=<domain:bus:slot.function>\n"
" Pass through a PCI device to UML via VFIO. Currently, only MSI-X\n"
" capable devices are supported, and it is assumed that drivers will\n"
" use MSI-X. This parameter can be specified multiple times to pass\n"
" through multiple PCI devices to UML.\n\n"
);
static int uml_vfio_mc_config(char *str, char **error_out)
{
struct uml_vfio_device *dev;
if (*str != '=') {
*error_out = "Invalid config";
return -EINVAL;
}
str += 1;
dev = uml_vfio_add_device(str);
if (IS_ERR(dev))
return PTR_ERR(dev);
uml_vfio_open_device(dev);
return 0;
}
static int uml_vfio_mc_id(char **str, int *start_out, int *end_out)
{
return -EOPNOTSUPP;
}
static int uml_vfio_mc_remove(int n, char **error_out)
{
return -EOPNOTSUPP;
}
static struct mc_device uml_vfio_mc = {
.list = LIST_HEAD_INIT(uml_vfio_mc.list),
.name = "vfio_uml.device",
.config = uml_vfio_mc_config,
.get_config = NULL,
.id = uml_vfio_mc_id,
.remove = uml_vfio_mc_remove,
};
static int __init uml_vfio_init(void)
{
struct uml_vfio_device *dev, *n;
sigio_broken();
/* If the opening fails, the device will be released. */
list_for_each_entry_safe(dev, n, &uml_vfio_devices, list)
uml_vfio_open_device(dev);
mconsole_register_dev(&uml_vfio_mc);
return 0;
}
late_initcall(uml_vfio_init);
static void __exit uml_vfio_exit(void)
{
struct uml_vfio_device *dev, *n;
list_for_each_entry_safe(dev, n, &uml_vfio_devices, list)
uml_vfio_release_device(dev);
if (uml_vfio_container.fd >= 0)
os_close_file(uml_vfio_container.fd);
}
module_exit(uml_vfio_exit);
+327
View File
@@ -0,0 +1,327 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2025 Ant Group
* Author: Tiwei Bie <tiwei.btw@antgroup.com>
*/
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/eventfd.h>
#include <linux/limits.h>
#include <linux/vfio.h>
#include <linux/pci_regs.h>
#include <as-layout.h>
#include <um_malloc.h>
#include "vfio_user.h"
int uml_vfio_user_open_container(void)
{
int r, fd;
fd = open("/dev/vfio/vfio", O_RDWR);
if (fd < 0)
return -errno;
r = ioctl(fd, VFIO_GET_API_VERSION);
if (r != VFIO_API_VERSION) {
r = r < 0 ? -errno : -EINVAL;
goto error;
}
r = ioctl(fd, VFIO_CHECK_EXTENSION, VFIO_TYPE1_IOMMU);
if (r <= 0) {
r = r < 0 ? -errno : -EINVAL;
goto error;
}
return fd;
error:
close(fd);
return r;
}
int uml_vfio_user_setup_iommu(int container)
{
/*
* This is a bit tricky. See the big comment in
* vhost_user_set_mem_table() in virtio_uml.c.
*/
unsigned long reserved = uml_reserved - uml_physmem;
struct vfio_iommu_type1_dma_map dma_map = {
.argsz = sizeof(dma_map),
.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
.vaddr = uml_reserved,
.iova = reserved,
.size = physmem_size - reserved,
};
if (ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU) < 0)
return -errno;
if (ioctl(container, VFIO_IOMMU_MAP_DMA, &dma_map) < 0)
return -errno;
return 0;
}
int uml_vfio_user_get_group_id(const char *device)
{
char *path, *buf, *end;
const char *name;
int r;
path = uml_kmalloc(PATH_MAX, UM_GFP_KERNEL);
if (!path)
return -ENOMEM;
sprintf(path, "/sys/bus/pci/devices/%s/iommu_group", device);
buf = uml_kmalloc(PATH_MAX + 1, UM_GFP_KERNEL);
if (!buf) {
r = -ENOMEM;
goto free_path;
}
r = readlink(path, buf, PATH_MAX);
if (r < 0) {
r = -errno;
goto free_buf;
}
buf[r] = '\0';
name = basename(buf);
r = strtoul(name, &end, 10);
if (*end != '\0' || end == name) {
r = -EINVAL;
goto free_buf;
}
free_buf:
kfree(buf);
free_path:
kfree(path);
return r;
}
int uml_vfio_user_open_group(int group_id)
{
char *path;
int fd;
path = uml_kmalloc(PATH_MAX, UM_GFP_KERNEL);
if (!path)
return -ENOMEM;
sprintf(path, "/dev/vfio/%d", group_id);
fd = open(path, O_RDWR);
if (fd < 0) {
fd = -errno;
goto out;
}
out:
kfree(path);
return fd;
}
int uml_vfio_user_set_container(int container, int group)
{
if (ioctl(group, VFIO_GROUP_SET_CONTAINER, &container) < 0)
return -errno;
return 0;
}
int uml_vfio_user_unset_container(int container, int group)
{
if (ioctl(group, VFIO_GROUP_UNSET_CONTAINER, &container) < 0)
return -errno;
return 0;
}
static int vfio_set_irqs(int device, int start, int count, int *irqfd)
{
struct vfio_irq_set *irq_set;
int argsz = sizeof(*irq_set) + sizeof(*irqfd) * count;
int err = 0;
irq_set = uml_kmalloc(argsz, UM_GFP_KERNEL);
if (!irq_set)
return -ENOMEM;
irq_set->argsz = argsz;
irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
irq_set->index = VFIO_PCI_MSIX_IRQ_INDEX;
irq_set->start = start;
irq_set->count = count;
memcpy(irq_set->data, irqfd, sizeof(*irqfd) * count);
if (ioctl(device, VFIO_DEVICE_SET_IRQS, irq_set) < 0) {
err = -errno;
goto out;
}
out:
kfree(irq_set);
return err;
}
int uml_vfio_user_setup_device(struct uml_vfio_user_device *dev,
int group, const char *device)
{
struct vfio_device_info device_info = { .argsz = sizeof(device_info) };
struct vfio_irq_info irq_info = { .argsz = sizeof(irq_info) };
int err, i;
dev->device = ioctl(group, VFIO_GROUP_GET_DEVICE_FD, device);
if (dev->device < 0)
return -errno;
if (ioctl(dev->device, VFIO_DEVICE_GET_INFO, &device_info) < 0) {
err = -errno;
goto close_device;
}
dev->num_regions = device_info.num_regions;
if (dev->num_regions > VFIO_PCI_CONFIG_REGION_INDEX + 1)
dev->num_regions = VFIO_PCI_CONFIG_REGION_INDEX + 1;
dev->region = uml_kmalloc(sizeof(*dev->region) * dev->num_regions,
UM_GFP_KERNEL);
if (!dev->region) {
err = -ENOMEM;
goto close_device;
}
for (i = 0; i < dev->num_regions; i++) {
struct vfio_region_info region = {
.argsz = sizeof(region),
.index = i,
};
if (ioctl(dev->device, VFIO_DEVICE_GET_REGION_INFO, &region) < 0) {
err = -errno;
goto free_region;
}
dev->region[i].size = region.size;
dev->region[i].offset = region.offset;
}
/* Only MSI-X is supported currently. */
irq_info.index = VFIO_PCI_MSIX_IRQ_INDEX;
if (ioctl(dev->device, VFIO_DEVICE_GET_IRQ_INFO, &irq_info) < 0) {
err = -errno;
goto free_region;
}
dev->irq_count = irq_info.count;
dev->irqfd = uml_kmalloc(sizeof(int) * dev->irq_count, UM_GFP_KERNEL);
if (!dev->irqfd) {
err = -ENOMEM;
goto free_region;
}
memset(dev->irqfd, -1, sizeof(int) * dev->irq_count);
err = vfio_set_irqs(dev->device, 0, dev->irq_count, dev->irqfd);
if (err)
goto free_irqfd;
return 0;
free_irqfd:
kfree(dev->irqfd);
free_region:
kfree(dev->region);
close_device:
close(dev->device);
return err;
}
void uml_vfio_user_teardown_device(struct uml_vfio_user_device *dev)
{
kfree(dev->irqfd);
kfree(dev->region);
close(dev->device);
}
int uml_vfio_user_activate_irq(struct uml_vfio_user_device *dev, int index)
{
int irqfd;
irqfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (irqfd < 0)
return -errno;
dev->irqfd[index] = irqfd;
return irqfd;
}
void uml_vfio_user_deactivate_irq(struct uml_vfio_user_device *dev, int index)
{
close(dev->irqfd[index]);
dev->irqfd[index] = -1;
}
int uml_vfio_user_update_irqs(struct uml_vfio_user_device *dev)
{
return vfio_set_irqs(dev->device, 0, dev->irq_count, dev->irqfd);
}
static int vfio_region_read(struct uml_vfio_user_device *dev, unsigned int index,
uint64_t offset, void *buf, uint64_t size)
{
if (index >= dev->num_regions || offset + size > dev->region[index].size)
return -EINVAL;
if (pread(dev->device, buf, size, dev->region[index].offset + offset) < 0)
return -errno;
return 0;
}
static int vfio_region_write(struct uml_vfio_user_device *dev, unsigned int index,
uint64_t offset, const void *buf, uint64_t size)
{
if (index >= dev->num_regions || offset + size > dev->region[index].size)
return -EINVAL;
if (pwrite(dev->device, buf, size, dev->region[index].offset + offset) < 0)
return -errno;
return 0;
}
int uml_vfio_user_cfgspace_read(struct uml_vfio_user_device *dev,
unsigned int offset, void *buf, int size)
{
return vfio_region_read(dev, VFIO_PCI_CONFIG_REGION_INDEX,
offset, buf, size);
}
int uml_vfio_user_cfgspace_write(struct uml_vfio_user_device *dev,
unsigned int offset, const void *buf, int size)
{
return vfio_region_write(dev, VFIO_PCI_CONFIG_REGION_INDEX,
offset, buf, size);
}
int uml_vfio_user_bar_read(struct uml_vfio_user_device *dev, int bar,
unsigned int offset, void *buf, int size)
{
return vfio_region_read(dev, bar, offset, buf, size);
}
int uml_vfio_user_bar_write(struct uml_vfio_user_device *dev, int bar,
unsigned int offset, const void *buf, int size)
{
return vfio_region_write(dev, bar, offset, buf, size);
}
+44
View File
@@ -0,0 +1,44 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __UM_VFIO_USER_H
#define __UM_VFIO_USER_H
struct uml_vfio_user_device {
int device;
struct {
uint64_t size;
uint64_t offset;
} *region;
int num_regions;
int32_t *irqfd;
int irq_count;
};
int uml_vfio_user_open_container(void);
int uml_vfio_user_setup_iommu(int container);
int uml_vfio_user_get_group_id(const char *device);
int uml_vfio_user_open_group(int group_id);
int uml_vfio_user_set_container(int container, int group);
int uml_vfio_user_unset_container(int container, int group);
int uml_vfio_user_setup_device(struct uml_vfio_user_device *dev,
int group, const char *device);
void uml_vfio_user_teardown_device(struct uml_vfio_user_device *dev);
int uml_vfio_user_activate_irq(struct uml_vfio_user_device *dev, int index);
void uml_vfio_user_deactivate_irq(struct uml_vfio_user_device *dev, int index);
int uml_vfio_user_update_irqs(struct uml_vfio_user_device *dev);
int uml_vfio_user_cfgspace_read(struct uml_vfio_user_device *dev,
unsigned int offset, void *buf, int size);
int uml_vfio_user_cfgspace_write(struct uml_vfio_user_device *dev,
unsigned int offset, const void *buf, int size);
int uml_vfio_user_bar_read(struct uml_vfio_user_device *dev, int bar,
unsigned int offset, void *buf, int size);
int uml_vfio_user_bar_write(struct uml_vfio_user_device *dev, int bar,
unsigned int offset, const void *buf, int size);
#endif /* __UM_VFIO_USER_H */
+123
View File
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* Vhost-user protocol */
#ifndef __VHOST_USER_H__
#define __VHOST_USER_H__
/* Message flags */
#define VHOST_USER_FLAG_REPLY BIT(2)
#define VHOST_USER_FLAG_NEED_REPLY BIT(3)
/* Feature bits */
#define VHOST_USER_F_PROTOCOL_FEATURES 30
/* Protocol feature bits */
#define VHOST_USER_PROTOCOL_F_MQ 0
#define VHOST_USER_PROTOCOL_F_REPLY_ACK 3
#define VHOST_USER_PROTOCOL_F_SLAVE_REQ 5
#define VHOST_USER_PROTOCOL_F_CONFIG 9
#define VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS 14
/* Vring state index masks */
#define VHOST_USER_VRING_INDEX_MASK 0xff
#define VHOST_USER_VRING_POLL_MASK BIT(8)
/* Supported version */
#define VHOST_USER_VERSION 1
/* Supported transport features */
#define VHOST_USER_SUPPORTED_F BIT_ULL(VHOST_USER_F_PROTOCOL_FEATURES)
/* Supported protocol features */
#define VHOST_USER_SUPPORTED_PROTOCOL_F (BIT_ULL(VHOST_USER_PROTOCOL_F_MQ) | \
BIT_ULL(VHOST_USER_PROTOCOL_F_REPLY_ACK) | \
BIT_ULL(VHOST_USER_PROTOCOL_F_SLAVE_REQ) | \
BIT_ULL(VHOST_USER_PROTOCOL_F_CONFIG) | \
BIT_ULL(VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS))
enum vhost_user_request {
VHOST_USER_GET_FEATURES = 1,
VHOST_USER_SET_FEATURES = 2,
VHOST_USER_SET_OWNER = 3,
VHOST_USER_RESET_OWNER = 4,
VHOST_USER_SET_MEM_TABLE = 5,
VHOST_USER_SET_LOG_BASE = 6,
VHOST_USER_SET_LOG_FD = 7,
VHOST_USER_SET_VRING_NUM = 8,
VHOST_USER_SET_VRING_ADDR = 9,
VHOST_USER_SET_VRING_BASE = 10,
VHOST_USER_GET_VRING_BASE = 11,
VHOST_USER_SET_VRING_KICK = 12,
VHOST_USER_SET_VRING_CALL = 13,
VHOST_USER_SET_VRING_ERR = 14,
VHOST_USER_GET_PROTOCOL_FEATURES = 15,
VHOST_USER_SET_PROTOCOL_FEATURES = 16,
VHOST_USER_GET_QUEUE_NUM = 17,
VHOST_USER_SET_VRING_ENABLE = 18,
VHOST_USER_SEND_RARP = 19,
VHOST_USER_NET_SEND_MTU = 20,
VHOST_USER_SET_SLAVE_REQ_FD = 21,
VHOST_USER_IOTLB_MSG = 22,
VHOST_USER_SET_VRING_ENDIAN = 23,
VHOST_USER_GET_CONFIG = 24,
VHOST_USER_SET_CONFIG = 25,
VHOST_USER_VRING_KICK = 35,
};
enum vhost_user_slave_request {
VHOST_USER_SLAVE_IOTLB_MSG = 1,
VHOST_USER_SLAVE_CONFIG_CHANGE_MSG = 2,
VHOST_USER_SLAVE_VRING_HOST_NOTIFIER_MSG = 3,
VHOST_USER_SLAVE_VRING_CALL = 4,
};
struct vhost_user_header {
/*
* Use enum vhost_user_request for outgoing messages,
* uses enum vhost_user_slave_request for incoming ones.
*/
u32 request;
u32 flags;
u32 size;
} __packed;
struct vhost_user_config {
u32 offset;
u32 size;
u32 flags;
u8 payload[]; /* Variable length */
} __packed;
struct vhost_user_vring_state {
u32 index;
u32 num;
} __packed;
struct vhost_user_vring_addr {
u32 index;
u32 flags;
u64 desc, used, avail, log;
} __packed;
struct vhost_user_mem_region {
u64 guest_addr;
u64 size;
u64 user_addr;
u64 mmap_offset;
} __packed;
struct vhost_user_mem_regions {
u32 num;
u32 padding;
struct vhost_user_mem_region regions[2]; /* Currently supporting 2 */
} __packed;
union vhost_user_payload {
u64 integer;
struct vhost_user_config config;
struct vhost_user_vring_state vring_state;
struct vhost_user_vring_addr vring_addr;
struct vhost_user_mem_regions mem_regions;
};
struct vhost_user_msg {
struct vhost_user_header header;
union vhost_user_payload payload;
} __packed;
#endif
+618
View File
@@ -0,0 +1,618 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020 Intel Corporation
* Author: Johannes Berg <johannes@sipsolutions.net>
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/logic_iomem.h>
#include <linux/of_platform.h>
#include <linux/irqchip/irq-msi-lib.h>
#include <linux/irqdomain.h>
#include <linux/msi.h>
#include <linux/unaligned.h>
#include <irq_kern.h>
#include "virt-pci.h"
#define MAX_DEVICES 8
#define MAX_MSI_VECTORS 32
#define CFG_SPACE_SIZE 4096
struct um_pci_device_reg {
struct um_pci_device *dev;
void __iomem *iomem;
};
static struct pci_host_bridge *bridge;
static DEFINE_MUTEX(um_pci_mtx);
static struct um_pci_device *um_pci_platform_device;
static struct um_pci_device_reg um_pci_devices[MAX_DEVICES];
static struct fwnode_handle *um_pci_fwnode;
static struct irq_domain *um_pci_inner_domain;
static unsigned long um_pci_msi_used[BITS_TO_LONGS(MAX_MSI_VECTORS)];
static unsigned long um_pci_cfgspace_read(void *priv, unsigned int offset,
int size)
{
struct um_pci_device_reg *reg = priv;
struct um_pci_device *dev = reg->dev;
if (!dev)
return ULONG_MAX;
switch (size) {
case 1:
case 2:
case 4:
#ifdef CONFIG_64BIT
case 8:
#endif
break;
default:
WARN(1, "invalid config space read size %d\n", size);
return ULONG_MAX;
}
return dev->ops->cfgspace_read(dev, offset, size);
}
static void um_pci_cfgspace_write(void *priv, unsigned int offset, int size,
unsigned long val)
{
struct um_pci_device_reg *reg = priv;
struct um_pci_device *dev = reg->dev;
if (!dev)
return;
switch (size) {
case 1:
case 2:
case 4:
#ifdef CONFIG_64BIT
case 8:
#endif
break;
default:
WARN(1, "invalid config space write size %d\n", size);
return;
}
dev->ops->cfgspace_write(dev, offset, size, val);
}
static const struct logic_iomem_ops um_pci_device_cfgspace_ops = {
.read = um_pci_cfgspace_read,
.write = um_pci_cfgspace_write,
};
static unsigned long um_pci_bar_read(void *priv, unsigned int offset,
int size)
{
u8 *resptr = priv;
struct um_pci_device *dev = container_of(resptr - *resptr,
struct um_pci_device,
resptr[0]);
u8 bar = *resptr;
switch (size) {
case 1:
case 2:
case 4:
#ifdef CONFIG_64BIT
case 8:
#endif
break;
default:
WARN(1, "invalid bar read size %d\n", size);
return ULONG_MAX;
}
return dev->ops->bar_read(dev, bar, offset, size);
}
static void um_pci_bar_write(void *priv, unsigned int offset, int size,
unsigned long val)
{
u8 *resptr = priv;
struct um_pci_device *dev = container_of(resptr - *resptr,
struct um_pci_device,
resptr[0]);
u8 bar = *resptr;
switch (size) {
case 1:
case 2:
case 4:
#ifdef CONFIG_64BIT
case 8:
#endif
break;
default:
WARN(1, "invalid bar write size %d\n", size);
return;
}
dev->ops->bar_write(dev, bar, offset, size, val);
}
static void um_pci_bar_copy_from(void *priv, void *buffer,
unsigned int offset, int size)
{
u8 *resptr = priv;
struct um_pci_device *dev = container_of(resptr - *resptr,
struct um_pci_device,
resptr[0]);
u8 bar = *resptr;
dev->ops->bar_copy_from(dev, bar, buffer, offset, size);
}
static void um_pci_bar_copy_to(void *priv, unsigned int offset,
const void *buffer, int size)
{
u8 *resptr = priv;
struct um_pci_device *dev = container_of(resptr - *resptr,
struct um_pci_device,
resptr[0]);
u8 bar = *resptr;
dev->ops->bar_copy_to(dev, bar, offset, buffer, size);
}
static void um_pci_bar_set(void *priv, unsigned int offset, u8 value, int size)
{
u8 *resptr = priv;
struct um_pci_device *dev = container_of(resptr - *resptr,
struct um_pci_device,
resptr[0]);
u8 bar = *resptr;
dev->ops->bar_set(dev, bar, offset, value, size);
}
static const struct logic_iomem_ops um_pci_device_bar_ops = {
.read = um_pci_bar_read,
.write = um_pci_bar_write,
.set = um_pci_bar_set,
.copy_from = um_pci_bar_copy_from,
.copy_to = um_pci_bar_copy_to,
};
static void __iomem *um_pci_map_bus(struct pci_bus *bus, unsigned int devfn,
int where)
{
struct um_pci_device_reg *dev;
unsigned int busn = bus->number;
if (busn > 0)
return NULL;
/* not allowing functions for now ... */
if (devfn % 8)
return NULL;
if (devfn / 8 >= ARRAY_SIZE(um_pci_devices))
return NULL;
dev = &um_pci_devices[devfn / 8];
if (!dev)
return NULL;
return (void __iomem *)((unsigned long)dev->iomem + where);
}
static struct pci_ops um_pci_ops = {
.map_bus = um_pci_map_bus,
.read = pci_generic_config_read,
.write = pci_generic_config_write,
};
static void um_pci_rescan(void)
{
pci_lock_rescan_remove();
pci_rescan_bus(bridge->bus);
pci_unlock_rescan_remove();
}
#ifdef CONFIG_OF
/* Copied from arch/x86/kernel/devicetree.c */
struct device_node *pcibios_get_phb_of_node(struct pci_bus *bus)
{
struct device_node *np;
for_each_node_by_type(np, "pci") {
const void *prop;
unsigned int bus_min;
prop = of_get_property(np, "bus-range", NULL);
if (!prop)
continue;
bus_min = be32_to_cpup(prop);
if (bus->number == bus_min)
return np;
}
return NULL;
}
#endif
static struct resource virt_cfgspace_resource = {
.name = "PCI config space",
.start = 0xf0000000 - MAX_DEVICES * CFG_SPACE_SIZE,
.end = 0xf0000000 - 1,
.flags = IORESOURCE_MEM,
};
static long um_pci_map_cfgspace(unsigned long offset, size_t size,
const struct logic_iomem_ops **ops,
void **priv)
{
if (WARN_ON(size > CFG_SPACE_SIZE || offset % CFG_SPACE_SIZE))
return -EINVAL;
if (offset / CFG_SPACE_SIZE < MAX_DEVICES) {
*ops = &um_pci_device_cfgspace_ops;
*priv = &um_pci_devices[offset / CFG_SPACE_SIZE];
return 0;
}
WARN(1, "cannot map offset 0x%lx/0x%zx\n", offset, size);
return -ENOENT;
}
static const struct logic_iomem_region_ops um_pci_cfgspace_ops = {
.map = um_pci_map_cfgspace,
};
static struct resource virt_iomem_resource = {
.name = "PCI iomem",
.start = 0xf0000000,
.end = 0xffffffff,
.flags = IORESOURCE_MEM,
};
struct um_pci_map_iomem_data {
unsigned long offset;
size_t size;
const struct logic_iomem_ops **ops;
void **priv;
long ret;
};
static int um_pci_map_iomem_walk(struct pci_dev *pdev, void *_data)
{
struct um_pci_map_iomem_data *data = _data;
struct um_pci_device_reg *reg = &um_pci_devices[pdev->devfn / 8];
struct um_pci_device *dev;
int i;
if (!reg->dev)
return 0;
for (i = 0; i < ARRAY_SIZE(dev->resptr); i++) {
struct resource *r = &pdev->resource[i];
if ((r->flags & IORESOURCE_TYPE_BITS) != IORESOURCE_MEM)
continue;
/*
* must be the whole or part of the resource,
* not allowed to only overlap
*/
if (data->offset < r->start || data->offset > r->end)
continue;
if (data->offset + data->size - 1 > r->end)
continue;
dev = reg->dev;
*data->ops = &um_pci_device_bar_ops;
dev->resptr[i] = i;
*data->priv = &dev->resptr[i];
data->ret = data->offset - r->start;
/* no need to continue */
return 1;
}
return 0;
}
static long um_pci_map_iomem(unsigned long offset, size_t size,
const struct logic_iomem_ops **ops,
void **priv)
{
struct um_pci_map_iomem_data data = {
/* we want the full address here */
.offset = offset + virt_iomem_resource.start,
.size = size,
.ops = ops,
.priv = priv,
.ret = -ENOENT,
};
pci_walk_bus(bridge->bus, um_pci_map_iomem_walk, &data);
return data.ret;
}
static const struct logic_iomem_region_ops um_pci_iomem_ops = {
.map = um_pci_map_iomem,
};
static void um_pci_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
{
/*
* This is a very low address and not actually valid 'physical' memory
* in UML, so we can simply map MSI(-X) vectors to there, it cannot be
* legitimately written to by the device in any other way.
* We use the (virtual) IRQ number here as the message to simplify the
* code that receives the message, where for now we simply trust the
* device to send the correct message.
*/
msg->address_hi = 0;
msg->address_lo = 0xa0000;
msg->data = data->irq;
}
static struct irq_chip um_pci_msi_bottom_irq_chip = {
.name = "UM virtual MSI",
.irq_compose_msi_msg = um_pci_compose_msi_msg,
};
static int um_pci_inner_domain_alloc(struct irq_domain *domain,
unsigned int virq, unsigned int nr_irqs,
void *args)
{
unsigned long bit;
WARN_ON(nr_irqs != 1);
mutex_lock(&um_pci_mtx);
bit = find_first_zero_bit(um_pci_msi_used, MAX_MSI_VECTORS);
if (bit >= MAX_MSI_VECTORS) {
mutex_unlock(&um_pci_mtx);
return -ENOSPC;
}
set_bit(bit, um_pci_msi_used);
mutex_unlock(&um_pci_mtx);
irq_domain_set_info(domain, virq, bit, &um_pci_msi_bottom_irq_chip,
domain->host_data, handle_simple_irq,
NULL, NULL);
return 0;
}
static void um_pci_inner_domain_free(struct irq_domain *domain,
unsigned int virq, unsigned int nr_irqs)
{
struct irq_data *d = irq_domain_get_irq_data(domain, virq);
mutex_lock(&um_pci_mtx);
if (!test_bit(d->hwirq, um_pci_msi_used))
pr_err("trying to free unused MSI#%lu\n", d->hwirq);
else
__clear_bit(d->hwirq, um_pci_msi_used);
mutex_unlock(&um_pci_mtx);
}
static const struct irq_domain_ops um_pci_inner_domain_ops = {
.select = msi_lib_irq_domain_select,
.alloc = um_pci_inner_domain_alloc,
.free = um_pci_inner_domain_free,
};
#define UM_PCI_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
MSI_FLAG_USE_DEF_CHIP_OPS | \
MSI_FLAG_NO_AFFINITY)
#define UM_PCI_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \
MSI_FLAG_PCI_MSIX)
static const struct msi_parent_ops um_pci_msi_parent_ops = {
.required_flags = UM_PCI_MSI_FLAGS_REQUIRED,
.supported_flags = UM_PCI_MSI_FLAGS_SUPPORTED,
.bus_select_token = DOMAIN_BUS_NEXUS,
.bus_select_mask = MATCH_PCI_MSI,
.prefix = "UM-virtual-",
.init_dev_msi_info = msi_lib_init_dev_msi_info,
};
static struct resource busn_resource = {
.name = "PCI busn",
.start = 0,
.end = 0,
.flags = IORESOURCE_BUS,
};
static int um_pci_map_irq(const struct pci_dev *pdev, u8 slot, u8 pin)
{
struct um_pci_device_reg *reg = &um_pci_devices[pdev->devfn / 8];
if (WARN_ON(!reg->dev))
return -EINVAL;
/* Yes, we map all pins to the same IRQ ... doesn't matter for now. */
return reg->dev->irq;
}
void *pci_root_bus_fwnode(struct pci_bus *bus)
{
return um_pci_fwnode;
}
static long um_pci_map_platform(unsigned long offset, size_t size,
const struct logic_iomem_ops **ops,
void **priv)
{
if (!um_pci_platform_device)
return -ENOENT;
*ops = &um_pci_device_bar_ops;
*priv = &um_pci_platform_device->resptr[0];
return offset;
}
static const struct logic_iomem_region_ops um_pci_platform_ops = {
.map = um_pci_map_platform,
};
static struct resource virt_platform_resource = {
.name = "platform",
.start = 0x10000000,
.end = 0x1fffffff,
.flags = IORESOURCE_MEM,
};
int um_pci_device_register(struct um_pci_device *dev)
{
int i, free = -1;
int err = 0;
mutex_lock(&um_pci_mtx);
for (i = 0; i < MAX_DEVICES; i++) {
if (um_pci_devices[i].dev)
continue;
free = i;
break;
}
if (free < 0) {
err = -ENOSPC;
goto out;
}
dev->irq = irq_alloc_desc(numa_node_id());
if (dev->irq < 0) {
err = dev->irq;
goto out;
}
um_pci_devices[free].dev = dev;
out:
mutex_unlock(&um_pci_mtx);
if (!err)
um_pci_rescan();
return err;
}
void um_pci_device_unregister(struct um_pci_device *dev)
{
int i;
mutex_lock(&um_pci_mtx);
for (i = 0; i < MAX_DEVICES; i++) {
if (um_pci_devices[i].dev != dev)
continue;
um_pci_devices[i].dev = NULL;
irq_free_desc(dev->irq);
break;
}
mutex_unlock(&um_pci_mtx);
if (i < MAX_DEVICES) {
struct pci_dev *pci_dev;
pci_dev = pci_get_slot(bridge->bus, i);
if (pci_dev)
pci_stop_and_remove_bus_device_locked(pci_dev);
}
}
int um_pci_platform_device_register(struct um_pci_device *dev)
{
guard(mutex)(&um_pci_mtx);
if (um_pci_platform_device)
return -EBUSY;
um_pci_platform_device = dev;
return 0;
}
void um_pci_platform_device_unregister(struct um_pci_device *dev)
{
guard(mutex)(&um_pci_mtx);
if (um_pci_platform_device == dev)
um_pci_platform_device = NULL;
}
static int __init um_pci_init(void)
{
int err, i;
WARN_ON(logic_iomem_add_region(&virt_cfgspace_resource,
&um_pci_cfgspace_ops));
WARN_ON(logic_iomem_add_region(&virt_iomem_resource,
&um_pci_iomem_ops));
WARN_ON(logic_iomem_add_region(&virt_platform_resource,
&um_pci_platform_ops));
bridge = pci_alloc_host_bridge(0);
if (!bridge) {
err = -ENOMEM;
goto free;
}
um_pci_fwnode = irq_domain_alloc_named_fwnode("um-pci");
if (!um_pci_fwnode) {
err = -ENOMEM;
goto free;
}
struct irq_domain_info info = {
.fwnode = um_pci_fwnode,
.ops = &um_pci_inner_domain_ops,
.size = MAX_MSI_VECTORS,
};
um_pci_inner_domain = msi_create_parent_irq_domain(&info, &um_pci_msi_parent_ops);
if (!um_pci_inner_domain) {
err = -ENOMEM;
goto free;
}
pci_add_resource(&bridge->windows, &virt_iomem_resource);
pci_add_resource(&bridge->windows, &busn_resource);
bridge->ops = &um_pci_ops;
bridge->map_irq = um_pci_map_irq;
for (i = 0; i < MAX_DEVICES; i++) {
resource_size_t start;
start = virt_cfgspace_resource.start + i * CFG_SPACE_SIZE;
um_pci_devices[i].iomem = ioremap(start, CFG_SPACE_SIZE);
if (WARN(!um_pci_devices[i].iomem, "failed to map %d\n", i)) {
err = -ENOMEM;
goto free;
}
}
err = pci_host_probe(bridge);
if (err)
goto free;
return 0;
free:
if (um_pci_inner_domain)
irq_domain_remove(um_pci_inner_domain);
if (um_pci_fwnode)
irq_domain_free_fwnode(um_pci_fwnode);
if (bridge) {
pci_free_resource_list(&bridge->windows);
pci_free_host_bridge(bridge);
}
return err;
}
device_initcall(um_pci_init);
static void __exit um_pci_exit(void)
{
irq_domain_remove(um_pci_inner_domain);
pci_free_resource_list(&bridge->windows);
pci_free_host_bridge(bridge);
}
module_exit(um_pci_exit);
+41
View File
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __UM_VIRT_PCI_H
#define __UM_VIRT_PCI_H
#include <linux/pci.h>
struct um_pci_device {
const struct um_pci_ops *ops;
/* for now just standard BARs */
u8 resptr[PCI_STD_NUM_BARS];
int irq;
};
struct um_pci_ops {
unsigned long (*cfgspace_read)(struct um_pci_device *dev,
unsigned int offset, int size);
void (*cfgspace_write)(struct um_pci_device *dev, unsigned int offset,
int size, unsigned long val);
unsigned long (*bar_read)(struct um_pci_device *dev, int bar,
unsigned int offset, int size);
void (*bar_write)(struct um_pci_device *dev, int bar,
unsigned int offset, int size, unsigned long val);
void (*bar_copy_from)(struct um_pci_device *dev, int bar, void *buffer,
unsigned int offset, int size);
void (*bar_copy_to)(struct um_pci_device *dev, int bar,
unsigned int offset, const void *buffer, int size);
void (*bar_set)(struct um_pci_device *dev, int bar,
unsigned int offset, u8 value, int size);
};
int um_pci_device_register(struct um_pci_device *dev);
void um_pci_device_unregister(struct um_pci_device *dev);
int um_pci_platform_device_register(struct um_pci_device *dev);
void um_pci_platform_device_unregister(struct um_pci_device *dev);
#endif /* __UM_VIRT_PCI_H */
+634
View File
@@ -0,0 +1,634 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020 Intel Corporation
* Author: Johannes Berg <johannes@sipsolutions.net>
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <linux/logic_iomem.h>
#include <linux/of_platform.h>
#include <linux/irqdomain.h>
#include <linux/virtio_pcidev.h>
#include <linux/virtio-uml.h>
#include <linux/delay.h>
#include <linux/msi.h>
#include <linux/unaligned.h>
#include <irq_kern.h>
#include "virt-pci.h"
#define to_virtio_pcidev(_pdev) \
container_of(_pdev, struct virtio_pcidev_device, pdev)
/* for MSI-X we have a 32-bit payload */
#define MAX_IRQ_MSG_SIZE (sizeof(struct virtio_pcidev_msg) + sizeof(u32))
#define NUM_IRQ_MSGS 10
struct virtio_pcidev_message_buffer {
struct virtio_pcidev_msg hdr;
u8 data[8];
};
struct virtio_pcidev_device {
struct um_pci_device pdev;
struct virtio_device *vdev;
struct virtqueue *cmd_vq, *irq_vq;
#define VIRTIO_PCIDEV_WRITE_BUFS 20
struct virtio_pcidev_message_buffer bufs[VIRTIO_PCIDEV_WRITE_BUFS + 1];
void *extra_ptrs[VIRTIO_PCIDEV_WRITE_BUFS + 1];
DECLARE_BITMAP(used_bufs, VIRTIO_PCIDEV_WRITE_BUFS);
#define VIRTIO_PCIDEV_STAT_WAITING 0
unsigned long status;
bool platform;
};
static unsigned int virtio_pcidev_max_delay_us = 40000;
module_param_named(max_delay_us, virtio_pcidev_max_delay_us, uint, 0644);
static int virtio_pcidev_get_buf(struct virtio_pcidev_device *dev, bool *posted)
{
int i;
for (i = 0; i < VIRTIO_PCIDEV_WRITE_BUFS; i++) {
if (!test_and_set_bit(i, dev->used_bufs))
return i;
}
*posted = false;
return VIRTIO_PCIDEV_WRITE_BUFS;
}
static void virtio_pcidev_free_buf(struct virtio_pcidev_device *dev, void *buf)
{
int i;
if (buf == &dev->bufs[VIRTIO_PCIDEV_WRITE_BUFS]) {
kfree(dev->extra_ptrs[VIRTIO_PCIDEV_WRITE_BUFS]);
dev->extra_ptrs[VIRTIO_PCIDEV_WRITE_BUFS] = NULL;
return;
}
for (i = 0; i < VIRTIO_PCIDEV_WRITE_BUFS; i++) {
if (buf == &dev->bufs[i]) {
kfree(dev->extra_ptrs[i]);
dev->extra_ptrs[i] = NULL;
WARN_ON(!test_and_clear_bit(i, dev->used_bufs));
return;
}
}
WARN_ON(1);
}
static int virtio_pcidev_send_cmd(struct virtio_pcidev_device *dev,
struct virtio_pcidev_msg *cmd,
unsigned int cmd_size,
const void *extra, unsigned int extra_size,
void *out, unsigned int out_size)
{
struct scatterlist out_sg, extra_sg, in_sg;
struct scatterlist *sgs_list[] = {
[0] = &out_sg,
[1] = extra ? &extra_sg : &in_sg,
[2] = extra ? &in_sg : NULL,
};
struct virtio_pcidev_message_buffer *buf;
int delay_count = 0;
bool bounce_out;
int ret, len;
int buf_idx;
bool posted;
if (WARN_ON(cmd_size < sizeof(*cmd) || cmd_size > sizeof(*buf)))
return -EINVAL;
switch (cmd->op) {
case VIRTIO_PCIDEV_OP_CFG_WRITE:
case VIRTIO_PCIDEV_OP_MMIO_WRITE:
case VIRTIO_PCIDEV_OP_MMIO_MEMSET:
/* in PCI, writes are posted, so don't wait */
posted = !out;
WARN_ON(!posted);
break;
default:
posted = false;
break;
}
bounce_out = !posted && cmd_size <= sizeof(*cmd) &&
out && out_size <= sizeof(buf->data);
buf_idx = virtio_pcidev_get_buf(dev, &posted);
buf = &dev->bufs[buf_idx];
memcpy(buf, cmd, cmd_size);
if (posted && extra && extra_size > sizeof(buf) - cmd_size) {
dev->extra_ptrs[buf_idx] = kmemdup(extra, extra_size,
GFP_ATOMIC);
if (!dev->extra_ptrs[buf_idx]) {
virtio_pcidev_free_buf(dev, buf);
return -ENOMEM;
}
extra = dev->extra_ptrs[buf_idx];
} else if (extra && extra_size <= sizeof(buf) - cmd_size) {
memcpy((u8 *)buf + cmd_size, extra, extra_size);
cmd_size += extra_size;
extra_size = 0;
extra = NULL;
cmd = (void *)buf;
} else {
cmd = (void *)buf;
}
sg_init_one(&out_sg, cmd, cmd_size);
if (extra)
sg_init_one(&extra_sg, extra, extra_size);
/* allow stack for small buffers */
if (bounce_out)
sg_init_one(&in_sg, buf->data, out_size);
else if (out)
sg_init_one(&in_sg, out, out_size);
/* add to internal virtio queue */
ret = virtqueue_add_sgs(dev->cmd_vq, sgs_list,
extra ? 2 : 1,
out ? 1 : 0,
cmd, GFP_ATOMIC);
if (ret) {
virtio_pcidev_free_buf(dev, buf);
return ret;
}
if (posted) {
virtqueue_kick(dev->cmd_vq);
return 0;
}
/* kick and poll for getting a response on the queue */
set_bit(VIRTIO_PCIDEV_STAT_WAITING, &dev->status);
virtqueue_kick(dev->cmd_vq);
ret = 0;
while (1) {
void *completed = virtqueue_get_buf(dev->cmd_vq, &len);
if (completed == buf)
break;
if (completed)
virtio_pcidev_free_buf(dev, completed);
if (WARN_ONCE(virtqueue_is_broken(dev->cmd_vq) ||
++delay_count > virtio_pcidev_max_delay_us,
"um virt-pci delay: %d", delay_count)) {
ret = -EIO;
break;
}
udelay(1);
}
clear_bit(VIRTIO_PCIDEV_STAT_WAITING, &dev->status);
if (bounce_out)
memcpy(out, buf->data, out_size);
virtio_pcidev_free_buf(dev, buf);
return ret;
}
static unsigned long virtio_pcidev_cfgspace_read(struct um_pci_device *pdev,
unsigned int offset, int size)
{
struct virtio_pcidev_device *dev = to_virtio_pcidev(pdev);
struct virtio_pcidev_msg hdr = {
.op = VIRTIO_PCIDEV_OP_CFG_READ,
.size = size,
.addr = offset,
};
/* max 8, we might not use it all */
u8 data[8];
memset(data, 0xff, sizeof(data));
/* size has been checked in um_pci_cfgspace_read() */
if (virtio_pcidev_send_cmd(dev, &hdr, sizeof(hdr), NULL, 0, data, size))
return ULONG_MAX;
switch (size) {
case 1:
return data[0];
case 2:
return le16_to_cpup((void *)data);
case 4:
return le32_to_cpup((void *)data);
#ifdef CONFIG_64BIT
case 8:
return le64_to_cpup((void *)data);
#endif
default:
return ULONG_MAX;
}
}
static void virtio_pcidev_cfgspace_write(struct um_pci_device *pdev,
unsigned int offset, int size,
unsigned long val)
{
struct virtio_pcidev_device *dev = to_virtio_pcidev(pdev);
struct {
struct virtio_pcidev_msg hdr;
/* maximum size - we may only use parts of it */
u8 data[8];
} msg = {
.hdr = {
.op = VIRTIO_PCIDEV_OP_CFG_WRITE,
.size = size,
.addr = offset,
},
};
/* size has been checked in um_pci_cfgspace_write() */
switch (size) {
case 1:
msg.data[0] = (u8)val;
break;
case 2:
put_unaligned_le16(val, (void *)msg.data);
break;
case 4:
put_unaligned_le32(val, (void *)msg.data);
break;
#ifdef CONFIG_64BIT
case 8:
put_unaligned_le64(val, (void *)msg.data);
break;
#endif
}
WARN_ON(virtio_pcidev_send_cmd(dev, &msg.hdr, sizeof(msg), NULL, 0, NULL, 0));
}
static void virtio_pcidev_bar_copy_from(struct um_pci_device *pdev,
int bar, void *buffer,
unsigned int offset, int size)
{
struct virtio_pcidev_device *dev = to_virtio_pcidev(pdev);
struct virtio_pcidev_msg hdr = {
.op = VIRTIO_PCIDEV_OP_MMIO_READ,
.bar = bar,
.size = size,
.addr = offset,
};
memset(buffer, 0xff, size);
virtio_pcidev_send_cmd(dev, &hdr, sizeof(hdr), NULL, 0, buffer, size);
}
static unsigned long virtio_pcidev_bar_read(struct um_pci_device *pdev, int bar,
unsigned int offset, int size)
{
/* 8 is maximum size - we may only use parts of it */
u8 data[8];
/* size has been checked in um_pci_bar_read() */
virtio_pcidev_bar_copy_from(pdev, bar, data, offset, size);
switch (size) {
case 1:
return data[0];
case 2:
return le16_to_cpup((void *)data);
case 4:
return le32_to_cpup((void *)data);
#ifdef CONFIG_64BIT
case 8:
return le64_to_cpup((void *)data);
#endif
default:
return ULONG_MAX;
}
}
static void virtio_pcidev_bar_copy_to(struct um_pci_device *pdev,
int bar, unsigned int offset,
const void *buffer, int size)
{
struct virtio_pcidev_device *dev = to_virtio_pcidev(pdev);
struct virtio_pcidev_msg hdr = {
.op = VIRTIO_PCIDEV_OP_MMIO_WRITE,
.bar = bar,
.size = size,
.addr = offset,
};
virtio_pcidev_send_cmd(dev, &hdr, sizeof(hdr), buffer, size, NULL, 0);
}
static void virtio_pcidev_bar_write(struct um_pci_device *pdev, int bar,
unsigned int offset, int size,
unsigned long val)
{
/* maximum size - we may only use parts of it */
u8 data[8];
/* size has been checked in um_pci_bar_write() */
switch (size) {
case 1:
data[0] = (u8)val;
break;
case 2:
put_unaligned_le16(val, (void *)data);
break;
case 4:
put_unaligned_le32(val, (void *)data);
break;
#ifdef CONFIG_64BIT
case 8:
put_unaligned_le64(val, (void *)data);
break;
#endif
}
virtio_pcidev_bar_copy_to(pdev, bar, offset, data, size);
}
static void virtio_pcidev_bar_set(struct um_pci_device *pdev, int bar,
unsigned int offset, u8 value, int size)
{
struct virtio_pcidev_device *dev = to_virtio_pcidev(pdev);
struct {
struct virtio_pcidev_msg hdr;
u8 data;
} msg = {
.hdr = {
.op = VIRTIO_PCIDEV_OP_CFG_WRITE,
.bar = bar,
.size = size,
.addr = offset,
},
.data = value,
};
virtio_pcidev_send_cmd(dev, &msg.hdr, sizeof(msg), NULL, 0, NULL, 0);
}
static const struct um_pci_ops virtio_pcidev_um_pci_ops = {
.cfgspace_read = virtio_pcidev_cfgspace_read,
.cfgspace_write = virtio_pcidev_cfgspace_write,
.bar_read = virtio_pcidev_bar_read,
.bar_write = virtio_pcidev_bar_write,
.bar_copy_from = virtio_pcidev_bar_copy_from,
.bar_copy_to = virtio_pcidev_bar_copy_to,
.bar_set = virtio_pcidev_bar_set,
};
static void virtio_pcidev_irq_vq_addbuf(struct virtqueue *vq, void *buf, bool kick)
{
struct scatterlist sg[1];
sg_init_one(sg, buf, MAX_IRQ_MSG_SIZE);
if (virtqueue_add_inbuf(vq, sg, 1, buf, GFP_ATOMIC))
kfree(buf);
else if (kick)
virtqueue_kick(vq);
}
static void virtio_pcidev_handle_irq_message(struct virtqueue *vq,
struct virtio_pcidev_msg *msg)
{
struct virtio_device *vdev = vq->vdev;
struct virtio_pcidev_device *dev = vdev->priv;
if (!dev->pdev.irq)
return;
/* we should properly chain interrupts, but on ARCH=um we don't care */
switch (msg->op) {
case VIRTIO_PCIDEV_OP_INT:
generic_handle_irq(dev->pdev.irq);
break;
case VIRTIO_PCIDEV_OP_MSI:
/* our MSI message is just the interrupt number */
if (msg->size == sizeof(u32))
generic_handle_irq(le32_to_cpup((void *)msg->data));
else
generic_handle_irq(le16_to_cpup((void *)msg->data));
break;
case VIRTIO_PCIDEV_OP_PME:
/* nothing to do - we already woke up due to the message */
break;
default:
dev_err(&vdev->dev, "unexpected virt-pci message %d\n", msg->op);
break;
}
}
static void virtio_pcidev_cmd_vq_cb(struct virtqueue *vq)
{
struct virtio_device *vdev = vq->vdev;
struct virtio_pcidev_device *dev = vdev->priv;
void *cmd;
int len;
if (test_bit(VIRTIO_PCIDEV_STAT_WAITING, &dev->status))
return;
while ((cmd = virtqueue_get_buf(vq, &len)))
virtio_pcidev_free_buf(dev, cmd);
}
static void virtio_pcidev_irq_vq_cb(struct virtqueue *vq)
{
struct virtio_pcidev_msg *msg;
int len;
while ((msg = virtqueue_get_buf(vq, &len))) {
if (len >= sizeof(*msg))
virtio_pcidev_handle_irq_message(vq, msg);
/* recycle the message buffer */
virtio_pcidev_irq_vq_addbuf(vq, msg, true);
}
}
static int virtio_pcidev_init_vqs(struct virtio_pcidev_device *dev)
{
struct virtqueue_info vqs_info[] = {
{ "cmd", virtio_pcidev_cmd_vq_cb },
{ "irq", virtio_pcidev_irq_vq_cb },
};
struct virtqueue *vqs[2];
int err, i;
err = virtio_find_vqs(dev->vdev, 2, vqs, vqs_info, NULL);
if (err)
return err;
dev->cmd_vq = vqs[0];
dev->irq_vq = vqs[1];
virtio_device_ready(dev->vdev);
for (i = 0; i < NUM_IRQ_MSGS; i++) {
void *msg = kzalloc(MAX_IRQ_MSG_SIZE, GFP_KERNEL);
if (msg)
virtio_pcidev_irq_vq_addbuf(dev->irq_vq, msg, false);
}
virtqueue_kick(dev->irq_vq);
return 0;
}
static void __virtio_pcidev_virtio_platform_remove(struct virtio_device *vdev,
struct virtio_pcidev_device *dev)
{
um_pci_platform_device_unregister(&dev->pdev);
virtio_reset_device(vdev);
vdev->config->del_vqs(vdev);
kfree(dev);
}
static int virtio_pcidev_virtio_platform_probe(struct virtio_device *vdev,
struct virtio_pcidev_device *dev)
{
int err;
dev->platform = true;
err = virtio_pcidev_init_vqs(dev);
if (err)
goto err_free;
err = um_pci_platform_device_register(&dev->pdev);
if (err)
goto err_reset;
err = of_platform_default_populate(vdev->dev.of_node, NULL, &vdev->dev);
if (err)
goto err_unregister;
return 0;
err_unregister:
um_pci_platform_device_unregister(&dev->pdev);
err_reset:
virtio_reset_device(vdev);
vdev->config->del_vqs(vdev);
err_free:
kfree(dev);
return err;
}
static int virtio_pcidev_virtio_probe(struct virtio_device *vdev)
{
struct virtio_pcidev_device *dev;
int err;
dev = kzalloc_obj(*dev);
if (!dev)
return -ENOMEM;
dev->vdev = vdev;
vdev->priv = dev;
dev->pdev.ops = &virtio_pcidev_um_pci_ops;
if (of_device_is_compatible(vdev->dev.of_node, "simple-bus"))
return virtio_pcidev_virtio_platform_probe(vdev, dev);
err = virtio_pcidev_init_vqs(dev);
if (err)
goto err_free;
err = um_pci_device_register(&dev->pdev);
if (err)
goto err_reset;
device_set_wakeup_enable(&vdev->dev, true);
/*
* In order to do suspend-resume properly, don't allow VQs
* to be suspended.
*/
virtio_uml_set_no_vq_suspend(vdev, true);
return 0;
err_reset:
virtio_reset_device(vdev);
vdev->config->del_vqs(vdev);
err_free:
kfree(dev);
return err;
}
static void virtio_pcidev_virtio_remove(struct virtio_device *vdev)
{
struct virtio_pcidev_device *dev = vdev->priv;
if (dev->platform) {
of_platform_depopulate(&vdev->dev);
__virtio_pcidev_virtio_platform_remove(vdev, dev);
return;
}
device_set_wakeup_enable(&vdev->dev, false);
um_pci_device_unregister(&dev->pdev);
/* Stop all virtqueues */
virtio_reset_device(vdev);
dev->cmd_vq = NULL;
dev->irq_vq = NULL;
vdev->config->del_vqs(vdev);
kfree(dev);
}
static void virtio_pcidev_virtio_shutdown(struct virtio_device *vdev)
{
/* nothing to do, we just don't want queue shutdown */
}
static struct virtio_device_id id_table[] = {
{ CONFIG_UML_PCI_OVER_VIRTIO_DEVICE_ID, VIRTIO_DEV_ANY_ID },
{ 0 },
};
MODULE_DEVICE_TABLE(virtio, id_table);
static struct virtio_driver virtio_pcidev_virtio_driver = {
.driver.name = "virtio-pci",
.id_table = id_table,
.probe = virtio_pcidev_virtio_probe,
.remove = virtio_pcidev_virtio_remove,
.shutdown = virtio_pcidev_virtio_shutdown,
};
static int __init virtio_pcidev_init(void)
{
if (WARN(CONFIG_UML_PCI_OVER_VIRTIO_DEVICE_ID < 0,
"No virtio device ID configured for PCI - no PCI support\n"))
return 0;
return register_virtio_driver(&virtio_pcidev_virtio_driver);
}
late_initcall(virtio_pcidev_init);
static void __exit virtio_pcidev_exit(void)
{
unregister_virtio_driver(&virtio_pcidev_virtio_driver);
}
module_exit(virtio_pcidev_exit);
File diff suppressed because it is too large Load Diff
+226
View File
@@ -0,0 +1,226 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
*/
#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;
int chan_fd;
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,
.chan_fd = -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 = CONFIG_XTERM_CHAN_DEFAULT_EMULATOR;
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=" CONFIG_XTERM_CHAN_DEFAULT_EMULATOR
",-T,-e'.\n"
" Values for gnome-terminal are 'xterm=gnome-terminal,-t,--'.\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";
/* Ensure we are running on Xorg or Wayland. */
if (!getenv("DISPLAY") && !getenv("WAYLAND_DISPLAY")) {
printk(UM_KERN_ERR "xterm_open : neither $DISPLAY nor $WAYLAND_DISPLAY is 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;
}
data->chan_fd = fd;
new = xterm_fd(fd, &data->helper_pid);
if (new < 0) {
err = new;
printk(UM_KERN_ERR "xterm_open : xterm_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;
if (data->chan_fd != -1)
os_close_file(data->chan_fd);
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,
};
+12
View File
@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2002 Jeff Dike (jdike@karaya.com)
*/
#ifndef __XTERM_H__
#define __XTERM_H__
extern int xterm_fd(int socket, int *pid_out);
#endif
+83
View File
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
*/
#include <linux/slab.h>
#include <linux/completion.h>
#include <linux/irqreturn.h>
#include <asm/irq.h>
#include <irq_kern.h>
#include <os.h>
#include "xterm.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 = -1, n_fds = 1;
ssize_t ret;
ret = os_rcv_fd_msg(xterm->fd, &fd, n_fds,
&xterm->pid, sizeof(xterm->pid));
if (ret == -EAGAIN)
return IRQ_NONE;
if (ret < 0)
fd = ret;
else if (ret != sizeof(xterm->pid))
fd = -EMSGSIZE;
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_obj(*data);
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 < 0) {
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;
}