qemu/hw/s390-virtio.c

336 lines
9.4 KiB
C
Raw Normal View History

/*
* QEMU S390 virtio target
*
* Copyright (c) 2009 Alexander Graf <agraf@suse.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "hw.h"
#include "block.h"
#include "blockdev.h"
#include "sysemu.h"
#include "net.h"
#include "boards.h"
#include "monitor.h"
#include "loader.h"
#include "elf.h"
#include "hw/virtio.h"
#include "hw/sysbus.h"
#include "kvm.h"
#include "exec-memory.h"
#include "hw/s390-virtio-bus.h"
//#define DEBUG_S390
#ifdef DEBUG_S390
#define dprintf(fmt, ...) \
do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
#else
#define dprintf(fmt, ...) \
do { } while (0)
#endif
#define KVM_S390_VIRTIO_NOTIFY 0
#define KVM_S390_VIRTIO_RESET 1
#define KVM_S390_VIRTIO_SET_STATUS 2
#define KERN_IMAGE_START 0x010000UL
#define KERN_PARM_AREA 0x010480UL
#define INITRD_START 0x800000UL
#define INITRD_PARM_START 0x010408UL
#define INITRD_PARM_SIZE 0x010410UL
#define PARMFILE_START 0x001000UL
#define ZIPL_START 0x009000UL
#define ZIPL_LOAD_ADDR 0x009000UL
#define ZIPL_FILENAME "s390-zipl.rom"
#define MAX_BLK_DEVS 10
static VirtIOS390Bus *s390_bus;
static CPUState **ipi_states;
CPUState *s390_cpu_addr2state(uint16_t cpu_addr)
{
if (cpu_addr >= smp_cpus) {
return NULL;
}
return ipi_states[cpu_addr];
}
int s390_virtio_hypercall(CPUState *env, uint64_t mem, uint64_t hypercall)
{
int r = 0, i;
dprintf("KVM hypercall: %ld\n", hypercall);
switch (hypercall) {
case KVM_S390_VIRTIO_NOTIFY:
if (mem > ram_size) {
VirtIOS390Device *dev = s390_virtio_bus_find_vring(s390_bus,
mem, &i);
if (dev) {
virtio_queue_notify(dev->vdev, i);
} else {
r = -EINVAL;
}
} else {
/* Early printk */
}
break;
case KVM_S390_VIRTIO_RESET:
{
VirtIOS390Device *dev;
dev = s390_virtio_bus_find_mem(s390_bus, mem);
virtio_reset(dev->vdev);
s390: fix reset hypercall to reset the status This patch fixes the reset hypercall which is supposed to also reset the device status in device memory. This fixes the following bug: [root@localhost driver]# echo virtio0 > unbind [ 35.056966] ------------[ cut here ]------------ [ 35.057054] kernel BUG at drivers/virtio/virtio.c:157! [ 35.057113] illegal operation: 0001 [#1] SMP [ 35.057181] Modules linked in: [ 35.057243] CPU: 0 Not tainted 3.0.0-rc1-00180-g0792644-dirty #51 [ 35.057323] Process bash (pid: 497, task: 000000003e58c538, ksp: 000000003ef43978) [ 35.057409] Krnl PSW : 0704100180000000 00000000003d46f8 (virtio_check_driver_offered_feature+0x0/0x38) [ 35.057528] R:0 T:1 IO:1 EX:1 Key:0 M:1 W:0 P:0 AS:0 CC:1 PM:0 EA:3 [ 35.057616] Krnl GPRS: 0000000000000000 0000000040000000 0000000000000007 0000000000000000 [ 35.057716] 00000000003b3be4 0000000000000001 000000003ef4d380 000000003f1cff00 [ 35.057805] 000000003ef43f18 00000000005ca620 0000000000000008 0000000000838e88 [ 35.057919] 000000000083c860 000000003f7c2e00 00000000003d46b0 000000003ef43d10 [ 35.058027] Krnl Code: 00000000003d46e8: f0b00004ebcf srp 4(12,%r0),3023(%r14),0 [ 35.058115] 00000000003d46ee: f0a0000407f4 srp 4(11,%r0),2036,0 [ 35.058207] 00000000003d46f4: a7f40001 brc 15,3d46f6 [ 35.058295] >00000000003d46f8: e31020900004 lg %r1,144(%r2) [ 35.058383] 00000000003d46fe: bf2f1080 icm %r2,15,128(%r1) [ 35.058470] 00000000003d4702: a784000d brc 8,3d471c [ 35.058557] 00000000003d4706: e32010780004 lg %r2,120(%r1) [ 35.058645] 00000000003d470c: 59302000 c %r3,0(%r2) [ 35.058748] Call Trace: [ 35.058777] ([<00000000003d469e>] virtio_dev_remove+0x36/0x90) [ 35.058852] [<00000000003f3a40>] __device_release_driver+0x7c/0xec [ 35.058936] [<00000000003f3ae8>] device_release_driver+0x38/0x48 [ 35.059023] [<00000000003f2a98>] driver_unbind+0xa4/0xc4 [ 35.059111] [<00000000002acb70>] sysfs_write_file+0xe8/0x19c [ 35.059226] [<000000000022e7a4>] vfs_write+0xb0/0x18c [ 35.059317] [<000000000022eb18>] SyS_write+0x58/0xb4 [ 35.059398] [<000000000057e674>] sysc_noemu+0x16/0x1c [ 35.059475] [<000003fffd44b6c0>] 0x3fffd44b6c0 [ 35.059531] Last Breaking-Event-Address: [ 35.059576] [<00000000003d46f4>] virtio_dev_remove+0x8c/0x90 [ 35.059646] [ 35.059661] ---[ end trace 9b1959188f21ee11 ]--- Signed-off-by: Christian Borntraeger<borntraeger@de.ibm.com> Signed-off-by: Alexander Graf <agraf@suse.de>
2011-09-15 02:22:19 +04:00
stb_phys(dev->dev_offs + VIRTIO_DEV_OFFS_STATUS, 0);
s390_virtio_device_sync(dev);
break;
}
case KVM_S390_VIRTIO_SET_STATUS:
{
VirtIOS390Device *dev;
dev = s390_virtio_bus_find_mem(s390_bus, mem);
if (dev) {
s390_virtio_device_update_status(dev);
} else {
r = -EINVAL;
}
break;
}
default:
r = -EINVAL;
break;
}
return r;
}
/*
* The number of running CPUs. On s390 a shutdown is the state of all CPUs
* being either stopped or disabled (for interrupts) waiting. We have to
* track this number to call the shutdown sequence accordingly. This
* number is modified either on startup or while holding the big qemu lock.
*/
static unsigned s390_running_cpus;
void s390_add_running_cpu(CPUState *env)
{
if (env->halted) {
s390_running_cpus++;
env->halted = 0;
env->exception_index = -1;
}
}
unsigned s390_del_running_cpu(CPUState *env)
{
if (env->halted == 0) {
assert(s390_running_cpus >= 1);
s390_running_cpus--;
env->halted = 1;
env->exception_index = EXCP_HLT;
}
return s390_running_cpus;
}
/* PC hardware initialisation */
static void s390_init(ram_addr_t my_ram_size,
const char *boot_device,
const char *kernel_filename,
const char *kernel_cmdline,
const char *initrd_filename,
const char *cpu_model)
{
CPUState *env = NULL;
MemoryRegion *sysmem = get_system_memory();
MemoryRegion *ram = g_new(MemoryRegion, 1);
ram_addr_t kernel_size = 0;
ram_addr_t initrd_offset;
ram_addr_t initrd_size = 0;
int shift = 0;
uint8_t *storage_keys;
void *virtio_region;
target_phys_addr_t virtio_region_len;
target_phys_addr_t virtio_region_start;
int i;
/* s390x ram size detection needs a 16bit multiplier + an increment. So
guests > 64GB can be specified in 2MB steps etc. */
while ((my_ram_size >> (20 + shift)) > 65535) {
shift++;
}
my_ram_size = my_ram_size >> (20 + shift) << (20 + shift);
/* lets propagate the changed ram size into the global variable. */
ram_size = my_ram_size;
/* get a BUS */
s390_bus = s390_virtio_bus_init(&my_ram_size);
/* allocate RAM */
memory_region_init_ram(ram, "s390.ram", my_ram_size);
vmstate_register_ram_global(ram);
memory_region_add_subregion(sysmem, 0, ram);
/* clear virtio region */
virtio_region_len = my_ram_size - ram_size;
virtio_region_start = ram_size;
virtio_region = cpu_physical_memory_map(virtio_region_start,
&virtio_region_len, true);
memset(virtio_region, 0, virtio_region_len);
cpu_physical_memory_unmap(virtio_region, virtio_region_len, 1,
virtio_region_len);
/* allocate storage keys */
storage_keys = g_malloc0(my_ram_size / TARGET_PAGE_SIZE);
/* init CPUs */
if (cpu_model == NULL) {
cpu_model = "host";
}
ipi_states = g_malloc(sizeof(CPUState *) * smp_cpus);
for (i = 0; i < smp_cpus; i++) {
CPUState *tmp_env;
tmp_env = cpu_init(cpu_model);
if (!env) {
env = tmp_env;
}
ipi_states[i] = tmp_env;
tmp_env->halted = 1;
tmp_env->exception_index = EXCP_HLT;
tmp_env->storage_keys = storage_keys;
}
/* One CPU has to run */
s390_add_running_cpu(env);
if (kernel_filename) {
kernel_size = load_elf(kernel_filename, NULL, NULL, NULL, NULL,
NULL, 1, ELF_MACHINE, 0);
if (kernel_size == -1UL) {
kernel_size = load_image_targphys(kernel_filename, 0, ram_size);
}
/*
* we can not rely on the ELF entry point, since up to 3.2 this
* value was 0x800 (the SALIPL loader) and it wont work. For
* all (Linux) cases 0x10000 (KERN_IMAGE_START) should be fine.
*/
env->psw.addr = KERN_IMAGE_START;
env->psw.mask = 0x0000000180000000ULL;
} else {
ram_addr_t bios_size = 0;
char *bios_filename;
/* Load zipl bootloader */
if (bios_name == NULL) {
bios_name = ZIPL_FILENAME;
}
bios_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
bios_size = load_image_targphys(bios_filename, ZIPL_LOAD_ADDR, 4096);
g_free(bios_filename);
if ((long)bios_size < 0) {
hw_error("could not load bootloader '%s'\n", bios_name);
}
if (bios_size > 4096) {
hw_error("stage1 bootloader is > 4k\n");
}
env->psw.addr = ZIPL_START;
env->psw.mask = 0x0000000180000000ULL;
}
if (initrd_filename) {
initrd_offset = INITRD_START;
while (kernel_size + 0x100000 > initrd_offset) {
initrd_offset += 0x100000;
}
initrd_size = load_image_targphys(initrd_filename, initrd_offset,
ram_size - initrd_offset);
/* we have to overwrite values in the kernel image, which are "rom" */
memcpy(rom_ptr(INITRD_PARM_START), &initrd_offset, 8);
memcpy(rom_ptr(INITRD_PARM_SIZE), &initrd_size, 8);
}
if (kernel_cmdline) {
/* we have to overwrite values in the kernel image, which are "rom" */
memcpy(rom_ptr(KERN_PARM_AREA), kernel_cmdline,
strlen(kernel_cmdline) + 1);
}
/* Create VirtIO network adapters */
for(i = 0; i < nb_nics; i++) {
NICInfo *nd = &nd_table[i];
DeviceState *dev;
if (!nd->model) {
nd->model = g_strdup("virtio");
}
if (strcmp(nd->model, "virtio")) {
fprintf(stderr, "S390 only supports VirtIO nics\n");
exit(1);
}
dev = qdev_create((BusState *)s390_bus, "virtio-net-s390");
qdev_set_nic_properties(dev, nd);
qdev_init_nofail(dev);
}
/* Create VirtIO disk drives */
for(i = 0; i < MAX_BLK_DEVS; i++) {
DriveInfo *dinfo;
DeviceState *dev;
dinfo = drive_get(IF_IDE, 0, i);
if (!dinfo) {
continue;
}
dev = qdev_create((BusState *)s390_bus, "virtio-blk-s390");
qdev_prop_set_drive_nofail(dev, "drive", dinfo->bdrv);
qdev_init_nofail(dev);
}
}
static QEMUMachine s390_machine = {
.name = "s390-virtio",
.alias = "s390",
.desc = "VirtIO based S390 machine",
.init = s390_init,
.no_serial = 1,
.no_parallel = 1,
.use_virtcon = 1,
.max_cpus = 255,
.is_default = 1,
};
static void s390_machine_init(void)
{
qemu_register_machine(&s390_machine);
}
machine_init(s390_machine_init);