qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [RFC] Machine description as data


From: Markus Armbruster
Subject: [Qemu-devel] [RFC] Machine description as data
Date: Wed, 11 Feb 2009 16:40:08 +0100
User-agent: Gnus/5.11 (Gnus v5.11) Emacs/22.3 (gnu/linux)

Sorry for the length of this memo.  I tried to make it as concise as I
could.  And there's working mock-up source code to go with it.


Configuration should be data
----------------------------

A QEMU machine (selected with -M) is described by a struct QEMUMachine.
Which contains almost nothing of interest.  Pretty much everything,
including all the buses and devices is instead created by the machine's
initialization function.

Init functions consider a plethora of ad hoc configuration parameters
set by command line options.  Plenty of stuff remains hard-coded all
the same.

Configuration should be data, not code.

A machine's buses and devices can be expressed as a device tree.  More
on that below.

The need for a configuration file
---------------------------------

The command line is a rather odd place to define a virtual machine.
Command line is fine for manipulating a particular run of the machine,
but the machine description belongs into a configuration file.

Once configuration is data, we should be able to initialize it from a
configuration file with relative ease.

However, this memo is only about the *internal* representation of
configuration.  How we get there from a configuration file is a separate
question.  It's without doubt a relevant question, but I feel I need to
limit my scope to have a chance of getting anywhere.

The need for an abstract device interface
-----------------------------------------

Currently, each virtual device is created, configured and initialized in
its own idiosyncratic way.  Some configuration is received as arguments,
some is passed in global variables.

This is workable as long as the machine is constructed by ad hoc init
function code.  The resulting init function tends to be quite a
hairball, though.

I'd like to propose an abstract device interface, so we can build a
machine from its (tree-structured) configuration using just this
interface.  Device idiosyncrasies are to be hidden in the driver code
behind the interface.

What I propose to do
--------------------

A. Configuration as data

   Define an internal machine configuration data structure.  Needs to be
   sufficiently generic to be able to support even oddball machine
   types.  Make it a decorated tree, i.e. a tree of named nodes with
   named properties.

   Create an instance for a prototype machine type.  Make it a PC,
   because that's the easiest to test.

   Define an abstract device interface, initially covering just device
   configuration and initialization.

   Implement the device interface for the devices used by the prototype
   machine type.

   Do not break existing machine types here.  This means we need to keep
   legacy interfaces until their last user is gone (step B).  Could
   become somewhat messy in places for a while.

B. Convert all the existing machine configurations to data.

   This can and should be done incrementally, each machine by people who
   care and know about it.

   Clean up the legacy interfaces now unused, and any messes we made
   behind them.

C. Read (and maybe write) machine configuration

   The external format to use is debatable.  Compared to the rest of the
   task, its choice looks like detail to me, but I'm biased ;)

   Writing the data could be useful for debugging.

D. Command line options to modify the configuration tree

   If we want them.

E. Make legacy command line modify the configuration tree

   For compatibility.  This is my "favourite" part.

We need to start with A.  The other tasks are largely independent.

What I've already done
----------------------

Show me the code, they say.  Find attached a working prototype of step
A.  It passes the "Linux boots" test for me.  I didn't bother to rebase
to current HEAD, happy do to that on request.

Instead of hacking up machine "pc", I created a new machine "pcdt".  I
took a number of shortcuts:

* I put the "pcdt" code into the new file dt.c, and copied code from
  pc.c there.  I could have avoided that by putting my code in pc.c
  instead.  Putting it in a new file helped me pick apart the pc.c
  hairball.  To be cleaned up.

* I copied code from net.c.  Trivial to fix, just give it external
  linkage there.

* I hard-coded the configuration tree in the wrong place (tree.c), out of
  laziness.

* I didn't implement all the devices of the "pc" original.  The devices
  I implemented might not support all existing command line options.

Notable qualities:

* Device drivers are cleanly separated from each other, and from the
  device-agnostic configuration code.

* Each driver specifies the configurable properties in a single place.

* Device configuration is gotten from the configuration tree, which is
  fully checked.  Unknown properties are rejected.


Appendix: Linux device trees
----------------------------

This appendix is probably only of interest to some of you, feel free to
skip.

The IEEE 1275 Open Firmware Device Tree solves a somewhat similar
problem, namely to communicate environmental information (hardware and
configuration) from firmware to operating system.  It's chiefly used on
PowerPCs.  The OS calls Open Firmware to query the device tree.

Linux turns the Open Firmware device tree API into a data format.
Actually two: the DT blob format is a binary data structure, and the
DT source format is human-readable text.  The device tree compiler
"dtc" can convert the two.

We already have a bit of code dealing with this, in device_tree.c.

I briefly examined the DT source format and the tree structure it
describes for the purpose of QEMU configuration.  I decided against
using it in my prototype because I found it awfully low-level and
verbose for that purpose (I'm sure it serves the purpose it was designed
for just fine).  Issues include:

* Since the DT is designed for booting kernels, not configuring QEMU,
  there's information that has no place in QEMU configuration, and
  required QEMU configuration isn't there.

* Redundancy between node name and its device_type property.

* Property "reg", which encodes address ranges, does so in terms of
  "cells": #address-cells 32-bit words (big endian) for the address,
  followed by #size-cells words for the size, where #address-cells and
  #size-cells are properties of the enclosing bus.  If this sounds
  like gibberish to you, well, that's my point.


diff --git a/Makefile b/Makefile
index 4f7a55a..2198bba 100644
--- a/Makefile
+++ b/Makefile
@@ -85,6 +85,7 @@ OBJS+=sd.o ssi-sd.o
 OBJS+=bt.o bt-host.o bt-vhci.o bt-l2cap.o bt-sdp.o bt-hci.o bt-hid.o usb-bt.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+=tree.o
 
 ifdef CONFIG_BRLAPI
 OBJS+= baum.o
diff --git a/Makefile.target b/Makefile.target
index a091ce9..10a3245 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -580,6 +580,7 @@ OBJS+= ide.o pckbd.o ps2.o vga.o $(SOUND_HW) dma.o
 OBJS+= fdc.o mc146818rtc.o serial.o i8259.o i8254.o pcspk.o pc.o
 OBJS+= cirrus_vga.o apic.o parallel.o acpi.o piix_pci.o
 OBJS+= usb-uhci.o vmmouse.o vmport.o vmware_vga.o hpet.o
+OBJS+= dt.o
 CPPFLAGS += -DHAS_AUDIO -DHAS_AUDIO_CHOICE
 endif
 ifeq ($(TARGET_BASE_ARCH), ppc)
