diff --git a/Makefile b/Makefile index 22d200671..b25db42a5 100644 --- a/Makefile +++ b/Makefile @@ -101,6 +101,7 @@ OBJS+=bt-hci-csr.o OBJS+=buffered_file.o migration.o migration-tcp.o net.o qemu-sockets.o OBJS+=qemu-char.o aio.o net-checksum.o savevm.o cache-utils.o OBJS+=msmouse.o ps2.o +OBJS+=qdev.o ifdef CONFIG_BRLAPI OBJS+= baum.o diff --git a/Makefile.target b/Makefile.target index 30b159dc1..6bfd5fb1b 100644 --- a/Makefile.target +++ b/Makefile.target @@ -486,7 +486,7 @@ endif #CONFIG_BSD_USER ifndef CONFIG_USER_ONLY OBJS=vl.o osdep.o monitor.o pci.o loader.o isa_mmio.o machine.o dma-helpers.o \ - gdbstub.o gdbstub-xml.o + gdbstub.o gdbstub-xml.o sysbus.o # virtio has to be here due to weird dependency between PCI and virtio-net. # need to fix this properly OBJS+=virtio.o virtio-blk.o virtio-balloon.o virtio-net.o virtio-console.o diff --git a/hw/qdev.c b/hw/qdev.c new file mode 100644 index 000000000..eaa30f430 --- /dev/null +++ b/hw/qdev.c @@ -0,0 +1,237 @@ +/* + * Dynamic device configuration and creation. + * + * Copyright (c) 2009 CodeSourcery + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + */ + +/* The theory here is that it should be possible to create a machine without + knowledge of specific devices. Historically board init routines have + passed a bunch of arguments to each device, requiring the board know + exactly which device it is dealing with. This file provides an abstract + API for device configuration and initialization. Devices will generally + inherit from a particular bus (e.g. PCI or I2C) rather than + this API directly. */ + +#include "qdev.h" +#include "sysemu.h" + +struct DeviceProperty { + const char *name; + union { + int i; + void *ptr; + } value; + DeviceProperty *next; +}; + +struct DeviceType { + const char *name; + qdev_initfn init; + void *opaque; + int size; + DeviceType *next; +}; + +static DeviceType *device_type_list; + +/* Register a new device type. */ +DeviceType *qdev_register(const char *name, int size, qdev_initfn init, + void *opaque) +{ + DeviceType *t; + + assert(size >= sizeof(DeviceState)); + + t = qemu_mallocz(sizeof(DeviceType)); + t->next = device_type_list; + device_type_list = t; + t->name = qemu_strdup(name); + t->size = size; + t->init = init; + t->opaque = opaque; + + return t; +} + +/* Create a new device. This only initializes the device state structure + and allows properties to be set. qdev_init should be called to + initialize the actual device emulation. */ +DeviceState *qdev_create(void *bus, const char *name) +{ + DeviceType *t; + DeviceState *dev; + + for (t = device_type_list; t; t = t->next) { + if (strcmp(t->name, name) == 0) { + break; + } + } + if (!t) { + fprintf(stderr, "Unknown device '%s'\n", name); + exit(1); + } + + dev = qemu_mallocz(t->size); + dev->name = name; + dev->type = t; + dev->bus = bus; + return dev; +} + +/* Initialize a device. Device properties should be set before calling + this function. IRQs and MMIO regions should be connected/mapped after + calling this function. */ +void qdev_init(DeviceState *dev) +{ + dev->type->init(dev, dev->type->opaque); +} + +static DeviceProperty *create_prop(DeviceState *dev, const char *name) +{ + DeviceProperty *prop; + + /* TODO: Check for duplicate properties. */ + prop = qemu_mallocz(sizeof(*prop)); + prop->name = qemu_strdup(name); + prop->next = dev->props; + dev->props = prop; + + return prop; +} + +void qdev_set_prop_int(DeviceState *dev, const char *name, int value) +{ + DeviceProperty *prop; + + prop = create_prop(dev, name); + prop->value.i = value; +} + +void qdev_set_prop_ptr(DeviceState *dev, const char *name, void *value) +{ + DeviceProperty *prop; + + prop = create_prop(dev, name); + prop->value.ptr = value; +} + + +qemu_irq qdev_get_irq_sink(DeviceState *dev, int n) +{ + assert(n >= 0 && n < dev->num_irq_sink); + return dev->irq_sink[n]; +} + +/* Register device IRQ sinks. */ +void qdev_init_irq_sink(DeviceState *dev, qemu_irq_handler handler, int nirq) +{ + dev->num_irq_sink = nirq; + dev->irq_sink = qemu_allocate_irqs(handler, dev, nirq); +} + +/* Get a character (serial) device interface. */ +CharDriverState *qdev_init_chardev(DeviceState *dev) +{ + static int next_serial; + static int next_virtconsole; + /* FIXME: This is a nasty hack that needs to go away. */ + if (strncmp(dev->name, "virtio", 6) == 0) { + return virtcon_hds[next_virtconsole++]; + } else { + return serial_hds[next_serial++]; + } +} + +void *qdev_get_bus(DeviceState *dev) +{ + return dev->bus; +} + +static DeviceProperty *find_prop(DeviceState *dev, const char *name) +{ + DeviceProperty *prop; + + for (prop = dev->props; prop; prop = prop->next) { + if (strcmp(prop->name, name) == 0) { + return prop; + } + } + return NULL; +} + +uint64_t qdev_get_prop_int(DeviceState *dev, const char *name, uint64_t def) +{ + DeviceProperty *prop; + + prop = find_prop(dev, name); + if (!prop) + return def; + + return prop->value.i; +} + +void *qdev_get_prop_ptr(DeviceState *dev, const char *name) +{ + DeviceProperty *prop; + + prop = find_prop(dev, name); + assert(prop); + return prop->value.ptr; +} + +void qdev_init_gpio_in(DeviceState *dev, qemu_irq_handler handler, int n) +{ + assert(dev->num_gpio_in == 0); + dev->num_gpio_in = n; + dev->gpio_in = qemu_allocate_irqs(handler, dev, n); +} + +void qdev_init_gpio_out(DeviceState *dev, qemu_irq *pins, int n) +{ + assert(dev->num_gpio_out == 0); + dev->num_gpio_out = n; + dev->gpio_out = pins; +} + +qemu_irq qdev_get_gpio_in(DeviceState *dev, int n) +{ + assert(n >= 0 && n < dev->num_gpio_in); + return dev->gpio_in[n]; +} + +void qdev_connect_gpio_out(DeviceState * dev, int n, qemu_irq pin) +{ + assert(n >= 0 && n < dev->num_gpio_out); + dev->gpio_out[n] = pin; +} + +static int next_block_unit[IF_COUNT]; + +/* Get a block device. This should only be used for single-drive devices + (e.g. SD/Floppy/MTD). Multi-disk devices (scsi/ide) should use the + appropriate bus. */ +BlockDriverState *qdev_init_bdrv(DeviceState *dev, BlockInterfaceType type) +{ + int unit = next_block_unit[type]++; + int index; + + index = drive_get_index(type, 0, unit); + if (index == -1) { + return NULL; + } + return drives_table[index].bdrv; +} diff --git a/hw/qdev.h b/hw/qdev.h new file mode 100644 index 000000000..6e3e5ec7b --- /dev/null +++ b/hw/qdev.h @@ -0,0 +1,67 @@ +#ifndef QDEV_H +#define QDEV_H + +#include "hw.h" + +typedef struct DeviceType DeviceType; + +typedef struct DeviceProperty DeviceProperty; + +/* This structure should not be accessed directly. We declare it here + so that it can be embedded in individual device state structures. */ +struct DeviceState +{ + const char *name; + DeviceType *type; + void *bus; + DeviceProperty *props; + int num_irq_sink; + qemu_irq *irq_sink; + int num_gpio_out; + qemu_irq *gpio_out; + int num_gpio_in; + qemu_irq *gpio_in; +}; + +/*** Board API. This should go away once we have a machine config file. ***/ + +DeviceState *qdev_create(void *bus, const char *name); +void qdev_init(DeviceState *dev); + +/* Set properties between creation and init. */ +void qdev_set_prop_int(DeviceState *dev, const char *name, int value); +void qdev_set_prop_ptr(DeviceState *dev, const char *name, void *value); + +qemu_irq qdev_get_irq_sink(DeviceState *dev, int n); +qemu_irq qdev_get_gpio_in(DeviceState *dev, int n); +void qdev_connect_gpio_out(DeviceState *dev, int n, qemu_irq pin); + +/*** Device API. ***/ + +typedef void (*qdev_initfn)(DeviceState *dev, void *opaque); + +DeviceType *qdev_register(const char *name, int size, qdev_initfn init, + void *opaque); + +/* Register device properties. */ +void qdev_init_irq_sink(DeviceState *dev, qemu_irq_handler handler, int nirq); +void qdev_init_gpio_in(DeviceState *dev, qemu_irq_handler handler, int n); +void qdev_init_gpio_out(DeviceState *dev, qemu_irq *pins, int n); + +CharDriverState *qdev_init_chardev(DeviceState *dev); + +void *qdev_get_bus(DeviceState *dev); +uint64_t qdev_get_prop_int(DeviceState *dev, const char *name, uint64_t def); +void *qdev_get_prop_ptr(DeviceState *dev, const char *name); + +/* Convery from a base type to a parent type, with compile time checking. */ +#ifdef __GNUC__ +#define DO_UPCAST(type, field, dev) ( __extension__ ( { \ + char __attribute__((unused)) offset_must_be_zero[ \ + -offsetof(type, field)]; \ + container_of(dev, type, field);})) +#else +#define DO_UPCAST(type, field, dev) container_of(dev, type, field) +#endif + +#endif diff --git a/hw/sysbus.c b/hw/sysbus.c new file mode 100644 index 000000000..e6cb7dd78 --- /dev/null +++ b/hw/sysbus.c @@ -0,0 +1,139 @@ +/* + * System (CPU) Bus device support code + * + * Copyright (c) 2009 CodeSourcery + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + */ + +#include "sysbus.h" +#include "sysemu.h" + +void sysbus_connect_irq(SysBusDevice *dev, int n, qemu_irq irq) +{ + assert(n >= 0 && n < dev->num_irq); + dev->irqs[n] = 0; + if (dev->irqp[n]) { + *dev->irqp[n] = irq; + } +} + +void sysbus_mmio_map(SysBusDevice *dev, int n, target_phys_addr_t addr) +{ + assert(n >= 0 && n < dev->num_mmio); + + if (dev->mmio[n].addr == addr) { + /* ??? region already mapped here. */ + return; + } + if (dev->mmio[n].addr != (target_phys_addr_t)-1) { + /* Unregister previous mapping. */ + cpu_register_physical_memory(dev->mmio[n].addr, dev->mmio[n].size, + IO_MEM_UNASSIGNED); + } + dev->mmio[n].addr = addr; + if (dev->mmio[n].cb) { + dev->mmio[n].cb(dev, addr); + } else { + cpu_register_physical_memory(addr, dev->mmio[n].size, + dev->mmio[n].iofunc); + } +} + + +/* Request an IRQ source. The actual IRQ object may be populated later. */ +void sysbus_init_irq(SysBusDevice *dev, qemu_irq *p) +{ + int n; + + assert(dev->num_irq < QDEV_MAX_IRQ); + n = dev->num_irq++; + dev->irqp[n] = p; +} + +/* Pass IRQs from a target device. */ +void sysbus_pass_irq(SysBusDevice *dev, SysBusDevice *target) +{ + int i; + assert(dev->num_irq == 0); + dev->num_irq = target->num_irq; + for (i = 0; i < dev->num_irq; i++) { + dev->irqp[i] = target->irqp[i]; + } +} + +void sysbus_init_mmio(SysBusDevice *dev, target_phys_addr_t size, int iofunc) +{ + int n; + + assert(dev->num_mmio < QDEV_MAX_MMIO); + n = dev->num_mmio++; + dev->mmio[n].addr = -1; + dev->mmio[n].size = size; + dev->mmio[n].iofunc = iofunc; +} + +void sysbus_init_mmio_cb(SysBusDevice *dev, target_phys_addr_t size, + mmio_mapfunc cb) +{ + int n; + + assert(dev->num_mmio < QDEV_MAX_MMIO); + n = dev->num_mmio++; + dev->mmio[n].addr = -1; + dev->mmio[n].size = size; + dev->mmio[n].cb = cb; +} + +static void sysbus_device_init(DeviceState *dev, void *opaque) +{ + sysbus_initfn init = (sysbus_initfn)opaque; + + init(sysbus_from_qdev(dev)); +} + +void sysbus_register_dev(const char *name, size_t size, sysbus_initfn init) +{ + assert(size >= sizeof(SysBusDevice)); + qdev_register(name, size, sysbus_device_init, init); +} + +DeviceState *sysbus_create_varargs(const char *name, + target_phys_addr_t addr, ...) +{ + DeviceState *dev; + SysBusDevice *s; + va_list va; + qemu_irq irq; + int n; + + dev = qdev_create(NULL, name); + s = sysbus_from_qdev(dev); + qdev_init(dev); + if (addr != (target_phys_addr_t)-1) { + sysbus_mmio_map(s, 0, addr); + } + va_start(va, addr); + n = 0; + while (1) { + irq = va_arg(va, qemu_irq); + if (!irq) { + break; + } + sysbus_connect_irq(s, n, irq); + n++; + } + return dev; +} diff --git a/hw/sysbus.h b/hw/sysbus.h new file mode 100644 index 000000000..44ed792d2 --- /dev/null +++ b/hw/sysbus.h @@ -0,0 +1,56 @@ +#ifndef HW_SYSBUS_H +#define HW_SYSBUS_H 1 + +/* Devices attached directly to the main system bus. */ + +#include "qdev.h" + +#define QDEV_MAX_MMIO 5 +#define QDEV_MAX_IRQ 32 + +typedef struct SysBusDevice SysBusDevice; +typedef void (*mmio_mapfunc)(SysBusDevice *dev, target_phys_addr_t addr); + +struct SysBusDevice { + DeviceState qdev; + int num_irq; + qemu_irq irqs[QDEV_MAX_IRQ]; + qemu_irq *irqp[QDEV_MAX_IRQ]; + int num_mmio; + struct { + target_phys_addr_t addr; + target_phys_addr_t size; + mmio_mapfunc cb; + int iofunc; + } mmio[QDEV_MAX_MMIO]; +}; + +typedef void (*sysbus_initfn)(SysBusDevice *dev); + +/* Macros to compensate for lack of type inheritance in C. */ +#define sysbus_from_qdev(dev) ((SysBusDevice *)(dev)) +#define FROM_SYSBUS(type, dev) DO_UPCAST(type, busdev, dev) + +void sysbus_register_dev(const char *name, size_t size, sysbus_initfn init); +void *sysbus_new(void); +void sysbus_init_mmio(SysBusDevice *dev, target_phys_addr_t size, int iofunc); +void sysbus_init_mmio_cb(SysBusDevice *dev, target_phys_addr_t size, + mmio_mapfunc cb); +void sysbus_init_irq(SysBusDevice *dev, qemu_irq *p); +void sysbus_pass_irq(SysBusDevice *dev, SysBusDevice *target); + + +void sysbus_connect_irq(SysBusDevice *dev, int n, qemu_irq irq); +void sysbus_mmio_map(SysBusDevice *dev, int n, target_phys_addr_t addr); + +/* Legacy helper function for creating devices. */ +DeviceState *sysbus_create_varargs(const char *name, + target_phys_addr_t addr, ...); +static inline DeviceState *sysbus_create_simple(const char *name, + target_phys_addr_t addr, + qemu_irq irq) +{ + return sysbus_create_varargs(name, addr, irq, NULL); +} + +#endif /* !HW_SYSBUS_H */ diff --git a/qemu-common.h b/qemu-common.h index 5f7f275d0..9d644b68b 100644 --- a/qemu-common.h +++ b/qemu-common.h @@ -182,6 +182,7 @@ typedef struct PCMCIACardState PCMCIACardState; typedef struct MouseTransformInfo MouseTransformInfo; typedef struct uWireSlave uWireSlave; typedef struct I2SCodec I2SCodec; +typedef struct DeviceState DeviceState; /* CPU save/load. */ void cpu_save(QEMUFile *f, void *opaque); diff --git a/sysemu.h b/sysemu.h index 25a7c32a7..2ff61946f 100644 --- a/sysemu.h +++ b/sysemu.h @@ -132,7 +132,8 @@ extern unsigned int nb_prom_envs; #endif typedef enum { - IF_IDE, IF_SCSI, IF_FLOPPY, IF_PFLASH, IF_MTD, IF_SD, IF_VIRTIO, IF_XEN + IF_IDE, IF_SCSI, IF_FLOPPY, IF_PFLASH, IF_MTD, IF_SD, IF_VIRTIO, IF_XEN, + IF_COUNT } BlockInterfaceType; typedef enum { @@ -165,6 +166,8 @@ extern void drive_remove(int index); extern const char *drive_get_serial(BlockDriverState *bdrv); extern BlockInterfaceErrorAction drive_get_onerror(BlockDriverState *bdrv); +BlockDriverState *qdev_init_bdrv(DeviceState *dev, BlockInterfaceType type); + struct drive_opt { const char *file; char opt[1024]; @@ -259,4 +262,6 @@ int get_param_value(char *buf, int buf_size, const char *tag, const char *str); int check_params(const char * const *params, const char *str); +void register_devices(void); + #endif diff --git a/vl.c b/vl.c index b43cdd164..40b1d8bf4 100644 --- a/vl.c +++ b/vl.c @@ -2565,6 +2565,8 @@ int drive_init(struct drive_opt *arg, int snapshot, void *opaque) case IF_MTD: case IF_VIRTIO: break; + case IF_COUNT: + abort(); } if (!file[0]) return -2; @@ -5896,6 +5898,8 @@ int main(int argc, char **argv, char **envp) } } + module_call_init(MODULE_INIT_DEVICE); + machine->init(ram_size, boot_devices, kernel_filename, kernel_cmdline, initrd_filename, cpu_model);