From: Jack Lange Date: Wed, 17 Aug 2011 18:23:46 +0000 (-0400) Subject: added virtio console, finished the stream interface implementation, added v3_stream... X-Git-Url: http://v3vee.org/palacios/gitweb/gitweb.cgi?p=palacios.git;a=commitdiff_plain;h=ef34565873989f5b0240f7f9911c3666a69587c9 added virtio console, finished the stream interface implementation, added v3_stream user utility --- diff --git a/linux_module/iface-console.c b/linux_module/iface-console.c index 3e82d4c..9e13546 100644 --- a/linux_module/iface-console.c +++ b/linux_module/iface-console.c @@ -140,7 +140,7 @@ console_write(struct file * filp, const char __user * buf, size_t size, loff_t * for (i = 0; i < size; i++) { - if (copy_from_user(&(event.scan_code), buf, 1)) { + if (copy_from_user(&(event.scan_code), buf + i, 1)) { printk("Console Write fault\n"); return -EFAULT; } diff --git a/linux_module/iface-stream.c b/linux_module/iface-stream.c index 36582d9..9cb89e5 100644 --- a/linux_module/iface-stream.c +++ b/linux_module/iface-stream.c @@ -21,17 +21,20 @@ #include "iface-stream.h" -// This is going to need to be a lot bigger... -#define STREAM_BUF_SIZE 1024 +// This is probably overkill +#define STREAM_RING_LEN 4096 static struct list_head global_streams; -struct stream_buffer { + + +struct stream_state { char name[STREAM_NAME_LEN]; - struct ringbuf * buf; + + struct ringbuf * out_ring; int connected; @@ -40,19 +43,24 @@ struct stream_buffer { struct v3_guest * guest; struct list_head stream_node; + + struct v3_stream * v3_stream; }; // Currently just the list of open streams -struct vm_stream_state { +struct vm_global_streams { struct list_head open_streams; }; -static struct stream_buffer * find_stream_by_name(struct v3_guest * guest, const char * name) { - struct stream_buffer * stream = NULL; + + + +static struct stream_state * find_stream_by_name(struct v3_guest * guest, const char * name) { + struct stream_state * stream = NULL; struct list_head * stream_list = NULL; - struct vm_stream_state * vm_state = NULL; + struct vm_global_streams * vm_state = NULL; if (guest == NULL) { stream_list = &global_streams; @@ -78,28 +86,119 @@ static struct stream_buffer * find_stream_by_name(struct v3_guest * guest, const +#define TMP_BUF_LEN 128 + static ssize_t stream_read(struct file * filp, char __user * buf, size_t size, loff_t * offset) { - struct stream_buffer * stream = filp->private_data; + struct stream_state * stream = filp->private_data; + ssize_t bytes_read = 0; + ssize_t bytes_left = size; + unsigned long flags; + char tmp_buf[TMP_BUF_LEN]; + ssize_t total_bytes_left = 0; + + // memset(tmp_buf, 0, TMP_BUF_LEN); + + while (bytes_left > 0) { + int tmp_len = (TMP_BUF_LEN > bytes_left) ? bytes_left : TMP_BUF_LEN; + int tmp_read = 0; + + spin_lock_irqsave(&(stream->lock), flags); + tmp_read = ringbuf_read(stream->out_ring, tmp_buf, tmp_len); + spin_unlock_irqrestore(&(stream->lock), flags); + + if (tmp_read == 0) { + // If userspace reads more than we have + break; + } + + if (copy_to_user(buf + bytes_read, tmp_buf, tmp_read)) { + printk("Read Fault\n"); + return -EFAULT; + } + + bytes_left -= tmp_read; + bytes_read += tmp_read; + } - wait_event_interruptible(stream->intr_queue, (ringbuf_data_len(stream->buf) != 0)); - return ringbuf_read(stream->buf, buf, size); + spin_lock_irqsave(&(stream->lock), flags); + total_bytes_left = ringbuf_data_len(stream->out_ring); + spin_unlock_irqrestore(&(stream->lock), flags); + + if (total_bytes_left > 0) { + wake_up_interruptible(&(stream->intr_queue)); + } + + return bytes_read; } +static unsigned int +stream_poll(struct file * filp, struct poll_table_struct * poll_tb) { + struct stream_state * stream = filp->private_data; + unsigned int mask = POLLIN | POLLRDNORM; + unsigned long flags; + int data_avail = 0; + + poll_wait(filp, &(stream->intr_queue), poll_tb); + + spin_lock_irqsave(&(stream->lock), flags); + data_avail = ringbuf_data_len(stream->out_ring); + spin_unlock_irqrestore(&(stream->lock), flags); + + if (data_avail > 0) { + return mask; + } + return 0; + +} + +static ssize_t stream_write(struct file * filp, const char __user * buf, size_t size, loff_t * offset) { + struct stream_state * stream = filp->private_data; + char * kern_buf = NULL; + ssize_t bytes_written = 0; + + kern_buf = kmalloc(size, GFP_KERNEL); + + if (copy_from_user(kern_buf, buf, size)) { + printk("Stream Write Failed\n"); + return -EFAULT; + }; + + bytes_written = stream->v3_stream->input(stream->v3_stream, kern_buf, size); + + kfree(kern_buf); + + return bytes_written; +} + + +static int stream_release(struct inode * i, struct file * filp) { + struct stream_state * stream = filp->private_data; + unsigned long flags; + + spin_lock_irqsave(&(stream->lock), flags); + stream->connected = 0; + spin_unlock_irqrestore(&(stream->lock), flags); + + + return 0; + +} static struct file_operations stream_fops = { .read = stream_read, - // .release = stream_close, - // .poll = stream_poll, + .write = stream_write, + .release = stream_release, + .poll = stream_poll, }; -static void * palacios_stream_open(const char * name, void * private_data) { +static void * palacios_stream_open(struct v3_stream * v3_stream, const char * name, void * private_data) { struct v3_guest * guest = (struct v3_guest *)private_data; - struct stream_buffer * stream = NULL; - struct vm_stream_state * vm_state = NULL; + struct stream_state * stream = NULL; + struct vm_global_streams * vm_state = NULL; if (guest != NULL) { vm_state = get_vm_ext_data(guest, "STREAM_INTERFACE"); @@ -115,10 +214,13 @@ static void * palacios_stream_open(const char * name, void * private_data) { return NULL; } - stream = kmalloc(sizeof(struct stream_buffer), GFP_KERNEL); - - stream->buf = create_ringbuf(STREAM_BUF_SIZE); + stream = kmalloc(sizeof(struct stream_state), GFP_KERNEL); + memset(stream, 0, sizeof(struct stream_state)); + + stream->out_ring = create_ringbuf(STREAM_RING_LEN); + stream->v3_stream = v3_stream; stream->guest = guest; + stream->connected = 0; strncpy(stream->name, name, STREAM_NAME_LEN - 1); @@ -135,24 +237,38 @@ static void * palacios_stream_open(const char * name, void * private_data) { } -static int palacios_stream_write(void * stream_ptr, char * buf, int len) { - struct stream_buffer * stream = (struct stream_buffer *)stream_ptr; - int ret = 0; +static uint64_t palacios_stream_output(struct v3_stream * v3_stream, char * buf, int len) { + struct stream_state * stream = (struct stream_state *)v3_stream->host_stream_data; + int bytes_written = 0; + unsigned long flags; + - ret = ringbuf_write(stream->buf, buf, len); + if (stream->connected == 0) { + return 0; + } + + while (bytes_written < len) { + spin_lock_irqsave(&(stream->lock), flags); + bytes_written += ringbuf_write(stream->out_ring, buf + bytes_written, len - bytes_written); + spin_unlock_irqrestore(&(stream->lock), flags); - if (ret > 0) { wake_up_interruptible(&(stream->intr_queue)); + + if (bytes_written < len) { + // not enough space in ringbuffer, activate user space to drain it + schedule(); + } } - return ret; + + return bytes_written; } -static void palacios_stream_close(void * stream_ptr) { - struct stream_buffer * stream = (struct stream_buffer *)stream_ptr; +static void palacios_stream_close(struct v3_stream * v3_stream) { + struct stream_state * stream = (struct stream_state *)v3_stream->host_stream_data; - free_ringbuf(stream->buf); + free_ringbuf(stream->out_ring); list_del(&(stream->stream_node)); kfree(stream); @@ -160,7 +276,7 @@ static void palacios_stream_close(void * stream_ptr) { static struct v3_stream_hooks palacios_stream_hooks = { .open = palacios_stream_open, - .write = palacios_stream_write, + .output = palacios_stream_output, .close = palacios_stream_close, }; @@ -188,7 +304,7 @@ static int stream_deinit( void ) { static int stream_connect(struct v3_guest * guest, unsigned int cmd, unsigned long arg, void * priv_data) { void __user * argp = (void __user *)arg; - struct stream_buffer * stream = NULL; + struct stream_state * stream = NULL; int stream_fd = 0; char name[STREAM_NAME_LEN]; unsigned long flags = 0; @@ -221,7 +337,7 @@ static int stream_connect(struct v3_guest * guest, unsigned int cmd, unsigned lo } - stream_fd = anon_inode_getfd("v3-stream", &stream_fops, stream, 0); + stream_fd = anon_inode_getfd("v3-stream", &stream_fops, stream, O_RDWR); if (stream_fd < 0) { printk("Error creating stream inode for (%s)\n", name); @@ -235,12 +351,11 @@ static int stream_connect(struct v3_guest * guest, unsigned int cmd, unsigned lo static int guest_stream_init(struct v3_guest * guest, void ** vm_data) { - struct vm_stream_state * state = kmalloc(sizeof(struct vm_stream_state), GFP_KERNEL); + struct vm_global_streams * state = kmalloc(sizeof(struct vm_global_streams), GFP_KERNEL); INIT_LIST_HEAD(&(state->open_streams)); *vm_data = state; - add_guest_ctrl(guest, V3_VM_STREAM_CONNECT, stream_connect, state); return 0; @@ -248,7 +363,7 @@ static int guest_stream_init(struct v3_guest * guest, void ** vm_data) { static int guest_stream_deinit(struct v3_guest * guest, void * vm_data) { - struct vm_stream_state * state = vm_data; + struct vm_global_streams * state = vm_data; if (!list_empty(&(state->open_streams))) { printk("Error shutting down VM with open streams\n"); } diff --git a/linux_usr/Makefile b/linux_usr/Makefile index 1af1d8d..64b4672 100644 --- a/linux_usr/Makefile +++ b/linux_usr/Makefile @@ -1,4 +1,4 @@ -all: v3_ctrl v3_stop v3_cons v3_mem v3_monitor v3_serial v3_net v3_user_host_dev_example v3_os_debug v3_user_keyed_stream_example v3_user_keyed_stream_file +all: v3_ctrl v3_stop v3_cons v3_mem v3_monitor v3_stream v3_net v3_user_host_dev_example v3_os_debug v3_user_keyed_stream_example v3_user_keyed_stream_file @@ -15,8 +15,8 @@ v3_mem : v3_mem.c v3_ctrl.h v3_cons : v3_cons.c v3_ctrl.h gcc -static v3_cons.c -o v3_cons -lcurses -v3_serial : v3_serial.c v3_ctrl.h - gcc -static v3_serial.c -pthread -o v3_serial +v3_stream : v3_stream.c v3_ctrl.h + gcc -static v3_stream.c -o v3_stream v3_monitor : v3_cons.c v3_ctrl.h gcc -static v3_monitor.c -o v3_monitor @@ -38,4 +38,4 @@ v3_user_keyed_stream_file: v3_user_keyed_stream_file.c v3_user_keyed_stream.h v3 clean: - rm -f v3_ctrl v3_cons v3_mem v3_monitor v3_serial v3_user_host_dev_example v3_os_debug v3_user_keyed_stream_example v3_user_keyed_stream_file + rm -f v3_ctrl v3_cons v3_mem v3_monitor v3_stream v3_user_host_dev_example v3_os_debug v3_user_keyed_stream_example v3_user_keyed_stream_file diff --git a/linux_usr/v3_serial.c b/linux_usr/v3_serial.c deleted file mode 100644 index 2f66c7f..0000000 --- a/linux_usr/v3_serial.c +++ /dev/null @@ -1,101 +0,0 @@ -/* - * V3 Console utility - * (c) Jack lange & Lei Xia, 2010 - */ - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#include "v3_ctrl.h" - -static int cons_fd = -1; -static pthread_t input_handler; - -void *write_handler(void *val){ - char read; - printf("Write handler active\n"); - fflush(stdout); - while(1){ - read = getchar(); - if(write(cons_fd, &read, sizeof(char)) < 0){ - printf("WRITE ERROR"); - } - } -} - - -int main(int argc, char* argv[]) { - int vm_fd; - fd_set rset; - char *vm_dev = NULL; - char *stream; - - if (argc < 2) { - printf("Usage: ./v3_cons vm_device serial_number\n"); - return -1; - } - - vm_dev = argv[1]; - stream = argv[2]; - - vm_fd = open(vm_dev, O_RDONLY); - if (vm_fd == -1) { - printf("Error opening VM device: %s\n", vm_dev); - return -1; - } - - cons_fd = ioctl(vm_fd, V3_VM_SERIAL_CONNECT, stream); - - /* Close the file descriptor. */ - close(vm_fd); - if (cons_fd < 0) { - printf("Error opening stream Console\n"); - return -1; - } - - - if(pthread_create(&input_handler,0,write_handler,0)){ - perror("pthread_create"); - exit(-1); - } - - - while (1) { - int ret; - char cons_buf[1024]; - memset(cons_buf, 0, sizeof(cons_buf)); - int bytes_read = 0; - - FD_ZERO(&rset); - FD_SET(cons_fd, &rset); - - ret = select(cons_fd + 1, &rset, NULL, NULL, NULL); - - if (ret == 1) { - bytes_read = read(cons_fd, cons_buf, 1024); - cons_buf[bytes_read]='\0'; - printf("%s", cons_buf); - } else { - printf("v3_cons ERROR: select returned %d\n", ret); - return -1; - } - } - - - return 0; -} - - diff --git a/linux_usr/v3_stream.c b/linux_usr/v3_stream.c new file mode 100644 index 0000000..0a1f46a --- /dev/null +++ b/linux_usr/v3_stream.c @@ -0,0 +1,113 @@ +/* + * V3 Console utility + * (c) Jack lange & Lei Xia, 2010 + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "v3_ctrl.h" + +#define BUF_LEN 1025 +#define STREAM_NAME_LEN 128 + +int main(int argc, char* argv[]) { + int vm_fd; + fd_set rset; + char * vm_dev = NULL; + char stream[STREAM_NAME_LEN]; + char cons_buf[BUF_LEN]; + int stream_fd = 0; + + if (argc < 2) { + printf("Usage: ./v3_cons vm_device serial_number\n"); + return -1; + } + + vm_dev = argv[1]; + + if (strlen(argv[2]) >= STREAM_NAME_LEN) { + printf("ERROR: Stream name longer than maximum size (%d)\n", STREAM_NAME_LEN); + return -1; + } + + memcpy(stream, argv[2], strlen(argv[2])); + + vm_fd = open(vm_dev, O_RDONLY); + if (vm_fd == -1) { + printf("Error opening VM device: %s\n", vm_dev); + return -1; + } + + stream_fd = ioctl(vm_fd, V3_VM_SERIAL_CONNECT, stream); + + /* Close the file descriptor. */ + close(vm_fd); + + if (stream_fd < 0) { + printf("Error opening stream Console\n"); + return -1; + } + + while (1) { + int ret; + int bytes_read = 0; + char in_buf[512]; + + memset(cons_buf, 0, BUF_LEN); + + + FD_ZERO(&rset); + FD_SET(stream_fd, &rset); + FD_SET(STDIN_FILENO, &rset); + + ret = select(stream_fd + 1, &rset, NULL, NULL, NULL); + + if (ret == 0) { + continue; + } else if (ret == -1) { + perror("Select returned error\n"); + return -1; + } + + if (FD_ISSET(stream_fd, &rset)) { + + bytes_read = read(stream_fd, cons_buf, BUF_LEN - 1); + + cons_buf[bytes_read]='\0'; + printf("%s", cons_buf); + fflush(stdout); + + } else if (FD_ISSET(STDIN_FILENO, &rset)) { + fgets(in_buf, 512, stdin); + + if (write(stream_fd, in_buf, strlen(in_buf)) != strlen(in_buf)) { + fprintf(stderr, "Error sending input bufer\n"); + return -1; + } + } else { + printf("v3_cons ERROR: select returned %d\n", ret); + return -1; + } + + + } + + + return 0; +} + + diff --git a/palacios/include/devices/lnx_virtio_pci.h b/palacios/include/devices/lnx_virtio_pci.h index 5379479..e48df2c 100644 --- a/palacios/include/devices/lnx_virtio_pci.h +++ b/palacios/include/devices/lnx_virtio_pci.h @@ -40,11 +40,13 @@ #define VIRTIO_NET_SUBDEVICE_ID 1 #define VIRTIO_BLOCK_SUBDEVICE_ID 2 +#define VIRTIO_CONSOLE_SUBDEVICE_ID 3 #define VIRTIO_BALLOON_SUBDEVICE_ID 5 #define VIRTIO_SYMBIOTIC_SUBDEVICE_ID 10 #define VIRTIO_SYMMOD_SUBDEVICE_ID 11 #define VIRTIO_VNET_SUBDEVICE_ID 12 + #define HOST_FEATURES_PORT 0 #define GUEST_FEATURES_PORT 4 #define VRING_PG_NUM_PORT 8 diff --git a/palacios/include/interfaces/vmm_stream.h b/palacios/include/interfaces/vmm_stream.h index 513cf73..e9c49c7 100644 --- a/palacios/include/interfaces/vmm_stream.h +++ b/palacios/include/interfaces/vmm_stream.h @@ -23,24 +23,34 @@ +struct v3_stream { + void * host_stream_data; + void * guest_stream_data; + uint64_t (*input)(struct v3_stream * stream, uint8_t * buf, uint64_t len); +}; + + #ifdef __V3VEE__ #include -typedef void * v3_stream_t; + /* VM Can be NULL */ -v3_stream_t v3_stream_open(struct v3_vm_info * vm, const char * name); -int v3_stream_write(v3_stream_t stream, uint8_t * buf, uint32_t len); +struct v3_stream * v3_stream_open(struct v3_vm_info * vm, const char * name, + uint64_t (*input)(struct v3_stream * stream, uint8_t * buf, uint64_t len), + void * guest_stream_data); + +uint64_t v3_stream_output(struct v3_stream * stream, uint8_t * buf, uint32_t len); -void v3_stream_close(v3_stream_t stream); +void v3_stream_close(struct v3_stream * stream); #endif struct v3_stream_hooks { - void *(*open)(const char * name, void * private_data); - int (*write)(void * stream, char * buf, int len); - void (*close)(void * stream); + void *(*open)(struct v3_stream * stream, const char * name, void * host_vm_data); + uint64_t (*output)(struct v3_stream * stream, char * buf, int len); + void (*close)(struct v3_stream * stream); }; diff --git a/palacios/include/palacios/vmm_dev_mgr.h b/palacios/include/palacios/vmm_dev_mgr.h index 87f62fc..792aa0c 100644 --- a/palacios/include/palacios/vmm_dev_mgr.h +++ b/palacios/include/palacios/vmm_dev_mgr.h @@ -202,11 +202,11 @@ struct v3_dev_console_ops { struct v3_dev_char_ops { /* Backend implemented functions */ - int (*write)(uint8_t * buf, uint64_t len, void * private_data); + uint64_t (*output)(uint8_t * buf, uint64_t len, void * private_data); // int (*read)(uint8_t * buf, uint64_t len, void * private_data); /* Frontend Implemented functions */ - int (*push)(struct v3_vm_info * vm, uint8_t * buf, uint64_t len, void * private_data); + uint64_t (*input)(struct v3_vm_info * vm, uint8_t * buf, uint64_t len, void * private_data); }; diff --git a/palacios/src/devices/Kconfig b/palacios/src/devices/Kconfig index 575223b..758f2bd 100644 --- a/palacios/src/devices/Kconfig +++ b/palacios/src/devices/Kconfig @@ -153,6 +153,14 @@ config DEBUG_VIRTIO_SYM help Enable debugging for the Linux Virtio Symbiotic Device +config LINUX_VIRTIO_CONSOLE + bool "Enable Virtio Console Device" + default n + depends on PCI + help + Enable the Virtio Console + + config LINUX_VIRTIO_NET bool "Enable Virtio Network Device" default n diff --git a/palacios/src/devices/Makefile b/palacios/src/devices/Makefile index 0ab7f7f..1154059 100644 --- a/palacios/src/devices/Makefile +++ b/palacios/src/devices/Makefile @@ -15,6 +15,7 @@ obj-$(V3_CONFIG_LINUX_VIRTIO_BLOCK) += lnx_virtio_blk.o obj-$(V3_CONFIG_LINUX_VIRTIO_SYM) += lnx_virtio_sym.o obj-$(V3_CONFIG_LINUX_VIRTIO_NET) += lnx_virtio_nic.o obj-$(V3_CONFIG_LINUX_VIRTIO_VNET) += lnx_virtio_vnet.o +obj-$(V3_CONFIG_LINUX_VIRTIO_CONSOLE) += lnx_virtio_console.o obj-$(V3_CONFIG_VNET_NIC) += vnet_nic.o obj-$(V3_CONFIG_NVRAM) += nvram.o obj-$(V3_CONFIG_OS_DEBUG) += os_debug.o diff --git a/palacios/src/devices/char_stream.c b/palacios/src/devices/char_stream.c index f0c762d..2bde181 100644 --- a/palacios/src/devices/char_stream.c +++ b/palacios/src/devices/char_stream.c @@ -27,30 +27,27 @@ struct stream_state { - v3_stream_t stream; + struct v3_stream * stream; struct v3_dev_char_ops char_ops; + struct v3_vm_info * vm; + void * push_fn_arg; }; -static int serial_event_handler(struct v3_vm_info * vm, - struct v3_serial_event * evt, - void * private_data) { - struct stream_state * state = (struct stream_state *)private_data; +static uint64_t stream_input(struct v3_stream * stream, uint8_t * buf, uint64_t len) { + struct stream_state * state = stream->guest_stream_data; - if (state->char_ops.push != NULL){ - state->char_ops.push(vm, evt->data, evt->len, state->push_fn_arg); - } + return state->char_ops.input(state->vm, buf, len, state->push_fn_arg); - return 0; } -static int stream_write(uint8_t * buf, uint64_t length, void * private_data) { +static uint64_t stream_output(uint8_t * buf, uint64_t length, void * private_data) { struct stream_state * state = (struct stream_state *)private_data; - - return v3_stream_write(state->stream, buf, length); + + return v3_stream_output(state->stream, buf, length); } static int stream_free(struct stream_state * state) { @@ -93,8 +90,7 @@ static int stream_init(struct v3_vm_info * vm, v3_cfg_tree_t * cfg) { } - - state->stream = v3_stream_open(vm, stream_name); + state->stream = v3_stream_open(vm, stream_name, stream_input, state); if (state->stream == NULL) { PrintError("Could not open stream %s\n", stream_name); @@ -102,7 +98,8 @@ static int stream_init(struct v3_vm_info * vm, v3_cfg_tree_t * cfg) { return -1; } - state->char_ops.write = stream_write; + state->vm = vm; + state->char_ops.output = stream_output; if (v3_dev_connect_char(vm, v3_cfg_val(frontend_cfg, "tag"), &(state->char_ops), frontend_cfg, @@ -113,7 +110,7 @@ static int stream_init(struct v3_vm_info * vm, v3_cfg_tree_t * cfg) { return -1; } - v3_hook_host_event(vm, HOST_SERIAL_EVT, V3_HOST_EVENT_HANDLER(serial_event_handler), state); + return 0; } diff --git a/palacios/src/devices/lnx_virtio_console.c b/palacios/src/devices/lnx_virtio_console.c new file mode 100644 index 0000000..f7f2a11 --- /dev/null +++ b/palacios/src/devices/lnx_virtio_console.c @@ -0,0 +1,532 @@ +/* + * This file is part of the Palacios Virtual Machine Monitor developed + * by the V3VEE Project with funding from the United States National + * Science Foundation and the Department of Energy. + * + * The V3VEE Project is a joint project between Northwestern University + * and the University of New Mexico. You can find out more at + * http://www.v3vee.org + * + * Copyright (c) 2008, Jack Lange + * Copyright (c) 2008, The V3VEE Project + * All rights reserved. + * + * Author: Jack Lange + * + * This is free software. You are permitted to use, + * redistribute, and modify it as specified in the file "V3VEE_LICENSE". + */ + +#include +#include +#include +#include + +#include + + + +struct console_config { + uint16_t cols; + uint16_t rows; +} __attribute__((packed)); + + + + +#define QUEUE_SIZE 128 + +/* Host Feature flags */ +#define VIRTIO_CONSOLE_F_SIZE 0x1 + +struct virtio_console_state { + struct console_config cons_cfg; + struct virtio_config virtio_cfg; + + struct vm_device * pci_bus; + struct pci_device * pci_dev; + + struct virtio_queue queue[2]; + + struct v3_stream * stream; + + + struct virtio_queue * cur_queue; + + struct v3_vm_info * vm; + + int io_range_size; + + void * backend_data; + struct v3_dev_char_ops * ops; +}; + + +struct virtio_console_state * cons_state = NULL; + +static int virtio_reset(struct virtio_console_state * virtio) { + + memset(virtio->queue, 0, sizeof(struct virtio_queue) * 2); + + virtio->cur_queue = &(virtio->queue[0]); + + virtio->virtio_cfg.status = 0; + virtio->virtio_cfg.pci_isr = 0; + + /* Console configuration */ + // virtio->virtio_cfg.host_features = VIRTIO_NOTIFY_HOST; + + // Virtio Console uses two queues + virtio->queue[0].queue_size = QUEUE_SIZE; + virtio->queue[1].queue_size = QUEUE_SIZE; + + + memset(&(virtio->cons_cfg), 0, sizeof(struct console_config)); + + return 0; +} + +static int get_desc_count(struct virtio_queue * q, int index) { + struct vring_desc * tmp_desc = &(q->desc[index]); + int cnt = 1; + + while (tmp_desc->flags & VIRTIO_NEXT_FLAG) { + tmp_desc = &(q->desc[tmp_desc->next]); + cnt++; + } + + return cnt; +} + + +static int handle_kick(struct guest_info * core, struct virtio_console_state * virtio) { + struct virtio_queue * q = virtio->cur_queue; + + PrintDebug("VIRTIO CONSOLE KICK: cur_index=%d (mod=%d), avail_index=%d\n", + q->cur_avail_idx, q->cur_avail_idx % QUEUE_SIZE, q->avail->index); + + while (q->cur_avail_idx < q->avail->index) { + struct vring_desc * tmp_desc = NULL; + uint16_t desc_idx = q->avail->ring[q->cur_avail_idx % QUEUE_SIZE]; + int desc_cnt = get_desc_count(q, desc_idx); + int i = 0; + uint32_t req_len = 0; + + + PrintDebug("Descriptor Count=%d, index=%d\n", desc_cnt, q->cur_avail_idx % QUEUE_SIZE); + + for (i = 0; i < desc_cnt; i++) { + addr_t page_addr; + tmp_desc = &(q->desc[desc_idx]); + + + PrintDebug("Console output (ptr=%p) gpa=%p, len=%d, flags=%x, next=%d\n", + tmp_desc, + (void *)(addr_t)(tmp_desc->addr_gpa), tmp_desc->length, + tmp_desc->flags, tmp_desc->next); + + if (v3_gpa_to_hva(core, tmp_desc->addr_gpa, (addr_t *)&(page_addr)) == -1) { + PrintError("Could not translate block header address\n"); + return -1; + } + + virtio->ops->output((uint8_t *)page_addr, tmp_desc->length, virtio->backend_data); + + PrintDebug("Guest Console Currently Ignored\n"); + + req_len += tmp_desc->length; + desc_idx = tmp_desc->next; + } + + q->used->ring[q->used->index % QUEUE_SIZE].id = q->avail->ring[q->cur_avail_idx % QUEUE_SIZE]; + q->used->ring[q->used->index % QUEUE_SIZE].length = req_len; // What do we set this to???? + + q->used->index++; + q->cur_avail_idx++; + } + + if (!(q->avail->flags & VIRTIO_NO_IRQ_FLAG)) { + PrintDebug("Raising IRQ %d\n", virtio->pci_dev->config_header.intr_line); + v3_pci_raise_irq(virtio->pci_bus, 0, virtio->pci_dev); + virtio->virtio_cfg.pci_isr = VIRTIO_ISR_ACTIVE; + } + + return 0; +} + + +static uint64_t virtio_input(struct v3_vm_info * vm, uint8_t * buf, uint64_t len, void * private_data) { + struct virtio_console_state * cons_state = private_data; + struct virtio_queue * q = &(cons_state->queue[0]); + int xfer_len = 0; + + PrintDebug("VIRTIO CONSOLE Handle Input: cur_index=%d (mod=%d), avail_index=%d\n", + q->cur_avail_idx, q->cur_avail_idx % QUEUE_SIZE, q->avail->index); + + + if (q->cur_avail_idx != q->avail->index) { + uint16_t input_idx = q->avail->ring[q->cur_avail_idx % q->queue_size]; + struct vring_desc * input_desc = NULL; + uint8_t * input_buf = NULL; + + + input_desc = &(q->desc[input_idx]); + + if (v3_gpa_to_hva(&(cons_state->vm->cores[0]), input_desc->addr_gpa, (addr_t *)&(input_buf)) == -1) { + PrintError("Could not translate receive buffer address\n"); + return 0; + } + + memset(input_buf, 0, input_desc->length); + + xfer_len = (input_desc->length > len) ? len : input_desc->length; + + memcpy(input_buf, buf, xfer_len); + + + q->used->ring[q->used->index % q->queue_size].id = q->avail->ring[q->cur_avail_idx % q->queue_size]; + q->used->ring[q->used->index % q->queue_size].length = xfer_len; + + q->used->index++; + q->cur_avail_idx++; + } + + + // say hello + if (!(q->avail->flags & VIRTIO_NO_IRQ_FLAG)) { + v3_pci_raise_irq(cons_state->pci_bus, 0, cons_state->pci_dev); + cons_state->virtio_cfg.pci_isr = 0x1; + } + + return xfer_len; +} + + +static int virtio_io_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * private_data) { + struct virtio_console_state * virtio = (struct virtio_console_state *)private_data; + int port_idx = port % virtio->io_range_size; + + + PrintDebug("VIRTIO CONSOLE Write for port %d (index=%d) len=%d, value=%x\n", + port, port_idx, length, *(uint32_t *)src); + + + + switch (port_idx) { + case GUEST_FEATURES_PORT: + if (length != 4) { + PrintError("Illegal write length for guest features\n"); + return -1; + } + + virtio->virtio_cfg.guest_features = *(uint32_t *)src; + + break; + case VRING_PG_NUM_PORT: + if (length == 4) { + addr_t pfn = *(uint32_t *)src; + addr_t page_addr = (pfn << VIRTIO_PAGE_SHIFT); + + + virtio->cur_queue->pfn = pfn; + + virtio->cur_queue->ring_desc_addr = page_addr ; + virtio->cur_queue->ring_avail_addr = page_addr + (QUEUE_SIZE * sizeof(struct vring_desc)); + virtio->cur_queue->ring_used_addr = ( virtio->cur_queue->ring_avail_addr + \ + sizeof(struct vring_avail) + \ + (QUEUE_SIZE * sizeof(uint16_t))); + + // round up to next page boundary. + virtio->cur_queue->ring_used_addr = (virtio->cur_queue->ring_used_addr + 0xfff) & ~0xfff; + + if (v3_gpa_to_hva(core, virtio->cur_queue->ring_desc_addr, (addr_t *)&(virtio->cur_queue->desc)) == -1) { + PrintError("Could not translate ring descriptor address\n"); + return -1; + } + + + if (v3_gpa_to_hva(core, virtio->cur_queue->ring_avail_addr, (addr_t *)&(virtio->cur_queue->avail)) == -1) { + PrintError("Could not translate ring available address\n"); + return -1; + } + + + if (v3_gpa_to_hva(core, virtio->cur_queue->ring_used_addr, (addr_t *)&(virtio->cur_queue->used)) == -1) { + PrintError("Could not translate ring used address\n"); + return -1; + } + + PrintDebug("RingDesc_addr=%p, Avail_addr=%p, Used_addr=%p\n", + (void *)(virtio->cur_queue->ring_desc_addr), + (void *)(virtio->cur_queue->ring_avail_addr), + (void *)(virtio->cur_queue->ring_used_addr)); + + PrintDebug("RingDesc=%p, Avail=%p, Used=%p\n", + virtio->cur_queue->desc, virtio->cur_queue->avail, virtio->cur_queue->used); + + } else { + PrintError("Illegal write length for page frame number\n"); + return -1; + } + break; + case VRING_Q_SEL_PORT: + virtio->virtio_cfg.vring_queue_selector = *(uint16_t *)src; + + if (virtio->virtio_cfg.vring_queue_selector > 1) { + PrintError("Virtio Console device only uses 2 queue, selected %d\n", + virtio->virtio_cfg.vring_queue_selector); + return -1; + } + + virtio->cur_queue = &(virtio->queue[virtio->virtio_cfg.vring_queue_selector]); + + break; + case VRING_Q_NOTIFY_PORT: + PrintDebug("Handling Kick\n"); + if (handle_kick(core, virtio) == -1) { + PrintError("Could not handle Console Notification\n"); + return -1; + } + break; + case VIRTIO_STATUS_PORT: + virtio->virtio_cfg.status = *(uint8_t *)src; + + if (virtio->virtio_cfg.status == 0) { + PrintDebug("Resetting device\n"); + virtio_reset(virtio); + } + + break; + + case VIRTIO_ISR_PORT: + virtio->virtio_cfg.pci_isr = *(uint8_t *)src; + break; + default: + return -1; + break; + } + + return length; +} + + +static int virtio_io_read(struct guest_info * core, uint16_t port, void * dst, uint_t length, void * private_data) { + struct virtio_console_state * virtio = (struct virtio_console_state *)private_data; + int port_idx = port % virtio->io_range_size; + + + PrintDebug("VIRTIO CONSOLE Read for port %d (index =%d), length=%d\n", + port, port_idx, length); + + switch (port_idx) { + case HOST_FEATURES_PORT: + if (length != 4) { + PrintError("Illegal read length for host features\n"); + return -1; + } + + *(uint32_t *)dst = virtio->virtio_cfg.host_features; + + break; + case VRING_PG_NUM_PORT: + if (length != 4) { + PrintError("Illegal read length for page frame number\n"); + return -1; + } + + *(uint32_t *)dst = virtio->cur_queue->pfn; + + break; + case VRING_SIZE_PORT: + if (length != 2) { + PrintError("Illegal read length for vring size\n"); + return -1; + } + + *(uint16_t *)dst = virtio->cur_queue->queue_size; + + break; + + case VIRTIO_STATUS_PORT: + if (length != 1) { + PrintError("Illegal read length for status\n"); + return -1; + } + + *(uint8_t *)dst = virtio->virtio_cfg.status; + break; + + case VIRTIO_ISR_PORT: + *(uint8_t *)dst = virtio->virtio_cfg.pci_isr; + virtio->virtio_cfg.pci_isr = 0; + v3_pci_lower_irq(virtio->pci_bus, 0, virtio->pci_dev); + break; + + default: + if ( (port_idx >= sizeof(struct virtio_config)) && + (port_idx < (sizeof(struct virtio_config) + sizeof(struct console_config))) ) { + int cfg_offset = port_idx - sizeof(struct virtio_config); + uint8_t * cfg_ptr = (uint8_t *)&(virtio->cons_cfg); + + memcpy(dst, cfg_ptr + cfg_offset, length); + + } else { + PrintError("Read of Unhandled Virtio Read\n"); + return -1; + } + + break; + } + + return length; +} + + + + +static int connect_fn(struct v3_vm_info * vm, + void * frontend_data, + struct v3_dev_char_ops * ops, + v3_cfg_tree_t * cfg, + void * private_data, + void ** push_fn_arg) { + + struct virtio_console_state * state = (struct virtio_console_state *)frontend_data; + + state->ops = ops; + state->backend_data = private_data; + + state->ops->input = virtio_input; + *push_fn_arg = state; + + return 0; +} + +static int virtio_free(struct virtio_console_state * virtio) { + + // unregister from PCI + + V3_Free(virtio); + return 0; +} + + +static struct v3_device_ops dev_ops = { + .free = (int (*)(void *))virtio_free, + +}; + + + + +static int virtio_init(struct v3_vm_info * vm, v3_cfg_tree_t * cfg) { + struct vm_device * pci_bus = v3_find_dev(vm, v3_cfg_val(cfg, "bus")); + struct virtio_console_state * virtio_state = NULL; + struct pci_device * pci_dev = NULL; + char * dev_id = v3_cfg_val(cfg, "ID"); + + PrintDebug("Initializing VIRTIO Console device\n"); + + if (pci_bus == NULL) { + PrintError("VirtIO devices require a PCI Bus"); + return -1; + } + + + virtio_state = (struct virtio_console_state *)V3_Malloc(sizeof(struct virtio_console_state)); + memset(virtio_state, 0, sizeof(struct virtio_console_state)); + + + cons_state = virtio_state; + cons_state->vm = vm; + + + + struct vm_device * dev = v3_add_device(vm, dev_id, &dev_ops, virtio_state); + + if (dev == NULL) { + PrintError("Could not attach device %s\n", dev_id); + V3_Free(virtio_state); + return -1; + } + + // PCI initialization + { + struct v3_pci_bar bars[6]; + int num_ports = sizeof(struct virtio_config) + sizeof(struct console_config); + int tmp_ports = num_ports; + int i; + + + // This gets the number of ports, rounded up to a power of 2 + virtio_state->io_range_size = 1; // must be a power of 2 + + while (tmp_ports > 0) { + tmp_ports >>= 1; + virtio_state->io_range_size <<= 1; + } + + // this is to account for any low order bits being set in num_ports + // if there are none, then num_ports was already a power of 2 so we shift right to reset it + if ((num_ports & ((virtio_state->io_range_size >> 1) - 1)) == 0) { + virtio_state->io_range_size >>= 1; + } + + + for (i = 0; i < 6; i++) { + bars[i].type = PCI_BAR_NONE; + } + + bars[0].type = PCI_BAR_IO; + bars[0].default_base_port = -1; + bars[0].num_ports = virtio_state->io_range_size; + + bars[0].io_read = virtio_io_read; + bars[0].io_write = virtio_io_write; + bars[0].private_data = virtio_state; + + + pci_dev = v3_pci_register_device(pci_bus, PCI_STD_DEVICE, + 0, PCI_AUTO_DEV_NUM, 0, + "LNX_VIRTIO_CONSOLE", bars, + NULL, NULL, NULL, virtio_state); + + if (!pci_dev) { + PrintError("Could not register PCI Device\n"); + v3_remove_device(dev); + return -1; + } + + pci_dev->config_header.vendor_id = VIRTIO_VENDOR_ID; + pci_dev->config_header.subsystem_vendor_id = VIRTIO_SUBVENDOR_ID; + + + pci_dev->config_header.device_id = VIRTIO_CONSOLE_DEV_ID; + pci_dev->config_header.class = PCI_CLASS_DISPLAY; + pci_dev->config_header.subclass = PCI_DISPLAY_SUBCLASS_OTHER; + + pci_dev->config_header.subsystem_id = VIRTIO_CONSOLE_SUBDEVICE_ID; + + pci_dev->config_header.intr_pin = 1; + + pci_dev->config_header.max_latency = 1; // ?? (qemu does it...) + + + virtio_state->pci_dev = pci_dev; + virtio_state->pci_bus = pci_bus; + } + + virtio_reset(virtio_state); + + if (v3_dev_add_char_frontend(vm, dev_id, connect_fn, (void *)virtio_state) == -1) { + PrintError("Could not register %s as frontend\n", dev_id); + v3_remove_device(dev); + return -1; + } + + + return 0; +} + + +device_register("LNX_VIRTIO_CONSOLE", virtio_init) diff --git a/palacios/src/interfaces/vmm_stream.c b/palacios/src/interfaces/vmm_stream.c index 66ce081..377a545 100644 --- a/palacios/src/interfaces/vmm_stream.c +++ b/palacios/src/interfaces/vmm_stream.c @@ -28,28 +28,38 @@ static struct v3_stream_hooks * stream_hooks = NULL; // VM can be NULL -v3_stream_t v3_stream_open(struct v3_vm_info * vm, const char * name) { +struct v3_stream * v3_stream_open(struct v3_vm_info * vm, const char * name, + uint64_t (*input)(struct v3_stream * stream, uint8_t * buf, uint64_t len), + void * guest_stream_data) { + struct v3_stream * stream = NULL; + V3_ASSERT(stream_hooks != NULL); V3_ASSERT(stream_hooks->open != NULL); - return stream_hooks->open(name, vm->host_priv_data); + stream = V3_Malloc(sizeof(struct v3_stream *)); + + stream->input = input; + stream->guest_stream_data = guest_stream_data; + stream->host_stream_data = stream_hooks->open(stream, name, vm->host_priv_data); + + return stream; } -int v3_stream_write(v3_stream_t stream, uint8_t * buf, uint32_t len) { +uint64_t v3_stream_output(struct v3_stream * stream, uint8_t * buf, uint32_t len) { V3_ASSERT(stream_hooks != NULL); - V3_ASSERT(stream_hooks->write != NULL); + V3_ASSERT(stream_hooks->output != NULL); - return stream_hooks->write(stream, buf, len); + return stream_hooks->output(stream, buf, len); } -void v3_stream_close(v3_stream_t stream) { +void v3_stream_close(struct v3_stream * stream) { V3_ASSERT(stream_hooks != NULL); V3_ASSERT(stream_hooks->close != NULL); - return stream_hooks->close(stream); -} - + stream_hooks->close(stream); + V3_Free(stream); +}