diff --git a/hw/dt.c b/hw/dt.c
new file mode 100644
index 0000000..f62198f
--- /dev/null
+++ b/hw/dt.c
@@ -0,0 +1,1672 @@
+#include <assert.h>
+#include "hw.h"
+#include "pc.h"
+#include "fdc.h"
+#include "pci.h"
+#include "block.h"
+#include "sysemu.h"
+#include "audio/audio.h"
+#include "net.h"
+#include "smbus.h"
+#include "boards.h"
+#include "console.h"
+#include "fw_cfg.h"
+#include "virtio-blk.h"
+#include "virtio-balloon.h"
+#include "virtio-console.h"
+#include "hpet_emul.h"
+#include "tree.h"
+
+/* Forward declarations */
+struct dt_device;
+struct dt_driver;
+struct dt_prop_spec;
+static void dt_parse_prop(struct dt_device *dev, struct tree_prop *prop);
+static BlockDriverState **dt_piix3_hd(struct tree *piix3);
+
+
+// FIXME copied from pc.c, external defs stripped, unused stuff #if 0'ed
+/* output Bochs bios info messages */
+//#define DEBUG_BIOS
+
+#define BIOS_FILENAME "bios.bin"
+#define VGABIOS_FILENAME "vgabios.bin"
+#define VGABIOS_CIRRUS_FILENAME "vgabios-cirrus.bin"
+
+#define PC_MAX_BIOS_SIZE (4 * 1024 * 1024)
+
+/* Leave a chunk of memory at the top of RAM for the BIOS ACPI tables.  */
+#define ACPI_DATA_SIZE       0x10000
+#define BIOS_CFG_IOPORT 0x510
+
+#define MAX_IDE_BUS 2
+
+static fdctrl_t *floppy_controller;
+static RTCState *rtc_state;
+#if 0
+static PITState *pit;
+static IOAPICState *ioapic;
+#endif
+extern PCIDevice *i440fx_state;
+
+static void ioport80_write(void *opaque, uint32_t addr, uint32_t data)
+{
+}
+
+#if 0
+/* MSDOS compatibility mode FPU exception support */
+static qemu_irq ferr_irq;
+/* XXX: add IGNNE support */
+void cpu_set_ferr(CPUX86State *s)
+{
+    qemu_irq_raise(ferr_irq);
+}
+
+static void ioportF0_write(void *opaque, uint32_t addr, uint32_t data)
+{
+    qemu_irq_lower(ferr_irq);
+}
+#else
+extern qemu_irq ferr_irq;
+void ioportF0_write(void *opaque, uint32_t addr, uint32_t data);
+#endif
+
+#if 0
+/* TSC handling */
+uint64_t cpu_get_tsc(CPUX86State *env)
+{
+    /* Note: when using kqemu, it is more logical to return the host TSC
+       because kqemu does not trap the RDTSC instruction for
+       performance reasons */
+#ifdef USE_KQEMU
+    if (env->kqemu_enabled) {
+        return cpu_get_real_ticks();
+    } else
+#endif
+    {
+        return cpu_get_ticks();
+    }
+}
+
+/* SMM support */
+void cpu_smm_update(CPUState *env)
+{
+    if (i440fx_state && env == first_cpu)
+        i440fx_set_smm(i440fx_state, (env->hflags >> HF_SMM_SHIFT) & 1);
+}
+
+/* IRQ handling */
+int cpu_get_pic_interrupt(CPUState *env)
+{
+    int intno;
+
+    intno = apic_get_interrupt(env);
+    if (intno >= 0) {
+        /* set irq request if a PIC irq is still pending */
+        /* XXX: improve that */
+        pic_update_irq(isa_pic);
+        return intno;
+    }
+    /* read the irq from the PIC */
+    if (!apic_accept_pic_intr(env))
+        return -1;
+
+    intno = pic_read_irq(isa_pic);
+    return intno;
+}
+#endif
+
+static void pic_irq_request(void *opaque, int irq, int level)
+{
+    CPUState *env = first_cpu;
+
+    if (env->apic_state) {
+        while (env) {
+            if (apic_accept_pic_intr(env))
+                apic_deliver_pic_intr(env, level);
+            env = env->next_cpu;
+        }
+    } else {
+        if (level)
+            cpu_interrupt(env, CPU_INTERRUPT_HARD);
+        else
+            cpu_reset_interrupt(env, CPU_INTERRUPT_HARD);
+    }
+}
+
+/* PC cmos mappings */
+
+#define REG_EQUIPMENT_BYTE          0x14
+
+static int cmos_get_fd_drive_type(int fd0)
+{
+    int val;
+
+    switch (fd0) {
+    case 0:
+        /* 1.44 Mb 3"5 drive */
+        val = 4;
+        break;
+    case 1:
+        /* 2.88 Mb 3"5 drive */
+        val = 5;
+        break;
+    case 2:
+        /* 1.2 Mb 5"5 drive */
+        val = 2;
+        break;
+    default:
+        val = 0;
+        break;
+    }
+    return val;
+}
+
+static void cmos_init_hd(int type_ofs, int info_ofs, BlockDriverState *hd)
+{
+    RTCState *s = rtc_state;
+    int cylinders, heads, sectors;
+    bdrv_get_geometry_hint(hd, &cylinders, &heads, &sectors);
+    rtc_set_memory(s, type_ofs, 47);
+    rtc_set_memory(s, info_ofs, cylinders);
+    rtc_set_memory(s, info_ofs + 1, cylinders >> 8);
+    rtc_set_memory(s, info_ofs + 2, heads);
+    rtc_set_memory(s, info_ofs + 3, 0xff);
+    rtc_set_memory(s, info_ofs + 4, 0xff);
+    rtc_set_memory(s, info_ofs + 5, 0xc0 | ((heads > 8) << 3));
+    rtc_set_memory(s, info_ofs + 6, cylinders);
+    rtc_set_memory(s, info_ofs + 7, cylinders >> 8);
+    rtc_set_memory(s, info_ofs + 8, sectors);
+}
+
+/* convert boot_device letter to something recognizable by the bios */
+static int boot_device2nibble(char boot_device)
+{
+    switch(boot_device) {
+    case 'a':
+    case 'b':
+        return 0x01; /* floppy boot */
+    case 'c':
+        return 0x02; /* hard drive boot */
+    case 'd':
+        return 0x03; /* CD-ROM boot */
+    case 'n':
+        return 0x04; /* Network boot */
+    }
+    return 0;
+}
+
+/* copy/pasted from cmos_init, should be made a general function
+ and used there as well */
+static int pc_boot_set(void *opaque, const char *boot_device)
+{
+#define PC_MAX_BOOT_DEVICES 3
+    RTCState *s = (RTCState *)opaque;
+    int nbds, bds[3] = { 0, };
+    int i;
+
+    nbds = strlen(boot_device);
+    if (nbds > PC_MAX_BOOT_DEVICES) {
+        term_printf("Too many boot devices for PC\n");
+        return(1);
+    }
+    for (i = 0; i < nbds; i++) {
+        bds[i] = boot_device2nibble(boot_device[i]);
+        if (bds[i] == 0) {
+            term_printf("Invalid boot device for PC: '%c'\n",
+                    boot_device[i]);
+            return(1);
+        }
+    }
+    rtc_set_memory(s, 0x3d, (bds[1] << 4) | bds[0]);
+    rtc_set_memory(s, 0x38, (bds[2] << 4));
+    return(0);
+}
+
+/* hd_table must contain 4 block drivers */
+static void cmos_init(ram_addr_t ram_size, ram_addr_t above_4g_mem_size,
+                      const char *boot_device, BlockDriverState **hd_table)
+{
+    RTCState *s = rtc_state;
+    int nbds, bds[3] = { 0, };
+    int val;
+    int fd0, fd1, nb;
+    int i;
+
+    /* various important CMOS locations needed by PC/Bochs bios */
+
+    /* memory size */
+    val = 640; /* base memory in K */
+    rtc_set_memory(s, 0x15, val);
+    rtc_set_memory(s, 0x16, val >> 8);
+
+    val = (ram_size / 1024) - 1024;
+    if (val > 65535)
+        val = 65535;
+    rtc_set_memory(s, 0x17, val);
+    rtc_set_memory(s, 0x18, val >> 8);
+    rtc_set_memory(s, 0x30, val);
+    rtc_set_memory(s, 0x31, val >> 8);
+
+    if (above_4g_mem_size) {
+        rtc_set_memory(s, 0x5b, (unsigned int)above_4g_mem_size >> 16);
+        rtc_set_memory(s, 0x5c, (unsigned int)above_4g_mem_size >> 24);
+        rtc_set_memory(s, 0x5d, (uint64_t)above_4g_mem_size >> 32);
+    }
+
+    if (ram_size > (16 * 1024 * 1024))
+        val = (ram_size / 65536) - ((16 * 1024 * 1024) / 65536);
+    else
+        val = 0;
+    if (val > 65535)
+        val = 65535;
+    rtc_set_memory(s, 0x34, val);
+    rtc_set_memory(s, 0x35, val >> 8);
+
+    /* set the number of CPU */
+    rtc_set_memory(s, 0x5f, smp_cpus - 1);
+
+    /* set boot devices, and disable floppy signature check if requested */
+#define PC_MAX_BOOT_DEVICES 3
+    nbds = strlen(boot_device);
+    if (nbds > PC_MAX_BOOT_DEVICES) {
+        fprintf(stderr, "Too many boot devices for PC\n");
+        exit(1);
+    }
+    for (i = 0; i < nbds; i++) {
+        bds[i] = boot_device2nibble(boot_device[i]);
+        if (bds[i] == 0) {
+            fprintf(stderr, "Invalid boot device for PC: '%c'\n",
+                    boot_device[i]);
+            exit(1);
+        }
+    }
+    rtc_set_memory(s, 0x3d, (bds[1] << 4) | bds[0]);
+    rtc_set_memory(s, 0x38, (bds[2] << 4) | (fd_bootchk ?  0x0 : 0x1));
+
+    /* floppy type */
+
+    fd0 = fdctrl_get_drive_type(floppy_controller, 0);
+    fd1 = fdctrl_get_drive_type(floppy_controller, 1);
+
+    val = (cmos_get_fd_drive_type(fd0) << 4) | cmos_get_fd_drive_type(fd1);
+    rtc_set_memory(s, 0x10, val);
+
+    val = 0;
+    nb = 0;
+    if (fd0 < 3)
+        nb++;
+    if (fd1 < 3)
+        nb++;
+    switch (nb) {
+    case 0:
+        break;
+    case 1:
+        val |= 0x01; /* 1 drive, ready for boot */
+        break;
+    case 2:
+        val |= 0x41; /* 2 drives, ready for boot */
+        break;
+    }
+    val |= 0x02; /* FPU is there */
+    val |= 0x04; /* PS/2 mouse installed */
+    rtc_set_memory(s, REG_EQUIPMENT_BYTE, val);
+
+    /* hard drives */
+
+    rtc_set_memory(s, 0x12, (hd_table[0] ? 0xf0 : 0) | (hd_table[1] ? 0x0f : 
0));
+    if (hd_table[0])
+        cmos_init_hd(0x19, 0x1b, hd_table[0]);
+    if (hd_table[1])
+        cmos_init_hd(0x1a, 0x24, hd_table[1]);
+
+    val = 0;
+    for (i = 0; i < 4; i++) {
+        if (hd_table[i]) {
+            int cylinders, heads, sectors, translation;
+            /* NOTE: bdrv_get_geometry_hint() returns the physical
+                geometry.  It is always such that: 1 <= sects <= 63, 1
+                <= heads <= 16, 1 <= cylinders <= 16383. The BIOS
+                geometry can be different if a translation is done. */
+            translation = bdrv_get_translation_hint(hd_table[i]);
+            if (translation == BIOS_ATA_TRANSLATION_AUTO) {
+                bdrv_get_geometry_hint(hd_table[i], &cylinders, &heads, 
&sectors);
+                if (cylinders <= 1024 && heads <= 16 && sectors <= 63) {
+                    /* No translation. */
+                    translation = 0;
+                } else {
+                    /* LBA translation. */
+                    translation = 1;
+                }
+            } else {
+                translation--;
+            }
+            val |= translation << (i * 2);
+        }
+    }
+    rtc_set_memory(s, 0x39, val);
+}
+
+#if 0
+void ioport_set_a20(int enable)
+{
+    /* XXX: send to all CPUs ? */
+    cpu_x86_set_a20(first_cpu, enable);
+}
+
+int ioport_get_a20(void)
+{
+    return ((first_cpu->a20_mask >> 20) & 1);
+}
+#endif
+
+static void ioport92_write(void *opaque, uint32_t addr, uint32_t val)
+{
+    ioport_set_a20((val >> 1) & 1);
+    /* XXX: bit 0 is fast reset */
+}
+
+static uint32_t ioport92_read(void *opaque, uint32_t addr)
+{
+    return ioport_get_a20() << 1;
+}
+
+/***********************************************************/
+/* Bochs BIOS debug ports */
+
+static void bochs_bios_write(void *opaque, uint32_t addr, uint32_t val)
+{
+    static const char shutdown_str[8] = "Shutdown";
+    static int shutdown_index = 0;
+
+    switch(addr) {
+        /* Bochs BIOS messages */
+    case 0x400:
+    case 0x401:
+        fprintf(stderr, "BIOS panic at rombios.c, line %d\n", val);
+        exit(1);
+    case 0x402:
+    case 0x403:
+#ifdef DEBUG_BIOS
+        fprintf(stderr, "%c", val);
+#endif
+        break;
+    case 0x8900:
+        /* same as Bochs power off */
+        if (val == shutdown_str[shutdown_index]) {
+            shutdown_index++;
+            if (shutdown_index == 8) {
+                shutdown_index = 0;
+                qemu_system_shutdown_request();
+            }
+        } else {
+            shutdown_index = 0;
+        }
+        break;
+
+        /* LGPL'ed VGA BIOS messages */
+    case 0x501:
+    case 0x502:
+        fprintf(stderr, "VGA BIOS panic, line %d\n", val);
+        exit(1);
+    case 0x500:
+    case 0x503:
+#ifdef DEBUG_BIOS
+        fprintf(stderr, "%c", val);
+#endif
+        break;
+    }
+}
+
+static void bochs_bios_init(void)
+{
+    void *fw_cfg;
+
+    register_ioport_write(0x400, 1, 2, bochs_bios_write, NULL);
+    register_ioport_write(0x401, 1, 2, bochs_bios_write, NULL);
+    register_ioport_write(0x402, 1, 1, bochs_bios_write, NULL);
+    register_ioport_write(0x403, 1, 1, bochs_bios_write, NULL);
+    register_ioport_write(0x8900, 1, 1, bochs_bios_write, NULL);
+
+    register_ioport_write(0x501, 1, 2, bochs_bios_write, NULL);
+    register_ioport_write(0x502, 1, 2, bochs_bios_write, NULL);
+    register_ioport_write(0x500, 1, 1, bochs_bios_write, NULL);
+    register_ioport_write(0x503, 1, 1, bochs_bios_write, NULL);
+
+    fw_cfg = fw_cfg_init(BIOS_CFG_IOPORT, BIOS_CFG_IOPORT + 1, 0, 0);
+    fw_cfg_add_i32(fw_cfg, FW_CFG_ID, 1);
+    fw_cfg_add_i64(fw_cfg, FW_CFG_RAM_SIZE, (uint64_t)ram_size);
+}
+
+#if 0
+/* Generate an initial boot sector which sets state and jump to
+   a specified vector */
+static void generate_bootsect(uint8_t *option_rom,
+                              uint32_t gpr[8], uint16_t segs[6], uint16_t ip)
+{
+    uint8_t rom[512], *p, *reloc;
+    uint8_t sum;
+    int i;
+
+    memset(rom, 0, sizeof(rom));
+
+    p = rom;
+    /* Make sure we have an option rom signature */
+    *p++ = 0x55;
+    *p++ = 0xaa;
+
+    /* ROM size in sectors*/
+    *p++ = 1;
+
+    /* Hook int19 */
+
+    *p++ = 0x50;               /* push ax */
+    *p++ = 0x1e;               /* push ds */
+    *p++ = 0x31; *p++ = 0xc0;  /* xor ax, ax */
+    *p++ = 0x8e; *p++ = 0xd8;  /* mov ax, ds */
+
+    *p++ = 0xc7; *p++ = 0x06;   /* movvw _start,0x64 */
+    *p++ = 0x64; *p++ = 0x00;
+    reloc = p;
+    *p++ = 0x00; *p++ = 0x00;
+
+    *p++ = 0x8c; *p++ = 0x0e;   /* mov cs,0x66 */
+    *p++ = 0x66; *p++ = 0x00;
+
+    *p++ = 0x1f;               /* pop ds */
+    *p++ = 0x58;               /* pop ax */
+    *p++ = 0xcb;               /* lret */
+    
+    /* Actual code */
+    *reloc = (p - rom);
+
+    *p++ = 0xfa;               /* CLI */
+    *p++ = 0xfc;               /* CLD */
+
+    for (i = 0; i < 6; i++) {
+       if (i == 1)             /* Skip CS */
+           continue;
+
+       *p++ = 0xb8;            /* MOV AX,imm16 */
+       *p++ = segs[i];
+       *p++ = segs[i] >> 8;
+       *p++ = 0x8e;            /* MOV <seg>,AX */
+       *p++ = 0xc0 + (i << 3);
+    }
+
+    for (i = 0; i < 8; i++) {
+       *p++ = 0x66;            /* 32-bit operand size */
+       *p++ = 0xb8 + i;        /* MOV <reg>,imm32 */
+       *p++ = gpr[i];
+       *p++ = gpr[i] >> 8;
+       *p++ = gpr[i] >> 16;
+       *p++ = gpr[i] >> 24;
+    }
+
+    *p++ = 0xea;               /* JMP FAR */
+    *p++ = ip;                 /* IP */
+    *p++ = ip >> 8;
+    *p++ = segs[1];            /* CS */
+    *p++ = segs[1] >> 8;
+
+    /* sign rom */
+    sum = 0;
+    for (i = 0; i < (sizeof(rom) - 1); i++)
+        sum += rom[i];
+    rom[sizeof(rom) - 1] = -sum;
+
+    memcpy(option_rom, rom, sizeof(rom));
+}
+
+static long get_file_size(FILE *f)
+{
+    long where, size;
+
+    /* XXX: on Unix systems, using fstat() probably makes more sense */
+
+    where = ftell(f);
+    fseek(f, 0, SEEK_END);
+    size = ftell(f);
+    fseek(f, where, SEEK_SET);
+
+    return size;
+}
+
+static void load_linux(uint8_t *option_rom,
+                       const char *kernel_filename,
+                      const char *initrd_filename,
+                      const char *kernel_cmdline)
+{
+    uint16_t protocol;
+    uint32_t gpr[8];
+    uint16_t seg[6];
+    uint16_t real_seg;
+    int setup_size, kernel_size, initrd_size, cmdline_size;
+    uint32_t initrd_max;
+    uint8_t header[1024];
+    target_phys_addr_t real_addr, prot_addr, cmdline_addr, initrd_addr;
+    FILE *f, *fi;
+
+    /* Align to 16 bytes as a paranoia measure */
+    cmdline_size = (strlen(kernel_cmdline)+16) & ~15;
+
+    /* load the kernel header */
+    f = fopen(kernel_filename, "rb");
+    if (!f || !(kernel_size = get_file_size(f)) ||
+       fread(header, 1, 1024, f) != 1024) {
+       fprintf(stderr, "qemu: could not load kernel '%s'\n",
+               kernel_filename);
+       exit(1);
+    }
+
+    /* kernel protocol version */
+#if 0
+    fprintf(stderr, "header magic: %#x\n", ldl_p(header+0x202));
+#endif
+    if (ldl_p(header+0x202) == 0x53726448)
+       protocol = lduw_p(header+0x206);
+    else
+       protocol = 0;
+
+    if (protocol < 0x200 || !(header[0x211] & 0x01)) {
+       /* Low kernel */
+       real_addr    = 0x90000;
+       cmdline_addr = 0x9a000 - cmdline_size;
+       prot_addr    = 0x10000;
+    } else if (protocol < 0x202) {
+       /* High but ancient kernel */
+       real_addr    = 0x90000;
+       cmdline_addr = 0x9a000 - cmdline_size;
+       prot_addr    = 0x100000;
+    } else {
+       /* High and recent kernel */
+       real_addr    = 0x10000;
+       cmdline_addr = 0x20000;
+       prot_addr    = 0x100000;
+    }
+
+#if 0
+    fprintf(stderr,
+           "qemu: real_addr     = 0x" TARGET_FMT_plx "\n"
+           "qemu: cmdline_addr  = 0x" TARGET_FMT_plx "\n"
+           "qemu: prot_addr     = 0x" TARGET_FMT_plx "\n",
+           real_addr,
+           cmdline_addr,
+           prot_addr);
+#endif
+
+    /* highest address for loading the initrd */
+    if (protocol >= 0x203)
+       initrd_max = ldl_p(header+0x22c);
+    else
+       initrd_max = 0x37ffffff;
+
+    if (initrd_max >= ram_size-ACPI_DATA_SIZE)
+       initrd_max = ram_size-ACPI_DATA_SIZE-1;
+
+    /* kernel command line */
+    pstrcpy_targphys(cmdline_addr, 4096, kernel_cmdline);
+
+    if (protocol >= 0x202) {
+       stl_p(header+0x228, cmdline_addr);
+    } else {
+       stw_p(header+0x20, 0xA33F);
+       stw_p(header+0x22, cmdline_addr-real_addr);
+    }
+
+    /* loader type */
+    /* High nybble = B reserved for Qemu; low nybble is revision number.
+       If this code is substantially changed, you may want to consider
+       incrementing the revision. */
+    if (protocol >= 0x200)
+       header[0x210] = 0xB0;
+
+    /* heap */
+    if (protocol >= 0x201) {
+       header[0x211] |= 0x80;  /* CAN_USE_HEAP */
+       stw_p(header+0x224, cmdline_addr-real_addr-0x200);
+    }
+
+    /* load initrd */
+    if (initrd_filename) {
+       if (protocol < 0x200) {
+           fprintf(stderr, "qemu: linux kernel too old to load a ram disk\n");
+           exit(1);
+       }
+
+       fi = fopen(initrd_filename, "rb");
+       if (!fi) {
+           fprintf(stderr, "qemu: could not load initial ram disk '%s'\n",
+                   initrd_filename);
+           exit(1);
+       }
+
+       initrd_size = get_file_size(fi);
+       initrd_addr = (initrd_max-initrd_size) & ~4095;
+
+        fprintf(stderr, "qemu: loading initrd (%#x bytes) at 0x" TARGET_FMT_plx
+                "\n", initrd_size, initrd_addr);
+
+       if (!fread_targphys_ok(initrd_addr, initrd_size, fi)) {
+           fprintf(stderr, "qemu: read error on initial ram disk '%s'\n",
+                   initrd_filename);
+           exit(1);
+       }
+       fclose(fi);
+
+       stl_p(header+0x218, initrd_addr);
+       stl_p(header+0x21c, initrd_size);
+    }
+
+    /* store the finalized header and load the rest of the kernel */
+    cpu_physical_memory_write(real_addr, header, 1024);
+
+    setup_size = header[0x1f1];
+    if (setup_size == 0)
+       setup_size = 4;
+
+    setup_size = (setup_size+1)*512;
+    kernel_size -= setup_size; /* Size of protected-mode code */
+
+    if (!fread_targphys_ok(real_addr+1024, setup_size-1024, f) ||
+       !fread_targphys_ok(prot_addr, kernel_size, f)) {
+       fprintf(stderr, "qemu: read error on kernel '%s'\n",
+               kernel_filename);
+       exit(1);
+    }
+    fclose(f);
+
+    /* generate bootsector to set up the initial register state */
+    real_seg = real_addr >> 4;
+    seg[0] = seg[2] = seg[3] = seg[4] = seg[4] = real_seg;
+    seg[1] = real_seg+0x20;    /* CS */
+    memset(gpr, 0, sizeof gpr);
+    gpr[4] = cmdline_addr-real_addr-16;        /* SP (-16 is paranoia) */
+
+    generate_bootsect(option_rom, gpr, seg, 0);
+}
+#endif
+
+static void main_cpu_reset(void *opaque)
+{
+    CPUState *env = opaque;
+    cpu_reset(env);
+}
+
+static const int ide_iobase[2] = { 0x1f0, 0x170 };
+static const int ide_iobase2[2] = { 0x3f6, 0x376 };
+static const int ide_irq[2] = { 14, 15 };
+
+#define NE2000_NB_MAX 6
+
+static const int ne2000_io[NE2000_NB_MAX] = { 0x300, 0x320, 0x340, 0x360, 
0x280, 0x380 };
+static const int ne2000_irq[NE2000_NB_MAX] = { 9, 10, 11, 3, 4, 5 };
+
+static const int serial_io[MAX_SERIAL_PORTS] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8 };
+static const int serial_irq[MAX_SERIAL_PORTS] = { 4, 3, 4, 3 };
+
+static const int parallel_io[MAX_PARALLEL_PORTS] = { 0x378, 0x278, 0x3bc };
+static const int parallel_irq[MAX_PARALLEL_PORTS] = { 7, 7, 7 };
+
+#if 0 //def HAS_AUDIO
+static void audio_init (PCIBus *pci_bus, qemu_irq *pic)
+{
+    struct soundhw *c;
+    int audio_enabled = 0;
+
+    for (c = soundhw; !audio_enabled && c->name; ++c) {
+        audio_enabled = c->enabled;
+    }
+
+    if (audio_enabled) {
+        AudioState *s;
+
+        s = AUD_init ();
+        if (s) {
+            for (c = soundhw; c->name; ++c) {
+                if (c->enabled) {
+                    if (c->isa) {
+                        c->init.init_isa (s, pic);
+                    }
+                    else {
+                        if (pci_bus) {
+                            c->init.init_pci (pci_bus, s);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+static void pc_init_ne2k_isa(NICInfo *nd, qemu_irq *pic)
+{
+    static int nb_ne2k = 0;
+
+    if (nb_ne2k == NE2000_NB_MAX)
+        return;
+    isa_ne2000_init(ne2000_io[nb_ne2k], pic[ne2000_irq[nb_ne2k]], nd);
+    nb_ne2k++;
+}
+#endif
+
+
+// FIXME copied from net.c
+
+static int parse_macaddr(uint8_t *macaddr, const char *p)
+{
+    int i;
+    char *last_char;
+    long int offset;
+
+    errno = 0;
+    offset = strtol(p, &last_char, 0);    
+    if (0 == errno && '\0' == *last_char &&
+            offset >= 0 && offset <= 0xFFFFFF) {
+        macaddr[3] = (offset & 0xFF0000) >> 16;
+        macaddr[4] = (offset & 0xFF00) >> 8;
+        macaddr[5] = offset & 0xFF;
+        return 0;
+    } else {
+        for(i = 0; i < 6; i++) {
+            macaddr[i] = strtol(p, (char **)&p, 16);
+            if (i == 5) {
+                if (*p != '\0')
+                    return -1;
+            } else {
+                if (*p != ':' && *p != '-')
+                    return -1;
+                p++;
+            }
+        }
+        return 0;    
+    }
+
+    return -1;
+}
+
+/* Device Interface */
+
+/*
+ * Device life cycle:
+ *
+ * 1. Configuration: config() method runs after parent's.  Except kids
+ * are skipped when the parent's config() returns non-zero.  config()
+ * should initialize the device's private data from its configuration
+ * sub-tree.  It may edit the configuration sub-tree, and may declare
+ * initialization ordering constraints with tree_require_named().
+ * 
+ * 2. Initialization: init() method runs after parent's and after that
+ * of devices declared required by config().  It should not touch the
+ * configuration tree.
+ *
+ * 3. Start: start() method runs, order is unspecified.
+ * 
+ * Error handling in these driver methods: print to stderr and exit
+ * the program unsuccessfully.
+ *
+ * There is no device shutdown protocol yet.
+ */
+
+struct dt_device {
+    struct tree *conf;         /* configuration sub-tree */
+    struct dt_driver *drv;     /* device driver */
+    void *priv;                        /* device private data */
+};
+
+struct dt_driver {
+    const char *name;
+    size_t privsz;             /* size of device private data */
+    struct dt_prop_spec *prop_spec; /* recognized conf node properties */
+    int (*config)(struct dt_device *);
+    void (*init)(struct dt_device *);
+    void (*start)(struct dt_device *);
+};
+
+static struct dt_driver dt_driver_table[];
+
+static struct dt_driver *
+dt_driver_by_name(const char *name)
+{
+    int i;
+
+    for (i = 0; dt_driver_table[i].name; i++) {
+       if (!strcmp(name, dt_driver_table[i].name))
+           return &dt_driver_table[i];
+    }
+    return NULL;
+}
+
+static struct dt_device *
+dt_device_of(struct tree *conf)
+{
+    return tree_get_user(conf);
+}
+
+static struct dt_device *
+dt_new_device(struct tree *conf, struct dt_driver *drv)
+{
+    struct dt_device *dev;
+    struct tree_prop *prop;
+
+    dev = qemu_malloc(sizeof(*dev));
+    dev->conf = conf;
+    dev->drv = drv;
+    dev->priv = qemu_malloc(drv->privsz);
+    tree_put_user(conf, dev);
+
+    TREE_FOREACH_PROP(prop, conf)
+       dt_parse_prop(dev, prop);
+
+    return dev;
+}
+
+static void
+dt_config(struct tree *conf)
+{
+    struct dt_driver *drv;
+    struct dt_device *dev;
+    struct tree *kid;
+
+    drv = dt_driver_by_name(tree_node_name(conf));
+    if (!drv) {
+       fprintf(stderr, "No driver for device %s\n",
+               tree_node_name(conf));
+       exit(1);
+    }
+    dev = dt_new_device(conf, drv);
+    if (drv->config) {
+       if (drv->config(dev))
+           return;
+    }
+
+    TREE_FOREACH_KID(kid, conf)
+       dt_config(kid);
+}
+
+static void
+dt_init_visitor(struct tree *node, void *arg)
+{
+    struct dt_device *dev = dt_device_of(node);
+
+    if (dev && dev->drv->init)
+       dev->drv->init(dev);
+}
+
+static void
+dt_init(struct tree *conf)
+{
+    tree_visit(conf, dt_init_visitor, NULL);
+}
+
+static void
+dt_start(struct tree *conf)
+{
+    struct dt_device *dev = dt_device_of(conf);
+    struct tree *kid;
+
+    if (dev && dev->drv->start)
+       dev->drv->start(dev);
+
+    TREE_FOREACH_KID(kid, conf)
+       dt_start(kid);
+}
+
+
+/* Device properties */
+
+/*
+ * This is for parsing configuration tree node properties into device
+ * private data.
+ */
+
+struct dt_prop_spec {
+    const char *name;
+    ptrdiff_t offs;            /* offset in device private data */
+    size_t size;               /* size there, for sanity checking */
+    int (*parse)(void *, const char *, struct dt_prop_spec *);
+};
+
+#define DT_PROP_SPEC_INIT(name, strty, member, fmt)                    \
+    { name, offsetof(strty, member), sizeof(((strty *)0)->member),     \
+      dt_parse_##fmt }
+
+static struct dt_prop_spec *
+dt_prop_spec_by_name(struct dt_driver *drv, const char *name)
+{
+    struct dt_prop_spec *spec;
+
+    for (spec = drv->prop_spec; spec && spec->name; spec++) {
+       if (!strcmp(spec->name, name))
+           return spec;
+    }
+    return NULL;
+}
+
+static void
+dt_parse_prop(struct dt_device *dev, struct tree_prop *prop)
+{
+    const char *name = tree_prop_name(prop);
+    size_t size;
+    const char *val = tree_prop_value(prop, &size);
+    struct dt_prop_spec *spec = dt_prop_spec_by_name(dev->drv, name);
+
+    if (!spec) {
+       fprintf(stderr, "A %s device has no property %s\n",
+               dev->drv->name, name);
+       exit(1);
+    }
+
+    if (memchr(val, 0, size) != val + size - 1
+       || spec->parse((char *)dev->priv + spec->offs, val, spec) < 0) {
+       fprintf(stderr, "Bad value %.*s for property %s of device %s\n",
+               size, val, name, dev->drv->name);
+       exit(1);
+    }
+}
+
+static int
+dt_parse_string(void *dst, const char *src, struct dt_prop_spec *spec)
+{
+    assert(spec->size == sizeof(char *));
+    *(const char **)dst = src;
+    return 0;
+}
+
+static int
+dt_parse_int(void *dst, const char *src, struct dt_prop_spec *spec)
+{
+    char *ep;
+    long val;
+
+    assert(spec->size == sizeof(int));
+    errno = 0;
+    val = strtol(src, &ep, 0);
+    if (*ep || ep == src || errno || (int)val != val)
+       return -1;
+    *(int *)dst = val;
+    return 0;
+}
+
+static int
+dt_parse_ram_addr_t(void *dst, const char *src, struct dt_prop_spec *spec)
+{
+    char *ep;
+    unsigned long val;
+
+    assert(spec->size == sizeof(ram_addr_t));
+    errno = 0;
+    val = strtoul(src, &ep, 0);
+    if (*ep || ep == src || errno || (ram_addr_t)val != val)
+       return -1;
+    *(ram_addr_t *)dst = val;
+    return 0;
+}
+
+static int
+dt_parse_macaddr(void *dst, const char *src, struct dt_prop_spec *spec)
+{
+    assert(spec->size == 6);
+    if (parse_macaddr(dst, src) < 0)
+       return -1;
+    return 0;
+}
+
+
+/* CPUs Driver */
+
+struct dt_device_cpus {
+    const char *model;
+    int num;
+};
+
+static struct dt_prop_spec dt_cpus_props[] = {
+    DT_PROP_SPEC_INIT("model", struct dt_device_cpus, model, string),
+    DT_PROP_SPEC_INIT("num", struct dt_device_cpus, num, int),
+};
+
+static void
+dt_cpus_init(struct dt_device *dev)
+{
+    struct dt_device_cpus *priv = dev->priv;
+    int i;
+    CPUState *env;
+
+    for(i = 0; i < priv->num; i++) {
+        env = cpu_init(priv->model);
+        if (!env) {
+            fprintf(stderr, "Unable to find x86 CPU definition\n");
+            exit(1);
+        }
+        if (i != 0)
+            env->halted = 1;
+        qemu_register_reset(main_cpu_reset, env);
+    }
+}
+
+
+/* Memory Ranges */
+
+struct dt_device_memrng {
+    target_phys_addr_t phys_addr;
+    ram_addr_t size;
+    ram_addr_t host_offs;
+    ram_addr_t flags;
+};
+
+static void
+dt_memrng(struct dt_device_memrng *rng,
+         target_phys_addr_t phys_addr, ram_addr_t size,
+         ram_addr_t host_offs, ram_addr_t flags)
+{
+    rng->phys_addr = phys_addr;
+    rng->size = size;
+    rng->host_offs = host_offs;
+    rng->flags = flags;
+}
+
+static void
+dt_memrng_ram(struct dt_device_memrng *rng,
+             target_phys_addr_t phys_addr, ram_addr_t size)
+{
+    dt_memrng(rng, phys_addr, size, qemu_ram_alloc(size), 0);
+}
+
+static void
+dt_memrng_rom(struct dt_device_memrng *rng,
+             target_phys_addr_t phys_addr, ram_addr_t maxsz,
+             const char *dir, const char *image, int top)
+{
+    char buf[1024];
+    int size;
+
+    snprintf(buf, sizeof(buf), "%s/%s", dir, image);
+    size = get_image_size(buf);
+    if (size < 0 || size > maxsz)
+       goto error;
+    if (top)
+       phys_addr = phys_addr + maxsz - size;
+    dt_memrng(rng, phys_addr, size, qemu_ram_alloc(size), IO_MEM_ROM);
+    if (load_image(buf, phys_ram_base + rng->host_offs) != size)
+       goto error;
+    return;
+
+error:
+    fprintf(stderr, "qemu: could not load image '%s'\n", buf);
+    exit(1);
+}
+
+static void
+dt_memrng_init(struct dt_device_memrng *rng, int n)
+{
+    int i;
+
+    for (i = 0; i < n; i++)
+       cpu_register_physical_memory(rng[i].phys_addr, rng[i].size,
+                                    rng[i].host_offs | rng[i].flags);
+}
+
+
+/* Memory Driver */
+
+struct dt_device_memory {
+    ram_addr_t ram_size;
+    struct dt_device_memrng *rng;
+    int nrng;
+    /* TODO want a real memory map here */
+    ram_addr_t below_4g, above_4g;
+};
+
+static struct dt_prop_spec dt_memory_props[] = {
+    DT_PROP_SPEC_INIT("ram", struct dt_device_memory, ram_size, ram_addr_t),
+};
+
+static int
+dt_memory_config(struct dt_device *dev)
+{
+    /* TODO memory map hardcoded; get it from dev->conf instead */
+    struct dt_device_memory *priv = dev->priv;
+    struct dt_device_memrng *rng = qemu_malloc(sizeof(*rng) * 4);
+
+    if (priv->ram_size >= 0xe0000000 ) {
+        priv->above_4g = priv->ram_size - 0xe0000000;
+        priv->below_4g = 0xe0000000;
+    } else {
+        priv->below_4g = priv->ram_size;
+       priv->above_4g = 0;
+    }
+
+    dt_memrng_ram(&rng[0], 0, 0xa0000);
+    qemu_ram_alloc(0x60000);
+    dt_memrng_ram(&rng[1], 0x100000, priv->below_4g - 0x100000);
+    if (priv->above_4g)
+       abort();                /* TODO */
+    dt_memrng_rom(&rng[2], 0xe0000000, 0x20000000,
+                 bios_dir, BIOS_FILENAME, 1);
+                               /* TODO get name from dev->conf */
+    dt_memrng(&rng[3], 0xe0000, 0x20000,
+             rng[2].host_offs + rng[2].size - 0x20000, IO_MEM_ROM);
+    /* TODO option ROMs */
+
+    priv->rng = rng;
+    priv->nrng = 4;
+    return 0;
+}
+
+static void
+dt_memory_init(struct dt_device *dev)
+{
+    struct dt_device_memory *priv = dev->priv;
+
+    dt_memrng_init(priv->rng, priv->nrng);
+    bochs_bios_init();
+}
+
+static ram_addr_t
+dt_memory_below_4g(struct tree *memory)
+{
+    struct dt_device *dev = dt_device_of(memory);
+    struct dt_device_memory *priv = dev->priv;
+    assert(dev->drv->init == dt_memory_init);
+    return priv->below_4g;
+}
+
+static ram_addr_t
+dt_memory_above_4g(struct tree *memory)
+{
+    struct dt_device *dev = dt_device_of(memory);
+    struct dt_device_memory *priv = dev->priv;
+    assert(dev->drv->init == dt_memory_init);
+    return priv->above_4g;
+}
+
+
+/* Drives */
+
+static void
+dt_drive_config(struct dt_device *dev, BlockDriverState *drive[], int n)
+{
+    static struct dt_prop_spec spec = { NULL, 0, sizeof(int), NULL };
+    struct tree *kid;
+    int i, index;
+
+    memset(drive, 0, sizeof(*drive) * n);
+    i = 0;
+    TREE_FOREACH_KID(kid, dev->conf) {
+       if (strcmp(tree_node_name(kid), "drive"))
+           continue;
+       if (dt_parse_int(&index, tree_get_prop_s(kid, "_index"), &spec) < 0)
+           abort();
+       assert(i < n);
+       drive[i++] = drives_table[index].bdrv;
+    }
+}
+
+
+/* PC Miscellanous Driver */
+
+/*
+ * This is a driver for a whole collection of devices.  Could be
+ * picked apart into separate drivers, I guess.
+ */
+
+struct dt_device_pc_misc {
+    const char *boot_device;
+    int apic;
+    int hpet;
+    qemu_irq *i8259;
+    BlockDriverState *fd[MAX_FD];
+};
+
+static struct dt_prop_spec dt_pc_misc_props[] = {
+    DT_PROP_SPEC_INIT("boot-device", struct dt_device_pc_misc, boot_device,
+                     string),
+};
+
+static int
+dt_pc_misc_config(struct dt_device *dev)
+{
+    struct dt_device_pc_misc *priv = dev->priv;
+
+    priv->apic = 1;
+    priv->hpet = 1;
+    priv->i8259 = NULL;
+    dt_drive_config(dev, priv->fd, sizeof(priv->fd) / sizeof(*priv->fd));
+    return 1;
+}
+
+static void
+dt_pc_misc_init(struct dt_device *dev)
+{
+    struct dt_device_pc_misc *priv = dev->priv;
+    CPUState *env;
+    qemu_irq *cpu_irq;
+    IOAPICState *ioapic;
+    PITState *pit;
+    int i;
+
+    if (priv->apic) {
+       for (env = first_cpu; env; env = env->next_cpu) {
+            env->cpuid_features |= CPUID_APIC;
+           apic_init(env);
+       }
+    }
+
+    vmport_init();
+
+    cpu_irq = qemu_allocate_irqs(pic_irq_request, NULL, 1);
+    priv->i8259 = i8259_init(cpu_irq[0]);
+    ferr_irq = priv->i8259[13];
+
+    register_ioport_write(0x80, 1, 1, ioport80_write, NULL);
+    register_ioport_write(0xf0, 1, 1, ioportF0_write, NULL);
+
+    rtc_state = rtc_init(0x70, priv->i8259[8], 2000);
+    qemu_register_boot_set(pc_boot_set, rtc_state);
+
+    register_ioport_read(0x92, 1, 1, ioport92_read, NULL);
+    register_ioport_write(0x92, 1, 1, ioport92_write, NULL);
+
+    if (priv->apic) {
+        ioapic = ioapic_init();
+        pic_set_alt_irq_func(isa_pic, ioapic_set_irq, ioapic);
+    }
+
+    pit = pit_init(0x40, priv->i8259[0]);
+    pcspk_init(pit);
+    if (priv->hpet)
+        hpet_init(priv->i8259);
+    
+    for(i = 0; i < MAX_SERIAL_PORTS; i++) {
+        if (serial_hds[i]) {
+            serial_init(serial_io[i], priv->i8259[serial_irq[i]], 115200,
+                        serial_hds[i]);
+        }
+    }
+
+    for(i = 0; i < MAX_PARALLEL_PORTS; i++) {
+        if (parallel_hds[i]) {
+            parallel_init(parallel_io[i], priv->i8259[parallel_irq[i]],
+                          parallel_hds[i]);
+        }
+    }
+
+    i8042_init(priv->i8259[1], priv->i8259[12], 0x60);
+    DMA_init(0);
+
+    floppy_controller = fdctrl_init(priv->i8259[6], 2, 0, 0x3f0, priv->fd);
+}
+
+static void
+dt_pc_misc_start(struct dt_device *dev)
+{
+    struct dt_device_pc_misc *priv = dev->priv;
+    struct tree *memory = tree_node_by_name(dev->conf, "/memory");
+    struct tree *piix3 = tree_node_by_name(dev->conf, "/pci/piix3");
+
+    cmos_init(dt_memory_below_4g(memory),
+             dt_memory_above_4g(memory),
+             priv->boot_device,
+             dt_piix3_hd(piix3));
+}
+
+static qemu_irq *
+dt_pc_misc_i8259(struct tree *pc_misc)
+{
+    struct dt_device *dev = dt_device_of(pc_misc);
+    struct dt_device_pc_misc *priv = dev->priv;
+    assert(dev->drv->init == dt_pc_misc_init);
+    return priv->i8259;
+}
+
+
+/* PCI Bus Driver */
+
+struct dt_device_pci {
+    PCIBus *bus;
+    struct tree *pc;
+};
+
+static int
+dt_pci_config(struct dt_device *dev)
+{
+    struct dt_device_pci *priv = dev->priv;
+
+    priv->bus = NULL;
+    priv->pc = tree_require_named(dev->conf, "/pc-misc");
+    return 0;
+}
+
+static void
+dt_pci_init(struct dt_device *dev)
+{
+    struct dt_device_pci *priv = dev->priv;
+
+    priv->bus = i440fx_init(&i440fx_state, dt_pc_misc_i8259(priv->pc));
+}
+
+static void
+dt_pci_start(struct dt_device *dev)
+{
+    i440fx_init_memory_mappings(i440fx_state);
+}
+
+static void
+dt_must_be_on_pcibus(struct dt_device *dev)
+{
+    struct dt_device *bus = dt_device_of(tree_parent(dev->conf));
+
+    if (bus->drv->init != dt_pci_init) {
+       fprintf(stderr, "Device %s must be on a PCI bus\n", dev->drv->name);
+       exit(1);
+    }
+}
+
+static struct PCIBus *
+dt_get_pcibus(struct dt_device *dev)
+{
+    struct dt_device *bus = dt_device_of(tree_parent(dev->conf));
+
+    assert(bus->drv->init == dt_pci_init);
+    return ((struct dt_device_pci *)bus->priv)->bus;
+}
+
+
+/* PIIX3 Driver */
+
+struct dt_device_piix3 {
+    int devfn;
+    int acpi;
+    int usb;
+    struct tree *pc;
+    BlockDriverState *hd[MAX_IDE_BUS * MAX_IDE_DEVS];
+};
+
+static int
+dt_piix3_config(struct dt_device *dev)
+{
+    struct dt_device_piix3 *priv = dev->priv;
+
+    priv->devfn = -1;
+    priv->acpi = 1;
+    priv->usb = 1;
+    priv->pc = tree_require_named(dev->conf, "/pc-misc");
+    dt_drive_config(dev, priv->hd, sizeof(priv->hd) / sizeof(*priv->hd));
+    dt_must_be_on_pcibus(dev);
+    return 1;
+}
+
+static void
+dt_piix3_init(struct dt_device *dev)
+{
+    struct dt_device_piix3 *priv = dev->priv;
+    PCIBus *pci_bus = dt_get_pcibus(dev);
+    qemu_irq *i8259 = dt_pc_misc_i8259(priv->pc);
+    int i;
+
+    priv->devfn = piix3_init(pci_bus, priv->devfn);
+
+    pci_piix3_ide_init(pci_bus, priv->hd, priv->devfn + 1, i8259);
+
+    if (priv->usb)
+       usb_uhci_piix3_init(pci_bus, priv->devfn + 2);
+
+    if (priv->acpi) {
+        uint8_t *eeprom_buf = qemu_mallocz(8 * 256); /* XXX: make this 
persistent */
+        i2c_bus *smbus;
+
+        /* TODO: Populate SPD eeprom data.  */
+        smbus = piix4_pm_init(pci_bus, priv->devfn + 3, 0xb100, i8259[9]);
+        for (i = 0; i < 8; i++)
+            smbus_eeprom_device_init(smbus, 0x50 + i, eeprom_buf + (i * 256));
+    }
+}
+
+static BlockDriverState **
+dt_piix3_hd(struct tree *piix3)
+{
+    struct dt_device *dev = dt_device_of(piix3);
+    struct dt_device_piix3 *priv = dev->priv;
+
+    assert(dev->drv->init == dt_piix3_init);
+    return priv->hd;
+}
+
+
+/* VGA Driver */
+
+struct dt_driver_vga {
+    const char *model;
+    const char *bios;
+    void (*init)(PCIBus *, uint8_t *, ram_addr_t, int);
+};
+
+static void
+pci_vmsvga_init_(PCIBus *bus, uint8_t *vga_ram_base,
+                ram_addr_t vga_ram_offset, int vga_ram_size)
+{
+    pci_vmsvga_init(bus, vga_ram_base, vga_ram_offset, vga_ram_size);
+}
+
+static void
+pci_vga_init_(PCIBus *bus, uint8_t *vga_ram_base,
+             ram_addr_t vga_ram_offset, int vga_ram_size)
+{
+    pci_vga_init(bus, vga_ram_base, vga_ram_offset, vga_ram_size, 0, 0);
+}
+
+static struct dt_driver_vga dt_driver_vga_table[] = {
+    { "cirrus", VGABIOS_CIRRUS_FILENAME, pci_cirrus_vga_init },
+    { "vms", VGABIOS_FILENAME, pci_vmsvga_init_ },
+    { "std", VGABIOS_FILENAME, pci_vga_init_ },
+    { NULL, NULL, NULL }
+};
+
+struct dt_device_vga {
+    const char *model;
+    ram_addr_t ram_size;
+    struct dt_device_memrng rng[1];
+    ram_addr_t ram_offs;
+    struct dt_driver_vga *vga_drv;
+};
+
+static struct dt_prop_spec dt_vga_props[] = {
+    DT_PROP_SPEC_INIT("model", struct dt_device_vga, model, string),
+    DT_PROP_SPEC_INIT("ram", struct dt_device_vga, ram_size, ram_addr_t),
+};
+
+static int
+dt_vga_config(struct dt_device *dev)
+{
+    struct dt_device_vga *priv = dev->priv;
+    int i;
+
+    dt_memrng_rom(&priv->rng[0], 0xc0000, 0x10000,
+                 bios_dir, VGABIOS_CIRRUS_FILENAME, 0);
+                               /* TODO get name from dev->conf */
+    priv->ram_offs = qemu_ram_alloc(priv->ram_size);
+
+    for (i = 0; dt_driver_vga_table[i].model; i++) {
+       if (!strcmp(dt_driver_vga_table[i].model, priv->model))
+           break;
+    }
+    if (!dt_driver_vga_table[i].model) {
+       fprintf(stderr, "Unknown VGA model %s\n", priv->model);
+       exit(1);
+    }
+    priv->vga_drv = &dt_driver_vga_table[i];
+    dt_must_be_on_pcibus(dev);
+    return 0;
+}
+
+static void
+dt_vga_init(struct dt_device *dev)
+{
+    struct dt_device_vga *priv = dev->priv;
+
+    dt_memrng_init(priv->rng, 1);
+    priv->vga_drv->init(dt_get_pcibus(dev),
+                       phys_ram_base + priv->ram_offs,
+                       priv->ram_offs, priv->ram_size);
+}
+
+
+/* NIC Driver */
+
+struct dt_device_nic {
+    NICInfo nd;
+    int vlanid;
+};
+
+static struct dt_prop_spec dt_nic_props[] = {
+    DT_PROP_SPEC_INIT("model", struct dt_device_nic, nd.model, string),
+    DT_PROP_SPEC_INIT("mac", struct dt_device_nic, nd.macaddr, macaddr),
+    DT_PROP_SPEC_INIT("name", struct dt_device_nic, nd.name, string),
+    DT_PROP_SPEC_INIT("vlan", struct dt_device_nic, vlanid, int),
+};
+
+static int
+dt_nic_config(struct dt_device *dev)
+{
+    struct dt_device_nic *priv = dev->priv;
+
+    priv->nd.vlan = qemu_find_vlan(priv->vlanid);
+    dt_must_be_on_pcibus(dev);
+    return 0;
+}
+
+static void
+dt_nic_init(struct dt_device *dev)
+{
+    struct dt_device_nic *priv = dev->priv;
+
+    pci_nic_init(dt_get_pcibus(dev), &priv->nd, -1, NULL);
+}
+
+
+/* Machine Driver */
+
+static struct dt_driver dt_driver_table[] = {
+    { "", 0, NULL, NULL },
+    { "cpus", sizeof(struct dt_device_cpus), dt_cpus_props,
+      NULL, dt_cpus_init, NULL },
+    { "memory", sizeof(struct dt_device_memory), dt_memory_props,
+      dt_memory_config, dt_memory_init, NULL },
+    { "pc-misc", sizeof(struct dt_device_pc_misc), dt_pc_misc_props,
+      dt_pc_misc_config, dt_pc_misc_init, dt_pc_misc_start },
+    { "pci", sizeof(struct dt_device_pci), NULL,
+      dt_pci_config, dt_pci_init, dt_pci_start },
+    { "piix3", sizeof(struct dt_device_piix3), NULL,
+      dt_piix3_config, dt_piix3_init, NULL },
+    { "vga", sizeof(struct dt_device_vga), dt_vga_props,
+      dt_vga_config, dt_vga_init, NULL },
+    { "nic", sizeof(struct dt_device_nic), dt_nic_props,
+      dt_nic_config, dt_nic_init, NULL },
+    { NULL, 0, NULL, NULL, NULL }
+};
+
+static void
+dt_attach_drive(struct tree *controller, int drive_index)
+{
+    struct tree *node = tree_new_kid(controller, "drive", NULL);
+
+    tree_put_propf(node, "_index", "%d", drive_index);
+}
+
+/*
+ * Extract configuration from arguments and various global variables
+ * and put it into the configuration tree.
+ */
+static void
+dt_customize_config(struct tree *tree,
+                   ram_addr_t ram_size, int vga_ram_size,
+                   const char *boot_device,
+                   const char *kernel_filename,
+                   const char *kernel_cmdline,
+                   const char *initrd_filename,
+                   const char *cpu_model)
+{
+    struct tree *node, *pci;
+    int i, index;
+
+    node = tree_node_by_name(tree, "/cpus");
+    tree_put_propf(node, "num", "%d", smp_cpus);
+    if (cpu_model)
+       tree_put_propf(node, "model", "%s", cpu_model);
+
+    node = tree_node_by_name(tree, "/memory");
+    tree_put_propf(node, "ram", "%#lx", (unsigned long)ram_size);
+
+    pci = tree_node_by_name(tree, "/pci");
+    if (cirrus_vga_enabled || vmsvga_enabled || std_vga_enabled) {
+       node = tree_new_kid(pci, "vga", NULL);
+       tree_put_propf(node, "model", "%s",
+                         cirrus_vga_enabled ? "cirrus" :
+                         vmsvga_enabled ? "vms" : "std");
+       tree_put_propf(node, "ram", "%#x", vga_ram_size);
+    }
+
+    for(i = 0; i < nb_nics; i++) {
+       /* TODO non-PCI NICs */
+       struct NICInfo *n = &nd_table[i];
+
+       node = tree_new_kid(pci, "nic", NULL);
+       tree_put_propf(node, "mac", "%02x:%02x:%02x:%02x:%02x:%02x",
+                      n->macaddr[0], n->macaddr[1], n->macaddr[2],
+                      n->macaddr[3], n->macaddr[4], n->macaddr[5]);
+       tree_put_propf(node, "model", "%s",
+                      n->model ? n->model : "ne2k_pci");
+       if (n->name)
+           tree_put_propf(node, "name", "%s", n->name);
+       tree_put_propf(node, "vlan", "%d", n->vlan->id);
+    }
+
+    node = tree_node_by_name(pci, "piix3");
+    for(i = 0; i < MAX_IDE_BUS * MAX_IDE_DEVS; i++) {
+        index = drive_get_index(IF_IDE, i / MAX_IDE_DEVS, i % MAX_IDE_DEVS);
+       if (index != -1)
+           dt_attach_drive(node, index);
+    }
+
+    node = tree_node_by_name(tree, "/pc-misc");
+    tree_put_propf(node, "boot-device", "%s", boot_device);
+    for(i = 0; i < MAX_FD; i++) {
+        index = drive_get_index(IF_FLOPPY, 0, i);
+       if (index != -1)
+           dt_attach_drive(node, index);
+    }
+
+    if (kernel_filename)
+       abort();                /* TODO */
+}
+
+static void
+pc_init_dt(ram_addr_t ram_size, int vga_ram_size,
+          const char *boot_device,
+          const char *kernel_filename,
+          const char *kernel_cmdline,
+          const char *initrd_filename,
+          const char *cpu_model)
+{
+    struct tree *tree;
+
+    tree = tree_create();
+    if (!tree)
+       exit(1);
+    tree_print(tree);
+    dt_customize_config(tree, ram_size, vga_ram_size, boot_device,
+                       kernel_filename, kernel_cmdline, initrd_filename,
+                       cpu_model);
+    dt_config(tree);
+    tree_print(tree);
+    dt_init(tree);
+    dt_start(tree);
+}
+
+QEMUMachine pcdt_machine = {
+    .name = "pcdt",
+    .desc = "Standard PC (device tree)",
+    .init = pc_init_dt,
+    .ram_require = VGA_RAM_SIZE + PC_MAX_BIOS_SIZE,
+    .max_cpus = 255,
+};
diff --git a/hw/pc.c b/hw/pc.c
index 176730e..fc9ee20 100644
--- a/hw/pc.c
+++ b/hw/pc.c
@@ -57,21 +57,21 @@ static fdctrl_t *floppy_controller;
 static RTCState *rtc_state;
 static PITState *pit;
 static IOAPICState *ioapic;
-static PCIDevice *i440fx_state;
+PCIDevice *i440fx_state;
 
 static void ioport80_write(void *opaque, uint32_t addr, uint32_t data)
 {
 }
 
 /* MSDOS compatibility mode FPU exception support */
-static qemu_irq ferr_irq;
+qemu_irq ferr_irq;
 /* XXX: add IGNNE support */
 void cpu_set_ferr(CPUX86State *s)
 {
     qemu_irq_raise(ferr_irq);
 }
 
-static void ioportF0_write(void *opaque, uint32_t addr, uint32_t data)
+void ioportF0_write(void *opaque, uint32_t addr, uint32_t data)
 {
     qemu_irq_lower(ferr_irq);
 }
diff --git a/target-i386/machine.c b/target-i386/machine.c
index faab2eb..5a2a0c2 100644
--- a/target-i386/machine.c
+++ b/target-i386/machine.c
@@ -7,6 +7,8 @@
 
 void register_machines(void)
 {
+    extern QEMUMachine pcdt_machine;
+    qemu_register_machine(&pcdt_machine);
     qemu_register_machine(&pc_machine);
     qemu_register_machine(&isapc_machine);
 }
diff --git a/tree.c b/tree.c
new file mode 100644
index 0000000..66c3ea5
--- /dev/null
+++ b/tree.c
@@ -0,0 +1,374 @@
+/* Ye Olde Decorated Tree */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "tree.h"
+#include "qemu-common.h"
+#include "sys-queue.h"
+
+struct tree {
+    const char *name;
+    LIST_HEAD(, tree_prop) props;
+    struct tree *parent;
+    TAILQ_HEAD(, tree) kids;
+    TAILQ_ENTRY(tree) siblings;
+    void *user;
+    LIST_HEAD(, tree) reqs;
+    LIST_ENTRY(tree) reqlink;
+    int visit;
+};
+
+struct tree_prop {
+    const char *name;
+    const void *val;
+    int sz;
+    struct tree *owner;
+    LIST_ENTRY(tree_prop) link;
+};
+
+struct tree *
+tree_create(void)
+{
+    /* TODO read from a config file; tree is hardcoded for now */
+    static struct tree dt_root = {
+       .name = "",
+    };
+
+    static struct tree dt_cpus = {
+       .name = "cpus",
+       .parent = &dt_root,
+    };
+
+    static struct tree_prop dt_cpus_model = {
+       .name = "model",
+       .val = "qemu32",
+       .sz = -1,
+       .owner = &dt_cpus,
+    };
+
+    static struct tree dt_memory = {
+       .name = "memory",
+       .parent = &dt_root,
+    };
+
+    static struct tree dt_pc_misc = {
+       .name = "pc-misc",
+       .parent = &dt_root,
+    };
+
+    static struct tree dt_pci = {
+       .name = "pci",
+       .parent = &dt_root,
+    };
+
+    static struct tree dt_piix3 = {
+       .name = "piix3",
+       .parent = &dt_pci,
+    };
+
+    static struct tree *dt_nodes[] = {
+       &dt_root, &dt_cpus, &dt_memory, &dt_pc_misc,
+       &dt_pci, &dt_piix3,
+       NULL
+    };
+
+    static struct tree_prop *dt_props[] = {
+       &dt_cpus_model,
+       NULL
+    };
+
+    int i;
+
+    for (i = 0; dt_nodes[i]; i++) {
+       LIST_INIT(&dt_nodes[i]->props);
+       TAILQ_INIT(&dt_nodes[i]->kids);
+       LIST_INIT(&dt_nodes[i]->reqs);
+       if (dt_nodes[i]->parent)
+           TAILQ_INSERT_TAIL(&dt_nodes[i]->parent->kids,
+                             dt_nodes[i], siblings);
+    }
+    for (i = 0; dt_props[i]; i++) {
+       if (dt_props[i]->sz < 0)
+           dt_props[i]->sz = strlen(dt_props[i]->val) + 1;
+       LIST_INSERT_HEAD(&dt_props[i]->owner->props, dt_props[i], link);
+    }
+
+    return &dt_root;
+}
+
+const char *
+tree_node_name(const struct tree *node)
+{
+    return node->name;
+}
+
+static struct tree *
+tree_kid_by_name(const struct tree *dt, const char *name)
+{
+    const char *slash = strchr(name, '/');
+    size_t len = slash ? slash - name : strlen(name);
+    struct tree *kid;
+
+    TAILQ_FOREACH(kid, &dt->kids, siblings) {
+       if (!memcmp(kid->name, name, len) && kid->name[len] == 0)
+           return kid;
+    }
+    return NULL;
+}
+
+struct tree *
+tree_node_by_name(const struct tree *node, const char *name)
+{
+    struct tree *kid;
+    size_t len;
+
+    if (name[0] == '/') {
+       for (; node->parent; node = node->parent) ;
+       name++;
+    }
+
+    if (name[0] == 0)
+       return (struct tree *)node;
+
+    kid = tree_kid_by_name(node, name);
+    if (!kid)
+       return NULL;
+
+    len = strlen(kid->name);
+    if (name[len] == 0)
+       return kid;
+    assert (name[len] == '/');
+
+    while (name[len] == '/') len++;
+    return tree_node_by_name(kid, name + len);
+}
+
+struct tree_prop *
+tree_first_prop(const struct tree *node)
+{
+    return LIST_FIRST(&node->props);
+}
+
+struct tree_prop *
+tree_next_prop(const struct tree_prop *prop)
+{
+    return LIST_NEXT(prop, link);
+}
+
+struct tree_prop *
+tree_get_prop(const struct tree *node, const char *name)
+{
+    struct tree_prop *prop;
+
+    LIST_FOREACH(prop, &node->props, link) {
+       if (!strcmp(prop->name, name))
+           return prop;
+    }
+    return NULL;
+}
+
+const char *
+tree_get_prop_s(const struct tree *node, const char *name)
+{
+    struct tree_prop *prop = tree_get_prop(node, name);
+    if (!prop
+       || memchr(prop->val, 0, prop->sz) != prop->val + prop->sz - 1) {
+       errno = EINVAL;
+       return NULL;
+    }
+    return prop->val;
+}
+
+const char *
+tree_prop_name(const struct tree_prop *prop)
+{
+    return prop->name;
+}
+
+const void *
+tree_prop_value(const struct tree_prop *prop, size_t *size)
+{
+    if (size)
+       *size = prop->sz;
+    return prop->val;
+}
+
+void
+tree_put_prop(struct tree *node, const char *name,
+             const void *val, size_t sz)
+{
+    struct tree_prop *prop;
+
+    prop = tree_get_prop(node, name);
+    if (!prop) {
+       prop = qemu_malloc(sizeof(*prop));
+       prop->name = name;
+       prop->owner = node;
+       LIST_INSERT_HEAD(&node->props, prop, link);
+    }
+    /* FIXME need a destructor for val */
+    prop->val = val;
+    prop->sz = sz;
+}
+
+void
+tree_put_propf(struct tree *node, const char *name, const char *fmt, ...)
+{
+    va_list ap;
+    size_t len;
+    char *buf;
+
+    va_start(ap, fmt);
+    len = vsnprintf(NULL, 0, fmt, ap);
+    va_end(ap);
+
+    buf = qemu_malloc(len + 1);
+    va_start(ap, fmt);
+    vsnprintf(buf, len + 1, fmt, ap);
+    va_end(ap);
+
+    tree_put_prop(node, name, buf, len + 1);
+}
+
+void
+tree_put_user(struct tree *node, void *user)
+{
+    node->user = user;
+}
+
+void *
+tree_get_user(const struct tree *node)
+{
+    return node->user;
+}
+
+struct tree *
+tree_new_kid(struct tree *parent, const char *name, void *user)
+{
+    struct tree *kid = qemu_malloc(sizeof(*kid));
+
+    kid->name = name;
+    LIST_INIT(&kid->props);
+    kid->parent = parent;
+    TAILQ_INIT(&kid->kids);
+    TAILQ_INSERT_TAIL(&parent->kids, kid, siblings);
+    kid->user = user;
+    LIST_INIT(&kid->reqs);
+    kid->visit = 0;
+
+    return kid;
+}
+
+struct tree *
+tree_parent(const struct tree *node)
+{
+    return node->parent;
+}
+
+struct tree *
+tree_first_kid(const struct tree *node)
+{
+    return TAILQ_FIRST(&node->kids);
+}
+
+struct tree *
+tree_sibling(const struct tree *node)
+{
+    return TAILQ_NEXT(node, siblings);
+}
+
+void
+tree_require(struct tree *node, struct tree *req)
+{
+    LIST_INSERT_HEAD(&node->reqs, req, reqlink);
+}
+
+struct tree *
+tree_require_named(struct tree *node, const char *reqname)
+{
+    struct tree *req = tree_node_by_name(node, reqname);
+    tree_require(node, req);
+    return req;
+}
+
+static void
+tree_do_visit(struct tree *node,
+             void (*fun)(struct tree *, void *arg),
+             void *arg, int visit)
+{
+    struct tree *req, *kid;
+
+    assert(node->visit < visit - 1);
+    node->visit = visit - 1;
+    if (node->parent && node->parent->visit < visit)
+       tree_do_visit(node->parent, fun, arg, visit);
+    LIST_FOREACH(req, &node->reqs, reqlink) {
+       if (req->visit < visit)
+           tree_do_visit(req, fun, arg, visit);
+    }
+    node->visit = visit;
+    fun(node, arg);
+    TAILQ_FOREACH(kid, &node->kids, siblings) {
+       if (kid->visit < visit - 1)
+           tree_do_visit(kid, fun, arg, visit);
+    }
+}
+
+void
+tree_visit(struct tree *node,
+          void (*fun)(struct tree *, void *arg),
+          void *arg)
+{
+    static int visit;
+
+    visit += 2;
+    tree_do_visit(node, fun, arg, visit);
+}
+
+static void
+tree_print_sub(const struct tree *node, int indent)
+{
+    int i, use_str, sep;
+    const unsigned char *pv;
+    struct tree_prop *prop;
+    struct tree *kid;
+
+    printf("%*s%s {\n", indent, "", node->name[0] ? node->name : "/");
+    LIST_FOREACH(prop, &node->props, link) {
+       printf("%*s%s", indent + 4, "", prop->name);
+       pv = prop->val;
+       if (pv) {
+           printf(" = ");
+           use_str = pv[prop->sz - 1] == 0;
+           for (i = 0; i < prop->sz - 1; i++) {
+               if (!isprint(pv[i]))
+                   use_str = 0;
+           }
+           if (use_str)
+               printf("\"%s\"", (const char *)prop->val);
+           else {
+               sep = '[';
+               for (i = 0; i < prop->sz; i++) {
+                   printf("%c%02x", sep, pv[i]);
+                   sep = ' ';
+               }
+               printf("]");
+           }
+       }
+       printf(";\n");
+    }
+    TAILQ_FOREACH(kid, &node->kids, siblings)
+       tree_print_sub(kid, indent + 4);
+    printf("%*s};\n", indent, "");
+}
+
+void
+tree_print(const struct tree *node)
+{
+    tree_print_sub(node, 0);
+}
diff --git a/tree.h b/tree.h
new file mode 100644
index 0000000..4b91cf0
--- /dev/null
+++ b/tree.h
@@ -0,0 +1,46 @@
+#ifndef TREE_H
+#define TREE_H
+
+#include <stddef.h>
+
+struct tree;
+struct tree_prop;
+
+struct tree *tree_create(void);
+const char *tree_node_name(const struct tree *node);
+struct tree *tree_node_by_name(const struct tree *node,
+                              const char *name);
+
+struct tree_prop *tree_first_prop(const struct tree *node);
+struct tree_prop *tree_next_prop(const struct tree_prop *prop);
+#define TREE_FOREACH_PROP(var, node) \
+    for (var = tree_first_prop(node); var; var = tree_next_prop(var))
+struct tree_prop *tree_get_prop(const struct tree *node, const char *name);
+const char *tree_get_prop_s(const struct tree *node, const char *name);
+const char *tree_prop_name(const struct tree_prop *prop);
+const void *tree_prop_value(const struct tree_prop *prop, size_t *size);
+void tree_put_prop(struct tree *node, const char *name,
+                  const void *val, size_t sz);
+void tree_put_propf(struct tree *node, const char *name,
+                   const char *fmt, ...)
+    __attribute__((format(printf,3,4)));
+
+void tree_put_user(struct tree *node, void *user);
+void *tree_get_user(const struct tree *node);
+
+struct tree *tree_new_kid(struct tree *parent, const char *name, void *user);
+struct tree *tree_parent(const struct tree *node);
+struct tree *tree_first_kid(const struct tree *node);
+struct tree *tree_sibling(const struct tree *node);
+#define TREE_FOREACH_KID(var, node)                                    \
+    for (var = tree_first_kid(node); var; var = tree_sibling(var))
+
+void tree_require(struct tree *node, struct tree *req);
+struct tree *tree_require_named(struct tree *node, const char *reqname);
+void tree_visit(struct tree *node,
+               void (*fun)(struct tree *, void *arg),
+               void *arg);
+
+void tree_print(const struct tree *node);
+
+#endif




reply via email to

[Prev in Thread] Current Thread [Next in Thread]