diff --git a/hw/Makefile.objs b/hw/Makefile.objs
index 59dd2d5..ebbeb16 100644
--- a/hw/Makefile.objs
+++ b/hw/Makefile.objs
@@ -72,6 +72,13 @@ hw-obj-$(CONFIG_ALTERA) += altera_vic.o
 hw-obj-$(CONFIG_ALTERA) += altera_uart.o
 hw-obj-$(CONFIG_ALTERA) += altera_timer.o
 
+# Lab X devices
+hw-obj-$(CONFIG_LABX) += labx_audio_packetizer.o
+hw-obj-$(CONFIG_LABX) += labx_audio_depacketizer.o
+hw-obj-$(CONFIG_LABX) += labx_dma.o
+hw-obj-$(CONFIG_LABX) += labx_ethernet.o
+hw-obj-$(CONFIG_LABX) += labx_ptp.o
+
 # PKUnity SoC devices
 hw-obj-$(CONFIG_PUV3) += puv3_intc.o
 hw-obj-$(CONFIG_PUV3) += puv3_ost.o
diff --git a/hw/labx_audio_depacketizer.c b/hw/labx_audio_depacketizer.c
new file mode 100644
index 0000000..5da3f47
--- /dev/null
+++ b/hw/labx_audio_depacketizer.c
@@ -0,0 +1,409 @@
+
+/*
+ * QEMU model of the LabX audio depacketizer.
+ *
+ * Copyright (c) 2010 Lab X Technologies, LLC
+ *
+ * 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.1 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/lgpl-2.1.html>
+ */
+
+#include "sysbus.h"
+#include "sysemu.h"
+#include "labx_devices.h"
+
+#define min_bits qemu_fls
+#define RAM_INDEX(addr, size) (((addr)>>2)&((1<<min_bits((size)-1))-1))
+
+struct clock_domain_info {
+    uint32_t tsInterval;
+};
+
+typedef struct audio_depacketizer {
+    SysBusDevice busdev;
+
+    MemoryRegion  mmio_depacketizer;
+    MemoryRegion  mmio_clock_domain;
+    MemoryRegion  mmio_microcode;
+
+    /* Device Configuration */
+    uint32_t baseAddress;
+    uint32_t clockDomains;
+    uint32_t cacheDataWords;
+    uint32_t paramWords;
+    uint32_t microcodeWords;
+    uint32_t maxStreamSlots;
+    uint32_t maxStreams;
+    uint32_t hasDMA;
+    uint32_t matchArch;
+
+    /* IRQ */
+    qemu_irq irq;
+
+    /* Values set by drivers */
+
+    /* Microcode buffer */
+    uint32_t *microcodeRam;
+
+    /* Clock domain information */
+    struct clock_domain_info *clockDomainInfo;
+
+    /* Attached DMA (if hasDMA > 0) */
+    DeviceState *dma;
+} depacketizer_t;
+
+/*
+ * Depacketizer registers
+ */
+static uint64_t depacketizer_regs_read(void *opaque, target_phys_addr_t addr,
+                                       unsigned int size)
+{
+    depacketizer_t *p = opaque;
+
+    uint32_t retval = 0;
+
+    switch ((addr>>2) & 0xFF) {
+    case 0x00: /* control */
+        break;
+
+    case 0x01: /* vector bar */
+        break;
+
+    case 0x02: /* id select 0 */
+        break;
+
+    case 0x03: /* id select 1 */
+        break;
+
+    case 0x04: /* id select 2 */
+        break;
+
+    case 0x05: /* id select 3 */
+        break;
+
+    case 0x06: /* id config data */
+        break;
+
+    case 0x08: /* irq mask */
+        break;
+
+    case 0x09: /* irq flags */
+        break;
+
+    case 0x0A: /* sync */
+        break;
+
+    case 0x0B: /* relocate */
+        break;
+
+    case 0x0C: /* stream status 0 */
+        break;
+
+    case 0x0D: /* stream status 1 */
+        break;
+
+    case 0x0E: /* stream status 2 */
+        break;
+
+    case 0x0F: /* stream status 3 */
+        break;
+
+    case 0xFD: /* capabilities a */
+        retval = (p->maxStreamSlots & 0x7F);
+        break;
+
+    case 0xFE: /* capabilities b */
+        retval = ((p->matchArch & 0xFF) << 24) |
+                 ((p->maxStreams & 0xFF) << 16) |
+                 ((p->clockDomains & 0xFF) << 8) |
+                 ((min_bits(p->paramWords-1) & 0x0F) << 4) |
+                 ((min_bits(p->microcodeWords-1) & 0x0F));
+        break;
+
+    case 0xFF: /* revision */
+        retval = 0x00000014;
+        break;
+
+    default:
+        printf("labx-audio-depacketizer: Read of unknown register %08X\n",
+               addr);
+        break;
+    }
+
+    return retval;
+}
+
+static void depacketizer_regs_write(void *opaque, target_phys_addr_t addr,
+                                    uint64_t val64, unsigned int size)
+{
+    /*depacketizer_t *p = opaque; */
+    uint32_t value = val64;
+
+    switch ((addr>>2) & 0xFF) {
+    case 0x00: /* control */
+        break;
+
+    case 0x01: /* vector bar */
+        break;
+
+    case 0x02: /* id select 0 */
+        break;
+
+    case 0x03: /* id select 1 */
+        break;
+
+    case 0x04: /* id select 2 */
+        break;
+
+    case 0x05: /* id select 3 */
+        break;
+
+    case 0x06: /* id config data */
+        break;
+
+    case 0x08: /* irq mask */
+        break;
+
+    case 0x09: /* irq flags */
+        break;
+
+    case 0x0A: /* sync */
+        break;
+
+    case 0x0B: /* relocate */
+        break;
+
+    case 0x0C: /* stream status 0 */
+        break;
+
+    case 0x0D: /* stream status 1 */
+        break;
+
+    case 0x0E: /* stream status 2 */
+        break;
+
+    case 0x0F: /* stream status 3 */
+        break;
+
+    case 0xFD: /* capabilities a */
+        break;
+
+    case 0xFE: /* capabilities b */
+        break;
+
+    case 0xFF: /* revision */
+        break;
+
+    default:
+        printf("labx-audio-depacketizer: Write of unknown register "
+               "%08X = %08X\n", addr, value);
+        break;
+    }
+}
+
+static const MemoryRegionOps depacketizer_regs_ops = {
+    .read = depacketizer_regs_read,
+    .write = depacketizer_regs_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+
+/*
+ * Clock domain registers
+ */
+static uint64_t clock_domain_regs_read(void *opaque, target_phys_addr_t addr,
+                                       unsigned int size)
+{
+    depacketizer_t *p = opaque;
+
+    uint32_t retval = 0;
+    int domain = (addr>>6) & ((1<<min_bits(p->clockDomains-1))-1);
+
+    switch ((addr>>2)&0x10) {
+    case 0x00: /* recovery index */
+        break;
+
+    case 0x01: /* ts interval */
+        retval = p->clockDomainInfo[domain].tsInterval;
+        break;
+
+    case 0x08: /* DAC offset */
+        break;
+
+    case 0x09: /* DAC P coeff */
+        break;
+
+    case 0x0A: /* lock count */
+        break;
+
+    default:
+        break;
+    }
+
+    return retval;
+}
+
+static void clock_domain_regs_write(void *opaque, target_phys_addr_t addr,
+                                    uint64_t val64, unsigned int size)
+{
+    depacketizer_t *p = opaque;
+    uint32_t value = val64;
+    int domain = (addr>>6) & ((1<<min_bits(p->clockDomains-1))-1);
+
+    switch ((addr>>2)&0x10) {
+    case 0x00: /* recovery index */
+        break;
+
+    case 0x01: /* ts interval */
+        p->clockDomainInfo[domain].tsInterval = value;
+        break;
+
+    case 0x08: /* DAC offset */
+        break;
+
+    case 0x09: /* DAC P coeff */
+        break;
+
+    case 0x0A: /* lock count */
+        break;
+
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps clock_domain_regs_ops = {
+    .read = clock_domain_regs_read,
+    .write = clock_domain_regs_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+
+/*
+ * Microcode RAM
+ */
+static uint64_t microcode_ram_read(void *opaque, target_phys_addr_t addr,
+                                   unsigned int size)
+{
+    depacketizer_t *p = opaque;
+
+    return p->microcodeRam[RAM_INDEX(addr, p->microcodeWords)];
+}
+
+static void microcode_ram_write(void *opaque, target_phys_addr_t addr,
+                               uint64_t val64, unsigned int size)
+{
+    depacketizer_t *p = opaque;
+    uint32_t value = val64;
+
+    p->microcodeRam[RAM_INDEX(addr, p->microcodeWords)] = value;
+}
+
+static const MemoryRegionOps microcode_ram_ops = {
+    .read = microcode_ram_read,
+    .write = microcode_ram_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+
+static int labx_audio_depacketizer_init(SysBusDevice *dev)
+{
+    depacketizer_t *p = FROM_SYSBUS(typeof(*p), dev);
+
+    /* Initialize defaults */
+    p->microcodeRam = g_malloc0(p->microcodeWords*4);
+    p->clockDomainInfo = g_malloc0(sizeof(struct clock_domain_info) *
+                                   p->clockDomains);
+
+    /* Set up the IRQ */
+    sysbus_init_irq(dev, &p->irq);
+
+    /* Set up memory regions */
+    memory_region_init_io(&p->mmio_depacketizer, &depacketizer_regs_ops, p,
+                          "labx,audio-depacketizer-regs",
+                          0x100 * 4);
+    memory_region_init_io(&p->mmio_clock_domain, &clock_domain_regs_ops, p,
+                          "labx,audio-depacketizer-cd-regs",
+                          0x10 * 4 * p->clockDomains);
+    memory_region_init_io(&p->mmio_microcode,    &microcode_ram_ops,     p,
+                          "labx,audio-depacketizer-microcode",
+                          4 * p->microcodeWords);
+
+    sysbus_init_mmio(dev, &p->mmio_depacketizer);
+    sysbus_init_mmio(dev, &p->mmio_clock_domain);
+    sysbus_init_mmio(dev, &p->mmio_microcode);
+
+    sysbus_mmio_map(dev, 0, p->baseAddress);
+    sysbus_mmio_map(dev, 1, p->baseAddress +
+                            (1 << (min_bits(p->microcodeWords-1)+2)));
+    sysbus_mmio_map(dev, 2, p->baseAddress +
+                            (2 << (min_bits(p->microcodeWords-1)+2)));
+
+    if (p->hasDMA) {
+        p->dma = labx_dma_create(p->baseAddress +
+                                 (4 << (min_bits(p->microcodeWords-1)+2)),
+                                 1024);
+    }
+
+    return 0;
+}
+
+static Property labx_audio_depacketizer_properties[] = {
+    DEFINE_PROP_UINT32("baseAddress",    depacketizer_t, baseAddress,    0),
+    DEFINE_PROP_UINT32("clockDomains",   depacketizer_t, clockDomains,   1),
+    DEFINE_PROP_UINT32("cacheDataWords", depacketizer_t, cacheDataWords, 1024),
+    DEFINE_PROP_UINT32("paramWords",     depacketizer_t, paramWords,     1024),
+    DEFINE_PROP_UINT32("microcodeWords", depacketizer_t, microcodeWords, 1024),
+    DEFINE_PROP_UINT32("maxStreamSlots", depacketizer_t, maxStreamSlots, 32),
+    DEFINE_PROP_UINT32("maxStreams",     depacketizer_t, maxStreams,     128),
+    DEFINE_PROP_UINT32("hasDMA",         depacketizer_t, hasDMA,         1),
+    DEFINE_PROP_UINT32("matchArch",      depacketizer_t, matchArch,      255),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void labx_audio_depacketizer_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+    k->init = labx_audio_depacketizer_init;
+    dc->props = labx_audio_depacketizer_properties;
+}
+
+static TypeInfo labx_audio_depacketizer_info = {
+    .name          = "labx,audio-depacketizer",
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(depacketizer_t),
+    .class_init    = labx_audio_depacketizer_class_init,
+};
+
+static void labx_audio_depacketizer_register(void)
+{
+    type_register_static(&labx_audio_depacketizer_info);
+}
+
+type_init(labx_audio_depacketizer_register)
+
diff --git a/hw/labx_audio_packetizer.c b/hw/labx_audio_packetizer.c
new file mode 100644
index 0000000..120cce0
--- /dev/null
+++ b/hw/labx_audio_packetizer.c
@@ -0,0 +1,397 @@
+
+/*
+ * QEMU model of the LabX audio packetizer.
+ *
+ * Copyright (c) 2010 Lab X Technologies, LLC
+ *
+ * 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.1 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/lgpl-2.1.html>
+ */
+
+#include "sysbus.h"
+#include "sysemu.h"
+
+#define min_bits qemu_fls
+#define RAM_INDEX(addr, size) (((addr)>>2)&((1<<min_bits((size)-1))-1))
+
+struct clock_domain_info {
+    uint32_t tsInterval;
+    uint32_t domainEnabled;
+};
+
+typedef struct audio_packetizer {
+    SysBusDevice busdev;
+
+    MemoryRegion  mmio_packetizer;
+    MemoryRegion  mmio_clock_domain;
+    MemoryRegion  mmio_template;
+    MemoryRegion  mmio_microcode;
+
+    /* Device Configuration */
+    uint32_t baseAddress;
+    uint32_t clockDomains;
+    uint32_t cacheDataWords;
+    uint32_t templateWords;
+    uint32_t microcodeWords;
+    uint32_t shaperFractionBits;
+    uint32_t maxStreamSlots;
+    uint32_t dualOutput;
+
+    /* IRQ */
+    qemu_irq irq;
+
+    /* Values set by drivers */
+    uint32_t tsOffset;
+    uint32_t sendSlope;
+    uint32_t idleSlope;
+
+    /* Microcode buffer */
+    uint32_t *microcodeRam;
+
+    /* Template buffer */
+    uint32_t *templateRam;
+
+    /* Clock domain information */
+    struct clock_domain_info *clockDomainInfo;
+} packetizer_t;
+
+/*
+ * Packetizer registers
+ */
+static uint64_t packetizer_regs_read(void *opaque, target_phys_addr_t addr,
+                                     unsigned int size)
+{
+    packetizer_t *p = opaque;
+
+    uint32_t retval = 0;
+
+    switch ((addr>>2) & 0xFF) {
+    case 0x00: /* control */
+        break;
+
+    case 0x01: /* start vector */
+        break;
+
+    case 0x02: /* ts offset */
+        break;
+
+    case 0x03: /* irq mask */
+        break;
+
+    case 0x04: /* irq flags */
+        break;
+
+    case 0x05: /* sync reg */
+        break;
+
+    case 0x06: /* send slope */
+        break;
+
+    case 0x07: /* idle slope */
+        break;
+
+    case 0xFD: /* capabilities a */
+        retval = (p->maxStreamSlots & 0x7F) | ((p->dualOutput) ? 0x80 : 0x00);
+        break;
+
+    case 0xFE: /* capabilities b */
+        retval = ((p->shaperFractionBits & 0x7F) << 24) |
+                 ((p->clockDomains & 0xFF) << 16) |
+                 ((min_bits(p->templateWords-1) & 0xFF) << 8) |
+                 ((min_bits(p->microcodeWords-1) & 0xFF));
+        break;
+
+    case 0xFF: /* revision */
+        retval = 0x00000013;
+        break;
+
+    default:
+        printf("labx-audio-packetizer: Read of unknown register %08X\n", addr);
+        break;
+    }
+
+    return retval;
+}
+
+static void packetizer_regs_write(void *opaque, target_phys_addr_t addr,
+                                  uint64_t val64, unsigned int size)
+{
+    packetizer_t *p = opaque;
+    uint32_t value = val64;
+
+    switch ((addr>>2) & 0xFF) {
+    case 0x00: /* control */
+        break;
+
+    case 0x01: /* start vector */
+        break;
+
+    case 0x02: /* ts offset */
+        p->tsOffset = value;
+        break;
+
+    case 0x03: /* irq mask */
+        break;
+
+    case 0x04: /* irq flags */
+        break;
+
+    case 0x05: /* sync reg */
+        break;
+
+    case 0x06: /* send slope */
+        p->sendSlope = value;
+        break;
+
+    case 0x07: /* idle slope */
+        p->idleSlope = value;
+        break;
+
+    case 0xFD: /* capabilities a */
+        break;
+
+    case 0xFE: /* capabilities b */
+        break;
+
+    case 0xFF: /* revision */
+        break;
+
+    default:
+        printf("labx-audio-packetizer: Write of unknown register "
+               "%08X = %08X\n", addr, value);
+        break;
+    }
+}
+
+static const MemoryRegionOps packetizer_regs_ops = {
+    .read = packetizer_regs_read,
+    .write = packetizer_regs_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+
+/*
+ * Clock domain registers
+ */
+static uint64_t clock_domain_regs_read(void *opaque, target_phys_addr_t addr,
+                                       unsigned int size)
+{
+    packetizer_t *p = opaque;
+
+    uint32_t retval = 0;
+    int domain = (addr>>3) & ((1<<min_bits(p->clockDomains-1))-1);
+
+    switch ((addr>>2)&0x01) {
+    case 0x00: /* ts interval */
+        retval = p->clockDomainInfo[domain].tsInterval;
+        break;
+
+    case 0x01: /* domain enable */
+        retval = p->clockDomainInfo[domain].domainEnabled;
+        break;
+
+    default:
+        break;
+    }
+
+    return retval;
+}
+
+static void clock_domain_regs_write(void *opaque, target_phys_addr_t addr,
+                                    uint64_t val64, unsigned int size)
+{
+    packetizer_t *p = opaque;
+    uint32_t value = val64;
+
+    int domain = (addr>>3) & ((1<<min_bits(p->clockDomains-1))-1);
+
+    switch ((addr>>2)&0x01) {
+    case 0x00: /* ts interval */
+        p->clockDomainInfo[domain].tsInterval = value;
+        break;
+
+    case 0x01: /* domain enable */
+        p->clockDomainInfo[domain].domainEnabled = value;
+        break;
+
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps clock_domain_regs_ops = {
+    .read = clock_domain_regs_read,
+    .write = clock_domain_regs_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+
+/*
+ * Template RAM
+ */
+static uint64_t template_ram_read(void *opaque, target_phys_addr_t addr,
+                                  unsigned int size)
+{
+    packetizer_t *p = opaque;
+
+    return p->templateRam[RAM_INDEX(addr, p->templateWords)];
+}
+
+static void template_ram_write(void *opaque, target_phys_addr_t addr,
+                               uint64_t val64, unsigned int size)
+{
+    packetizer_t *p = opaque;
+    uint32_t value = val64;
+
+    p->templateRam[RAM_INDEX(addr, p->templateWords)] = value;
+}
+
+static const MemoryRegionOps template_ram_ops = {
+    .read = template_ram_read,
+    .write = template_ram_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+
+/*
+ * Microcode RAM
+ */
+static uint64_t microcode_ram_read(void *opaque, target_phys_addr_t addr,
+                                   unsigned int size)
+{
+    packetizer_t *p = opaque;
+
+    return p->microcodeRam[RAM_INDEX(addr, p->microcodeWords)];
+}
+
+static void microcode_ram_write(void *opaque, target_phys_addr_t addr,
+                                uint64_t val64, unsigned int size)
+{
+    packetizer_t *p = opaque;
+    uint32_t value = val64;
+
+    p->microcodeRam[RAM_INDEX(addr, p->microcodeWords)] = value;
+}
+
+static const MemoryRegionOps microcode_ram_ops = {
+    .read = microcode_ram_read,
+    .write = microcode_ram_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+static int labx_audio_packetizer_init(SysBusDevice *dev)
+{
+    packetizer_t *p = FROM_SYSBUS(typeof(*p), dev);
+
+    /* Initialize defaults */
+    p->tsOffset = 0x00000000;
+    p->sendSlope = 0x00000000;
+    p->idleSlope = 0x00000000;
+    p->templateRam = g_malloc0(p->templateWords*4);
+    p->microcodeRam = g_malloc0(p->microcodeWords*4);
+    p->clockDomainInfo = g_malloc0(sizeof(struct clock_domain_info) *
+                                   p->clockDomains);
+
+    /* Set up the IRQ */
+    sysbus_init_irq(dev, &p->irq);
+
+    /* Set up memory regions */
+    memory_region_init_io(&p->mmio_packetizer,   &packetizer_regs_ops,   p,
+                          "labx,audio-packetizer-regs",
+                          0x100 * 4);
+    memory_region_init_io(&p->mmio_clock_domain, &clock_domain_regs_ops, p,
+                          "labx,audio-packetizer-cd-regs",
+                          2 * 4 * p->clockDomains);
+    memory_region_init_io(&p->mmio_template,     &template_ram_ops,      p,
+                          "labx,audio-packetizer-template",
+                          4 * p->templateWords);
+    memory_region_init_io(&p->mmio_microcode,    &microcode_ram_ops,     p,
+                          "labx,audio-packetizer-microcode",
+                          4 * p->microcodeWords);
+
+    sysbus_init_mmio(dev, &p->mmio_packetizer);
+    sysbus_init_mmio(dev, &p->mmio_clock_domain);
+    sysbus_init_mmio(dev, &p->mmio_template);
+    sysbus_init_mmio(dev, &p->mmio_microcode);
+
+    sysbus_mmio_map(dev, 0, p->baseAddress);
+    sysbus_mmio_map(dev, 1, p->baseAddress +
+                            (1 << (min_bits(p->microcodeWords-1)+2)));
+    sysbus_mmio_map(dev, 2, p->baseAddress +
+                            (2 << (min_bits(p->microcodeWords-1)+2)));
+    sysbus_mmio_map(dev, 3, p->baseAddress +
+                            (3 << (min_bits(p->microcodeWords-1)+2)));
+
+    return 0;
+}
+
+static Property labx_audio_packetizer_properties[] = {
+    DEFINE_PROP_UINT32("baseAddress",        packetizer_t, baseAddress,
+                       0),
+    DEFINE_PROP_UINT32("clockDomains",       packetizer_t, clockDomains,
+                       1),
+    DEFINE_PROP_UINT32("cacheDataWords",     packetizer_t, cacheDataWords,
+                       1024),
+    DEFINE_PROP_UINT32("templateWords",      packetizer_t, templateWords,
+                       1024),
+    DEFINE_PROP_UINT32("microcodeWords",     packetizer_t, microcodeWords,
+                       1024),
+    DEFINE_PROP_UINT32("shaperFractionBits", packetizer_t, shaperFractionBits,
+                       16),
+    DEFINE_PROP_UINT32("maxStreamSlots",     packetizer_t, maxStreamSlots,
+                       32),
+    DEFINE_PROP_UINT32("dualOutput",         packetizer_t, dualOutput,
+                       1),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void labx_audio_packetizer_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+    k->init = labx_audio_packetizer_init;
+    dc->props = labx_audio_packetizer_properties;
+}
+
+static TypeInfo labx_audio_packetizer_info = {
+    .name          = "labx,audio-packetizer",
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(packetizer_t),
+    .class_init    = labx_audio_packetizer_class_init,
+};
+
+static void labx_audio_packetizer_register(void)
+{
+    type_register_static(&labx_audio_packetizer_info);
+}
+
+type_init(labx_audio_packetizer_register)
+
diff --git a/hw/labx_devices.h b/hw/labx_devices.h
new file mode 100644
index 0000000..317341e
--- /dev/null
+++ b/hw/labx_devices.h
@@ -0,0 +1,103 @@
+/*
+ * Lab X device types header.
+ *
+ * Copyright (c) 2010 Lab X Technologies, LLC
+ *
+ * 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.1 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/lgpl-2.1.html>
+ */
+
+#include <net.h>
+
+/* Audio packetizer  */
+static inline DeviceState *
+labx_audio_packetizer_create(target_phys_addr_t base, qemu_irq irq,
+                             int clockDomains, int cacheDataWords)
+{
+    DeviceState *dev;
+
+    dev = qdev_create(NULL, "labx,audio-packetizer");
+    qdev_prop_set_uint32(dev, "baseAddress", base);
+    qdev_prop_set_uint32(dev, "clockDomains", clockDomains);
+    qdev_prop_set_uint32(dev, "cacheDataWords", cacheDataWords);
+    qdev_init_nofail(dev);
+    sysbus_connect_irq(sysbus_from_qdev(dev), 0, irq);
+    return dev;
+}
+
+/* Audio depacketizer  */
+static inline DeviceState *
+labx_audio_depacketizer_create(target_phys_addr_t base, qemu_irq irq,
+                               int clockDomains, int cacheDataWords, int hasDMA)
+{
+    DeviceState *dev;
+
+    dev = qdev_create(NULL, "labx,audio-depacketizer");
+    qdev_prop_set_uint32(dev, "baseAddress", base);
+    qdev_prop_set_uint32(dev, "clockDomains", clockDomains);
+    qdev_prop_set_uint32(dev, "cacheDataWords", cacheDataWords);
+    qdev_prop_set_uint32(dev, "hasDMA", hasDMA);
+    qdev_init_nofail(dev);
+    sysbus_connect_irq(sysbus_from_qdev(dev), 0, irq);
+    return dev;
+}
+
+/* DMA */
+static inline DeviceState *
+labx_dma_create(target_phys_addr_t base, int microcodeWords)
+{
+    DeviceState *dev;
+
+    dev = qdev_create(NULL, "labx,dma");
+    qdev_prop_set_uint32(dev, "baseAddress", base);
+    qdev_prop_set_uint32(dev, "microcodeWords", microcodeWords);
+    qdev_init_nofail(dev);
+    return dev;
+}
+
+/* Ethernet */
+static inline DeviceState *
+labx_ethernet_create(NICInfo *nd, target_phys_addr_t base, qemu_irq hostIrq,
+                     qemu_irq fifoIrq, qemu_irq phyIrq)
+{
+    DeviceState *dev;
+    SysBusDevice *s;
+
+    qemu_check_nic_model(nd, "labx-ethernet");
+
+    dev = qdev_create(NULL, "labx,ethernet");
+    qdev_prop_set_uint32(dev, "baseAddress", base);
+    qdev_set_nic_properties(dev, nd);
+    qdev_init_nofail(dev);
+
+    s = sysbus_from_qdev(dev);
+    sysbus_connect_irq(s, 0, hostIrq);
+    sysbus_connect_irq(s, 1, fifoIrq);
+    sysbus_connect_irq(s, 2, phyIrq);
+
+    return dev;
+}
+
+/* PTP */
+static inline DeviceState *
+labx_ptp_create(target_phys_addr_t base)
+{
+    DeviceState *dev;
+
+    dev = qdev_create(NULL, "labx,ptp");
+    qdev_prop_set_uint32(dev, "baseAddress", base);
+    qdev_init_nofail(dev);
+    return dev;
+}
+
diff --git a/hw/labx_dma.c b/hw/labx_dma.c
new file mode 100644
index 0000000..9d8058c
--- /dev/null
+++ b/hw/labx_dma.c
@@ -0,0 +1,241 @@
+
+/*
+ * QEMU model of the LabX DMA Engine.
+ *
+ * Copyright (c) 2010 Lab X Technologies, LLC
+ *
+ * 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.1 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/lgpl-2.1.html>
+ */
+
+#include "sysbus.h"
+#include "sysemu.h"
+
+#define min_bits qemu_fls
+#define RAM_INDEX(addr, size) (((addr)>>2)&((1<<min_bits((size)-1))-1))
+
+
+struct labx_dma {
+    SysBusDevice busdev;
+
+    MemoryRegion  mmio_dma;
+    MemoryRegion  mmio_microcode;
+
+    /* Device Configuration */
+    uint32_t baseAddress;
+    uint32_t paramWords;
+    uint32_t microcodeWords;
+    uint32_t numIndexRegs;
+    uint32_t numChannels;
+    uint32_t numAlus;
+
+    /* Values set by drivers */
+
+    /* Microcode buffer */
+    uint32_t *microcodeRam;
+};
+
+/*
+ * DMA registers
+ */
+static uint64_t dma_regs_read(void *opaque, target_phys_addr_t addr,
+                              unsigned int size)
+{
+    struct labx_dma *p = opaque;
+
+    uint32_t retval = 0;
+
+    if ((addr>>2) & 0x80) {
+        /* vector */
+    } else {
+        switch ((addr>>2) & 0x7F) {
+        case 0x00: /* control */
+            break;
+
+        case 0x01: /* channel enable */
+            break;
+
+        case 0x02: /* channel start */
+            break;
+
+        case 0x03: /* channel irq enable */
+            break;
+
+        case 0x04: /* channel irq */
+            break;
+
+        case 0x05: /* sync */
+            break;
+
+        case 0x7E: /* capabilities */
+            retval = ((p->numIndexRegs               & 0x0F) << 12) |
+                     ((p->numChannels                & 0x03) << 10) |
+                     ((p->numAlus                    & 0x03) << 8) |
+                     ((min_bits(p->paramWords-1)     & 0x0F) << 4) |
+                     ((min_bits(p->microcodeWords-1) & 0x0F));
+            break;
+
+        case 0x7F: /* revision */
+            retval = 0x00000011;
+            break;
+
+        default:
+            printf("labx-dma: Read of unknown register %08X\n", addr);
+            break;
+        }
+    }
+
+    return retval;
+}
+
+static void dma_regs_write(void *opaque, target_phys_addr_t addr,
+                           uint64_t val64, unsigned int size)
+{
+    /*struct labx_dma *p = opaque; */
+    uint32_t value = val64;
+
+    if ((addr>>2) & 0x80) {
+        /* vector */
+    } else {
+        switch ((addr>>2) & 0x7F) {
+        case 0x00: /* control */
+            break;
+
+        case 0x01: /* channel enable */
+            break;
+
+        case 0x02: /* channel start */
+            break;
+
+        case 0x03: /* channel irq enable */
+            break;
+
+        case 0x04: /* channel irq */
+            break;
+
+        case 0x05: /* sync */
+            break;
+
+        case 0x7E: /* capabilities */
+            break;
+
+        case 0x7F: /* revision */
+            break;
+
+        default:
+            printf("labx-dma: Write of unknown register "
+                   "%08X = %08X\n", addr, value);
+            break;
+        }
+    }
+}
+
+static const MemoryRegionOps dma_regs_ops = {
+    .read = dma_regs_read,
+    .write = dma_regs_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+
+/*
+ * Microcode RAM
+ */
+static uint64_t microcode_ram_read(void *opaque, target_phys_addr_t addr,
+                                   unsigned int size)
+{
+    struct labx_dma *p = opaque;
+
+    return p->microcodeRam[RAM_INDEX(addr, p->microcodeWords)];
+}
+
+static void microcode_ram_write(void *opaque, target_phys_addr_t addr,
+                                uint64_t val64, unsigned int size)
+{
+    struct labx_dma *p = opaque;
+    uint32_t value = val64;
+
+    p->microcodeRam[RAM_INDEX(addr, p->microcodeWords)] = value;
+}
+
+static const MemoryRegionOps microcode_ram_ops = {
+    .read = microcode_ram_read,
+    .write = microcode_ram_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+
+static int labx_dma_init(SysBusDevice *dev)
+{
+    struct labx_dma *p = FROM_SYSBUS(typeof(*p), dev);
+
+    /* Initialize defaults */
+    p->microcodeRam = g_malloc0(p->microcodeWords*4);
+
+    /* Set up memory regions */
+    memory_region_init_io(&p->mmio_dma,       &dma_regs_ops,      p,
+                          "labx,dma-regs",      0x100 * 4);
+    memory_region_init_io(&p->mmio_microcode, &microcode_ram_ops, p,
+                          "labx,dma-microcode", 4 * p->microcodeWords);
+
+    sysbus_init_mmio(dev, &p->mmio_dma);
+    sysbus_init_mmio(dev, &p->mmio_microcode);
+
+    sysbus_mmio_map(dev, 0, p->baseAddress);
+    sysbus_mmio_map(dev, 1, p->baseAddress +
+                            (1 << (min_bits(p->microcodeWords-1)+2)));
+
+    return 0;
+}
+
+static Property labx_dma_properties[] = {
+    DEFINE_PROP_UINT32("baseAddress",    struct labx_dma, baseAddress,    0),
+    DEFINE_PROP_UINT32("paramWords",     struct labx_dma, paramWords,     1024),
+    DEFINE_PROP_UINT32("microcodeWords", struct labx_dma, microcodeWords, 1024),
+    DEFINE_PROP_UINT32("numIndexRegs",   struct labx_dma, numIndexRegs,   4),
+    DEFINE_PROP_UINT32("numChannels",    struct labx_dma, numChannels,    1),
+    DEFINE_PROP_UINT32("numAlus",        struct labx_dma, numAlus,        1),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void labx_dma_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+    k->init = labx_dma_init;
+    dc->props = labx_dma_properties;
+}
+
+static TypeInfo labx_dma_info = {
+    .name          = "labx,dma",
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(struct labx_dma),
+    .class_init    = labx_dma_class_init,
+};
+
+static void labx_dma_register(void)
+{
+    type_register_static(&labx_dma_info);
+}
+
+type_init(labx_dma_register)
+
diff --git a/hw/labx_ethernet.c b/hw/labx_ethernet.c
new file mode 100644
index 0000000..c47c91b
--- /dev/null
+++ b/hw/labx_ethernet.c
@@ -0,0 +1,615 @@
+
+/*
+ * QEMU model of the LabX legacy ethernet core.
+ *
+ * Copyright (c) 2010 Lab X Technologies, LLC
+ *
+ * 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.1 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/lgpl-2.1.html>
+ */
+
+#include "sysbus.h"
+#include "sysemu.h"
+#include "net.h"
+
+#define FIFO_RAM_BYTES 2048
+#define LENGTH_FIFO_WORDS 16
+
+struct labx_ethernet {
+    SysBusDevice busdev;
+    qemu_irq hostIrq;
+    qemu_irq fifoIrq;
+    qemu_irq phyIrq;
+    NICState *nic;
+    NICConf conf;
+
+    MemoryRegion  mmio_ethernet;
+    MemoryRegion  mmio_mac;
+    MemoryRegion  mmio_fifo;
+
+    /* Device Configuration */
+    uint32_t baseAddress;
+
+    /* Values set by drivers */
+    uint32_t hostRegs[0x10];
+    uint32_t fifoRegs[0x10];
+
+    /* Tx buffers */
+    uint32_t *txBuffer;
+    uint32_t  txPushIndex;
+    uint32_t  txPopIndex;
+
+    uint32_t *txLengthBuffer;
+    uint32_t  txLengthPushIndex;
+    uint32_t  txLengthPopIndex;
+
+    /* Rx buffers */
+    uint32_t *rxBuffer;
+    uint32_t  rxPushIndex;
+    uint32_t  rxPopIndex;
+
+    uint32_t *rxLengthBuffer;
+    uint32_t  rxLengthPushIndex;
+    uint32_t  rxLengthPopIndex;
+};
+
+/*
+ * Legacy ethernet registers
+ */
+static void update_host_irq(struct labx_ethernet *p)
+{
+    if ((p->hostRegs[0x03] & p->hostRegs[2]) != 0) {
+        qemu_irq_raise(p->hostIrq);
+    } else {
+        qemu_irq_lower(p->hostIrq);
+    }
+}
+
+static void mdio_xfer(struct labx_ethernet *p, int readWrite,
+                      int phyAddr, int regAddr)
+{
+    printf("MDIO %s: addr=%d, reg=%d\n", (readWrite) ? "READ" : "WRITE",
+           phyAddr, regAddr);
+    if (readWrite) {
+        /* TODO: PHY info */
+        p->hostRegs[0x01] = 0x0000FFFF;
+    }
+    p->hostRegs[0x03] |= 1;
+    update_host_irq(p);
+}
+
+static uint64_t ethernet_regs_read(void *opaque, target_phys_addr_t addr,
+                                   unsigned int size)
+{
+    struct labx_ethernet *p = opaque;
+
+    uint32_t retval = 0;
+
+    switch ((addr>>2) & 0x0F) {
+    case 0x00: /* mdio control */
+    case 0x01: /* mdio data */
+    case 0x02: /* irq mask */
+    case 0x03: /* irq flags */
+    case 0x04: /* vlan mask */
+    case 0x05: /* filter select */
+        retval = p->hostRegs[(addr>>2) & 0x0F];
+        break;
+
+    case 0x06: /* filter control */
+        retval = 0x20000000;
+        break;
+
+    case 0x0F: /* revision */
+        retval = 0x00000C13;
+        break;
+
+    case 0x07: /* filter load */
+        retval = p->hostRegs[(addr>>2) & 0x0F];
+        break;
+
+    case 0x08: /* bad packet */
+        retval = 0;
+        break;
+
+    default:
+        printf("labx-ethernet: Read of unknown register %08X\n", addr);
+        break;
+    }
+
+    return retval;
+}
+
+static void ethernet_regs_write(void *opaque, target_phys_addr_t addr,
+                                uint64_t val64, unsigned int size)
+{
+    struct labx_ethernet *p = opaque;
+    uint32_t value = val64;
+
+    switch ((addr>>2) & 0x0F) {
+    case 0x00: /* mdio control */
+        p->hostRegs[0x00] = (value & 0x000007FF);
+        mdio_xfer(p, (value >> 10) & 1, (value >> 5) & 0x1F, value & 0x1F);
+        break;
+
+    case 0x01: /* mdio data */
+        p->hostRegs[0x01] = (value & 0x0000FFFF);
+        break;
+
+    case 0x02: /* irq mask */
+        p->hostRegs[0x02] = (value & 0x00000003);
+        update_host_irq(p);
+        break;
+
+    case 0x03: /* irq flags */
+        p->hostRegs[0x03] &= ~(value & 0x00000003);
+        update_host_irq(p);
+        break;
+
+    case 0x04: /* vlan mask */
+        break;
+
+    case 0x05: /* filter select */
+        break;
+
+    case 0x06: /* filter control */
+        break;
+
+    case 0x07: /* filter load */
+        break;
+
+    case 0x08: /* bad packet */
+        break;
+
+    case 0x0F: /* revision */
+        break;
+
+    default:
+        printf("labx-ethernet: Write of unknown register %08X = %08X\n",
+               addr, value);
+        break;
+    }
+}
+
+static const MemoryRegionOps ethernet_regs_ops = {
+    .read = ethernet_regs_read,
+    .write = ethernet_regs_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+
+/*
+ * MAC registers
+ */
+static uint64_t mac_regs_read(void *opaque, target_phys_addr_t addr,
+                              unsigned int size)
+{
+    /*struct labx_ethernet *p = opaque; */
+
+    uint32_t retval = 0;
+
+    switch ((addr>>2) & 0x0F) {
+    case 0x01: /* host rx config */
+        break;
+
+    case 0x02: /* host tx config */
+        break;
+
+    case 0x04: /* host speed config */
+        break;
+
+    case 0x05: /* host mdio config */
+        break;
+
+    default:
+        printf("labx-ethernet: Read of unknown mac register %08X\n", addr);
+        break;
+    }
+
+    return retval;
+}
+
+static void mac_regs_write(void *opaque, target_phys_addr_t addr,
+                           uint64_t val64, unsigned int size)
+{
+    /*struct labx_ethernet *p = opaque; */
+    uint32_t value = val64;
+
+    switch ((addr>>2) & 0x0F) {
+    case 0x01: /* host rx config */
+        break;
+
+    case 0x02: /* host tx config */
+        break;
+
+    case 0x04: /* host speed config */
+        break;
+
+    case 0x05: /* host mdio config */
+        break;
+
+    default:
+        printf("labx-ethernet: Write of unknown mac register %08X = %08X\n",
+               addr, value);
+        break;
+    }
+}
+
+static const MemoryRegionOps mac_regs_ops = {
+    .read = mac_regs_read,
+    .write = mac_regs_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+
+/*
+ * FIFO registers
+ */
+
+#define FIFO_INT_STATUS_ADDRESS   0x0
+#define FIFO_INT_ENABLE_ADDRESS   0x1
+#  define FIFO_INT_RPURE 0x80000000
+#  define FIFO_INT_RPORE 0x40000000
+#  define FIFO_INT_RPUE  0x20000000
+#  define FIFO_INT_TPOE  0x10000000
+#  define FIFO_INT_TC    0x08000000
+#  define FIFO_INT_RC    0x04000000
+#  define FIFO_INT_MASK  0xFC000000
+#define FIFO_TX_RESET_ADDRESS     0x2
+#  define FIFO_RESET_MAGIC 0xA5
+#define FIFO_TX_VACANCY_ADDRESS   0x3
+#define FIFO_TX_DATA_ADDRESS      0x4
+#define FIFO_TX_LENGTH_ADDRESS    0x5
+#define FIFO_RX_RESET_ADDRESS     0x6
+#define FIFO_RX_OCCUPANCY_ADDRESS 0x7
+#define FIFO_RX_DATA_ADDRESS      0x8
+#define FIFO_RX_LENGTH_ADDRESS    0x9
+
+static void update_fifo_irq(struct labx_ethernet *p)
+{
+    if ((p->fifoRegs[FIFO_INT_STATUS_ADDRESS] &
+         p->fifoRegs[FIFO_INT_ENABLE_ADDRESS]) != 0) {
+        qemu_irq_raise(p->fifoIrq);
+    } else {
+        qemu_irq_lower(p->fifoIrq);
+    }
+}
+
+static void send_packet(struct labx_ethernet *p)
+{
+    while (p->txLengthPopIndex != p->txLengthPushIndex) {
+        int i;
+        uint32_t packetBuf[512];
+
+        int length = p->txLengthBuffer[p->txLengthPopIndex];
+        p->txLengthPopIndex = (p->txLengthPopIndex + 1) % LENGTH_FIFO_WORDS;
+
+        for (i = 0; i < ((length+3)/4); i++) {
+            packetBuf[i] = be32_to_cpu(p->txBuffer[p->txPopIndex]);
+            p->txPopIndex = (p->txPopIndex + 1) % (FIFO_RAM_BYTES/4);
+        }
+
+        qemu_send_packet(&p->nic->nc, (void *)packetBuf, length);
+    }
+
+    p->fifoRegs[FIFO_INT_STATUS_ADDRESS] |= FIFO_INT_TC;
+    update_fifo_irq(p);
+}
+
+static uint64_t fifo_regs_read(void *opaque, target_phys_addr_t addr,
+                               unsigned int size)
+{
+    struct labx_ethernet *p = opaque;
+
+    uint32_t retval = 0;
+
+    switch ((addr>>2) & 0x0F) {
+    case FIFO_INT_STATUS_ADDRESS:
+    case FIFO_INT_ENABLE_ADDRESS:
+    case FIFO_TX_RESET_ADDRESS:
+        retval = p->fifoRegs[(addr>>2) & 0x0F];
+        break;
+
+    case FIFO_TX_VACANCY_ADDRESS:
+        retval = (p->txPopIndex - p->txPushIndex) - 1;
+        if ((int32_t)retval < 0) {
+            retval += (FIFO_RAM_BYTES/4);
+        }
+
+        if (((p->txLengthPushIndex + 1) % LENGTH_FIFO_WORDS) ==
+            p->txLengthPopIndex) {
+            /* Full length fifo */
+            retval = 0;
+        }
+        break;
+
+    case FIFO_TX_DATA_ADDRESS:
+    case FIFO_TX_LENGTH_ADDRESS:
+    case FIFO_RX_RESET_ADDRESS:
+        retval = p->fifoRegs[(addr>>2) & 0x0F];
+        break;
+
+    case FIFO_RX_OCCUPANCY_ADDRESS:
+        retval = p->rxPushIndex - p->rxPopIndex;
+        if ((int32_t)retval < 0) {
+            retval += (FIFO_RAM_BYTES/4);
+        }
+        break;
+
+    case FIFO_RX_DATA_ADDRESS:
+        retval = p->rxBuffer[p->rxPopIndex];
+        if (p->rxPopIndex != p->rxPushIndex) {
+            p->rxPopIndex = (p->rxPopIndex+1) % (FIFO_RAM_BYTES/4);
+        } else {
+            p->fifoRegs[FIFO_INT_STATUS_ADDRESS] |= FIFO_INT_RPURE;
+            update_fifo_irq(p);
+        }
+        break;
+
+    case FIFO_RX_LENGTH_ADDRESS:
+        retval = p->rxLengthBuffer[p->rxLengthPopIndex];
+        if (p->rxLengthPopIndex != p->rxLengthPushIndex) {
+            p->rxLengthPopIndex = (p->rxLengthPopIndex+1) % LENGTH_FIFO_WORDS;
+        } else {
+            p->fifoRegs[FIFO_INT_STATUS_ADDRESS] |= FIFO_INT_RPURE;
+            update_fifo_irq(p);
+        }
+        break;
+
+    default:
+        printf("labx-ethernet: Read of unknown fifo register %08X\n", addr);
+        break;
+    }
+
+    /* printf("FIFO REG READ %08X (%d) = %08X\n",
+               addr, (addr>>2) & 0x0F, retval); */
+
+    return retval;
+}
+
+static void fifo_regs_write(void *opaque, target_phys_addr_t addr,
+                            uint64_t val64, unsigned int size)
+{
+    struct labx_ethernet *p = opaque;
+    uint32_t value = val64;
+
+    /* printf("FIFO REG WRITE %08X (%d) = %08X\n",
+              addr, (addr>>2) & 0x0F, value); */
+
+    switch ((addr>>2) & 0x0F) {
+    case FIFO_INT_STATUS_ADDRESS:
+        p->fifoRegs[FIFO_INT_STATUS_ADDRESS] &= ~(value & FIFO_INT_MASK);
+        update_fifo_irq(p);
+        break;
+
+    case FIFO_INT_ENABLE_ADDRESS:
+        p->fifoRegs[FIFO_INT_ENABLE_ADDRESS] = (value & FIFO_INT_MASK);
+        update_fifo_irq(p);
+        break;
+
+    case FIFO_TX_RESET_ADDRESS:
+        if (value == FIFO_RESET_MAGIC) {
+            p->txPushIndex = 0;
+            p->txPopIndex = 0;
+            p->txLengthPushIndex = 0;
+            p->txLengthPopIndex = 0;
+        }
+        break;
+
+    case FIFO_TX_VACANCY_ADDRESS:
+        break;
+
+    case FIFO_TX_DATA_ADDRESS:
+        if ((((p->txLengthPushIndex + 1) % LENGTH_FIFO_WORDS) ==
+             p->txLengthPopIndex) ||
+            (((p->txPushIndex + 1) % (FIFO_RAM_BYTES/4)) == p->txPopIndex)) {
+            /* Full length fifo or data fifo */
+            p->fifoRegs[FIFO_INT_STATUS_ADDRESS] |= FIFO_INT_TPOE;
+            update_fifo_irq(p);
+        } else {
+            /* Push back the data */
+            p->txBuffer[p->txPushIndex] = value;
+            p->txPushIndex = (p->txPushIndex + 1) % (FIFO_RAM_BYTES/4);
+        }
+        break;
+
+    case FIFO_TX_LENGTH_ADDRESS:
+        if (((p->txLengthPushIndex + 1) % LENGTH_FIFO_WORDS) ==
+            p->txLengthPopIndex) {
+            /* Full length fifo */
+            p->fifoRegs[FIFO_INT_STATUS_ADDRESS] |= FIFO_INT_TPOE;
+            update_fifo_irq(p);
+        } else {
+            /* Push back the length */
+            p->txLengthBuffer[p->txLengthPushIndex] = value;
+            p->txLengthPushIndex = (p->txLengthPushIndex + 1) %
+                                   LENGTH_FIFO_WORDS;
+            send_packet(p);
+        }
+        break;
+
+    case FIFO_RX_RESET_ADDRESS:
+        if (value == FIFO_RESET_MAGIC) {
+            p->rxPushIndex = 0;
+            p->rxPopIndex = 0;
+            p->rxLengthPushIndex = 0;
+            p->rxLengthPopIndex = 0;
+        }
+        break;
+
+    case FIFO_RX_OCCUPANCY_ADDRESS:
+        break;
+
+    case FIFO_RX_DATA_ADDRESS:
+        break;
+
+    case FIFO_RX_LENGTH_ADDRESS:
+        break;
+
+    default:
+        printf("labx-ethernet: Write of unknown fifo register %08X = %08X\n",
+               addr, value);
+        break;
+    }
+}
+
+static const MemoryRegionOps fifo_regs_ops = {
+    .read = fifo_regs_read,
+    .write = fifo_regs_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+
+static int eth_can_rx(NetClientState *nc)
+{
+    /*struct labx_ethernet *s = DO_UPCAST(NICState, nc, nc)->opaque; */
+
+    return 1;
+}
+
+static ssize_t eth_rx(NetClientState *nc, const uint8_t *buf, size_t size)
+{
+    struct labx_ethernet *p = DO_UPCAST(NICState, nc, nc)->opaque;
+    int i;
+    const uint32_t *wbuf = (const uint32_t *)buf;
+    int rxPushIndexStart = p->rxPushIndex;
+
+    for (i = 0; i < ((size+3)/4); i++) {
+        p->rxBuffer[p->rxPushIndex] = cpu_to_be32(wbuf[i]);
+        p->rxPushIndex = (p->rxPushIndex + 1) % (FIFO_RAM_BYTES/4);
+        if (p->rxPushIndex == p->rxPopIndex) {
+            /* Packet didn't fit */
+            p->rxPushIndex = rxPushIndexStart;
+            return -1;
+        }
+    }
+
+    if ((p->rxLengthPushIndex + 1) % LENGTH_FIFO_WORDS == p->rxLengthPopIndex) {
+        /* Length didn't fit */
+        p->rxPushIndex = rxPushIndexStart;
+        return -1;
+    }
+
+    p->rxLengthBuffer[p->rxLengthPushIndex] = size;
+    p->rxLengthPushIndex = (p->rxLengthPushIndex + 1) % LENGTH_FIFO_WORDS;
+
+    p->fifoRegs[FIFO_INT_STATUS_ADDRESS] |= FIFO_INT_RC;
+    update_fifo_irq(p);
+
+    return size;
+}
+
+static void eth_cleanup(NetClientState *nc)
+{
+    struct labx_ethernet *s = DO_UPCAST(NICState, nc, nc)->opaque;
+
+    s->nic = NULL;
+}
+
+static NetClientInfo net_labx_ethernet_info = {
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .size = sizeof(NICState),
+    .can_receive = eth_can_rx,
+    .receive = eth_rx,
+    .cleanup = eth_cleanup,
+};
+
+static int labx_ethernet_init(SysBusDevice *dev)
+{
+    struct labx_ethernet *p = FROM_SYSBUS(typeof(*p), dev);
+
+    /* Initialize defaults */
+    p->txBuffer = g_malloc0(FIFO_RAM_BYTES);
+    p->txLengthBuffer = g_malloc0(LENGTH_FIFO_WORDS*4);
+    p->rxBuffer = g_malloc0(FIFO_RAM_BYTES);
+    p->rxLengthBuffer = g_malloc0(LENGTH_FIFO_WORDS*4);
+
+    p->txPushIndex = 0;
+    p->txPopIndex = 0;
+    p->txLengthPushIndex = 0;
+    p->txLengthPopIndex = 0;
+    p->rxPushIndex = 0;
+    p->rxPopIndex = 0;
+    p->rxLengthPushIndex = 0;
+    p->rxLengthPopIndex = 0;
+
+    /* Set up memory regions */
+    memory_region_init_io(&p->mmio_ethernet, &ethernet_regs_ops, p,
+                          "labx,ethernet-regs",      0x10 * 4);
+    memory_region_init_io(&p->mmio_mac,      &mac_regs_ops,      p,
+                          "labx,ethernet-mac-regs",  0x10 * 4);
+    memory_region_init_io(&p->mmio_fifo,     &fifo_regs_ops,     p,
+                          "labx,ethernet-fifo-regs", 0x10 * 4);
+
+    sysbus_init_mmio(dev, &p->mmio_ethernet);
+    sysbus_init_mmio(dev, &p->mmio_mac);
+    sysbus_init_mmio(dev, &p->mmio_fifo);
+
+    sysbus_mmio_map(dev, 0, p->baseAddress);
+    sysbus_mmio_map(dev, 1, p->baseAddress + (1 << (10+2)));
+    sysbus_mmio_map(dev, 2, p->baseAddress + (2 << (10+2)));
+
+    /* Initialize the irqs */
+    sysbus_init_irq(dev, &p->hostIrq);
+    sysbus_init_irq(dev, &p->fifoIrq);
+    sysbus_init_irq(dev, &p->phyIrq);
+
+    /* Set up the NIC */
+    qemu_macaddr_default_if_unset(&p->conf.macaddr);
+    p->nic = qemu_new_nic(&net_labx_ethernet_info, &p->conf,
+                          object_get_typename(OBJECT(p)), dev->qdev.id, p);
+    qemu_format_nic_info_str(&p->nic->nc, p->conf.macaddr.a);
+    return 0;
+}
+
+static Property labx_ethernet_properties[] = {
+    DEFINE_PROP_UINT32("baseAddress", struct labx_ethernet, baseAddress, 0),
+    DEFINE_NIC_PROPERTIES(struct labx_ethernet, conf),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void labx_ethernet_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+    k->init = labx_ethernet_init;
+    dc->props = labx_ethernet_properties;
+}
+
+static TypeInfo labx_ethernet_info = {
+    .name          = "labx,ethernet",
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(struct labx_ethernet),
+    .class_init    = labx_ethernet_class_init,
+};
+
+static void labx_ethernet_register(void)
+{
+    type_register_static(&labx_ethernet_info);
+}
+
+type_init(labx_ethernet_register)
+
diff --git a/hw/labx_ptp.c b/hw/labx_ptp.c
new file mode 100644
index 0000000..68d4b54
--- /dev/null
+++ b/hw/labx_ptp.c
@@ -0,0 +1,291 @@
+
+/*
+ * QEMU model of the LabX PTP.
+ *
+ * Copyright (c) 2010 Lab X Technologies, LLC
+ *
+ * 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.1 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/lgpl-2.1.html>
+ */
+
+#include "sysbus.h"
+#include "sysemu.h"
+
+#define min_bits qemu_fls
+#define RAM_INDEX(addr, size) (((addr)>>2)&((1<<min_bits((size)-1))-1))
+
+#define PTP_MAX_PACKETS      8
+#define PTP_MAX_PACKET_BYTES 256
+#define PTP_RAM_BYTES        (PTP_MAX_PACKETS*PTP_MAX_PACKET_BYTES)
+#define PTP_HOST_RAM_WORDS   (PTP_RAM_BYTES/4)
+
+struct labx_ptp {
+    SysBusDevice busdev;
+
+    MemoryRegion  mmio_ptp;
+    MemoryRegion  mmio_tx;
+    MemoryRegion  mmio_rx;
+
+    /* Device Configuration */
+    uint32_t baseAddress;
+
+    /* Values set by drivers */
+
+    /* Tx buffers */
+    uint32_t *txRam;
+
+    /* Rx buffers */
+    uint32_t *rxRam;
+};
+
+/*
+ * PTP registers
+ */
+static uint64_t ptp_regs_read(void *opaque, target_phys_addr_t addr,
+                              unsigned int size)
+{
+    /*struct labx_ptp *p = opaque; */
+
+    uint32_t retval = 0;
+
+    switch ((addr>>2) & 0x0F) {
+    case 0x00: /* rx */
+        break;
+
+    case 0x01: /* tx */
+        break;
+
+    case 0x02: /* irq mask */
+        break;
+
+    case 0x03: /* irq flags */
+        break;
+
+    case 0x04: /* rtc increment */
+        break;
+
+    case 0x05: /* seconds high */
+        break;
+
+    case 0x06: /* seconds low */
+        break;
+
+    case 0x07: /* nanoseconds */
+        break;
+
+    case 0x08: /* timer */
+        break;
+
+    case 0x09: /* local seconds high */
+        break;
+
+    case 0x0A: /* local seconds low */
+        break;
+
+    case 0x0B: /* local nanoseconds */
+        break;
+
+    case 0x0F: /* revision */
+        retval = 0x00000111; /* Report 1 port, revision 1.1 */
+        break;
+
+    default:
+        printf("labx-ptp: Read of unknown register %08X\n", addr);
+        break;
+    }
+
+    return retval;
+}
+
+static void ptp_regs_write(void *opaque, target_phys_addr_t addr,
+                           uint64_t val64, unsigned int size)
+{
+    /*struct labx_ptp *p = opaque; */
+    uint32_t value = val64;
+
+    switch ((addr>>2) & 0x0F) {
+    case 0x00: /* rx */
+        break;
+
+    case 0x01: /* tx */
+        break;
+
+    case 0x02: /* irq mask */
+        break;
+
+    case 0x03: /* irq flags */
+        break;
+
+    case 0x04: /* rtc increment */
+        break;
+
+    case 0x05: /* seconds high */
+        break;
+
+    case 0x06: /* seconds low */
+        break;
+
+    case 0x07: /* nanoseconds */
+        break;
+
+    case 0x08: /* timer */
+        break;
+
+    case 0x09: /* local seconds high */
+        break;
+
+    case 0x0A: /* local seconds low */
+        break;
+
+    case 0x0B: /* local nanoseconds */
+        break;
+
+    case 0x0F: /* revision */
+        break;
+
+    default:
+        printf("labx-ptp: Write of unknown register %08X = %08X\n",
+               addr, value);
+        break;
+    }
+}
+
+static const MemoryRegionOps ptp_regs_ops = {
+    .read = ptp_regs_read,
+    .write = ptp_regs_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+
+/*
+ * Tx Ram
+ */
+static uint64_t tx_ram_read(void *opaque, target_phys_addr_t addr,
+                            unsigned int size)
+{
+    struct labx_ptp *p = opaque;
+
+    return p->txRam[RAM_INDEX(addr, PTP_HOST_RAM_WORDS)];
+}
+
+static void tx_ram_write(void *opaque, target_phys_addr_t addr,
+                         uint64_t val64, unsigned int size)
+{
+    struct labx_ptp *p = opaque;
+    uint32_t value = val64;
+
+    p->txRam[RAM_INDEX(addr, PTP_HOST_RAM_WORDS)] = value;
+}
+
+static const MemoryRegionOps tx_ram_ops = {
+    .read = tx_ram_read,
+    .write = tx_ram_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+
+/*
+ * Rx Ram
+ */
+static uint64_t rx_ram_read(void *opaque, target_phys_addr_t addr,
+                            unsigned int size)
+{
+    struct labx_ptp *p = opaque;
+
+    return p->rxRam[RAM_INDEX(addr, PTP_HOST_RAM_WORDS)];
+}
+
+static void rx_ram_write(void *opaque, target_phys_addr_t addr,
+                         uint64_t val64, unsigned int size)
+{
+    struct labx_ptp *p = opaque;
+    uint32_t value = val64;
+
+    p->rxRam[RAM_INDEX(addr, PTP_HOST_RAM_WORDS)] = value;
+}
+
+static const MemoryRegionOps rx_ram_ops = {
+    .read = rx_ram_read,
+    .write = rx_ram_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+
+static int labx_ptp_init(SysBusDevice *dev)
+{
+    struct labx_ptp *p = FROM_SYSBUS(typeof(*p), dev);
+
+    /* Initialize defaults */
+    p->txRam = g_malloc0(PTP_RAM_BYTES);
+    p->rxRam = g_malloc0(PTP_RAM_BYTES);
+
+    /* Set up memory regions */
+    memory_region_init_io(&p->mmio_ptp, &ptp_regs_ops, p, "labx,ptp-regs",
+                          0x100 * 4);
+    memory_region_init_io(&p->mmio_tx,  &tx_ram_ops,   p, "labx,ptp-tx",
+                          PTP_RAM_BYTES);
+    memory_region_init_io(&p->mmio_rx,  &rx_ram_ops,   p, "labx,ptp-rx",
+                          PTP_RAM_BYTES);
+
+    sysbus_init_mmio(dev, &p->mmio_ptp);
+    sysbus_init_mmio(dev, &p->mmio_tx);
+    sysbus_init_mmio(dev, &p->mmio_rx);
+
+    sysbus_mmio_map(dev, 0, p->baseAddress);
+    sysbus_mmio_map(dev, 1, p->baseAddress + (1 << min_bits(PTP_RAM_BYTES-1)));
+    sysbus_mmio_map(dev, 2, p->baseAddress + (2 << min_bits(PTP_RAM_BYTES-1)));
+
+    return 0;
+}
+
+static Property labx_ptp_properties[] = {
+    DEFINE_PROP_UINT32("baseAddress", struct labx_ptp, baseAddress, 0),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void labx_ptp_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+    k->init = labx_ptp_init;
+    dc->props = labx_ptp_properties;
+}
+
+static TypeInfo labx_ptp_info = {
+    .name          = "labx,ptp",
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(struct labx_ptp),
+    .class_init    = labx_ptp_class_init,
+};
+
+static void labx_ptp_register(void)
+{
+    type_register_static(&labx_ptp_info);
+}
+
+type_init(labx_ptp_register)
+
