aboutsummaryrefslogtreecommitdiff
path: root/hw
diff options
context:
space:
mode:
authorAnthony Liguori <aliguori@amazon.com>2013-12-10 16:13:32 -0800
committerAnthony Liguori <aliguori@amazon.com>2013-12-10 16:13:32 -0800
commit1ead3ed55584a62a12d840a71d3aab71f12ec42e (patch)
tree33bd2288c1d84b1342f1022afab001415a5f5037 /hw
parentb5527dad7dba7d85520aaec787fb6fb14be1c366 (diff)
parent74f1c6ddec8dc7566d9b75574bb006214cc7d3b4 (diff)
Merge remote-tracking branch 'pmaydell/tags/pull-target-arm-20131210' into staging
target-arm queue: * support REFCNT register on integrator/cp board * implement the A9MP's global timer * add the 'virt' platform * support '-cpu host' on KVM/ARM * Cadence GEM ethernet device bugfixes * Implement 32-bit ARMv8 VSEL, VMAXNM, VMINNM * fix TTBCR write masking * update 32 bit decoder to use new qemu_ld/st TCG opcodes # gpg: Signature made Tue 10 Dec 2013 06:22:01 AM PST using RSA key ID 14360CDE # gpg: Can't check signature: public key not found # By Peter Crosthwaite (16) and others # Via Peter Maydell * pmaydell/tags/pull-target-arm-20131210: (37 commits) target-arm: fix TTBCR write masking target-arm: Use new qemu_ld/st opcodes target-arm: Implement ARMv8 SIMD VMAXNM and VMINNM instructions. target-arm: Implement ARMv8 FP VMAXNM and VMINNM instructions. softfloat: Add minNum() and maxNum() functions to softfloat. softfloat: Remove unused argument from MINMAX macro. target-arm: Implement ARMv8 VSEL instruction. target-arm: Move call to disas_vfp_insn out of disas_coproc_insn. net/cadence_gem: Don't rx packets when no rx buffer available net/cadence_gem: Improve can_receive debug printfery net/cadence_gem: Fix register w1c logic net/cadence_gem: Fix small packet FCS stripping net/cadence_gem: Fix rx multi-fragment packets net/cadence_gem: Add missing VMSTATE_END_OF_LIST net/cadence_gem: Implement SAR (de)activation net/cadence_gem: Implement SAR match bit in rx desc net/cadence_gem: Implement RX descriptor match mode flags net/cadence_gem: Prefetch rx descriptors ASAP net/cadence_gem: simplify rx buf descriptor walking net/cadence_gem: Don't assert against 0 buffer address ... Message-id: 1386686613-2390-1-git-send-email-peter.maydell@linaro.org Signed-off-by: Anthony Liguori <aliguori@amazon.com>
Diffstat (limited to 'hw')
-rw-r--r--hw/arm/Makefile.objs2
-rw-r--r--hw/arm/boot.c32
-rw-r--r--hw/arm/integratorcp.c13
-rw-r--r--hw/arm/virt.c452
-rw-r--r--hw/cpu/a9mpcore.c44
-rw-r--r--hw/net/cadence_gem.c278
-rw-r--r--hw/timer/Makefile.objs1
-rw-r--r--hw/timer/a9gtimer.c369
8 files changed, 1060 insertions, 131 deletions
diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
index 3671b42738..78b56149b6 100644
--- a/hw/arm/Makefile.objs
+++ b/hw/arm/Makefile.objs
@@ -1,7 +1,7 @@
obj-y += boot.o collie.o exynos4_boards.o gumstix.o highbank.o
obj-y += integratorcp.o kzm.o mainstone.o musicpal.o nseries.o
obj-y += omap_sx1.o palm.o realview.o spitz.o stellaris.o
-obj-y += tosa.o versatilepb.o vexpress.o xilinx_zynq.o z2.o
+obj-y += tosa.o versatilepb.o vexpress.o virt.o xilinx_zynq.o z2.o
obj-y += armv7m.o exynos4210.o pxa2xx.o pxa2xx_gpio.o pxa2xx_pic.o
obj-y += omap1.o omap2.o strongarm.o
diff --git a/hw/arm/boot.c b/hw/arm/boot.c
index 583ec7992e..55d552f3a8 100644
--- a/hw/arm/boot.c
+++ b/hw/arm/boot.c
@@ -228,23 +228,31 @@ static void set_kernel_args_old(const struct arm_boot_info *info)
static int load_dtb(hwaddr addr, const struct arm_boot_info *binfo)
{
void *fdt = NULL;
- char *filename;
int size, rc;
uint32_t acells, scells;
- filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, binfo->dtb_filename);
- if (!filename) {
- fprintf(stderr, "Couldn't open dtb file %s\n", binfo->dtb_filename);
- goto fail;
- }
+ if (binfo->dtb_filename) {
+ char *filename;
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, binfo->dtb_filename);
+ if (!filename) {
+ fprintf(stderr, "Couldn't open dtb file %s\n", binfo->dtb_filename);
+ goto fail;
+ }
- fdt = load_device_tree(filename, &size);
- if (!fdt) {
- fprintf(stderr, "Couldn't open dtb file %s\n", filename);
+ fdt = load_device_tree(filename, &size);
+ if (!fdt) {
+ fprintf(stderr, "Couldn't open dtb file %s\n", filename);
+ g_free(filename);
+ goto fail;
+ }
g_free(filename);
- goto fail;
+ } else if (binfo->get_dtb) {
+ fdt = binfo->get_dtb(binfo, &size);
+ if (!fdt) {
+ fprintf(stderr, "Board was unable to create a dtb blob\n");
+ goto fail;
+ }
}
- g_free(filename);
acells = qemu_devtree_getprop_cell(fdt, "/", "#address-cells");
scells = qemu_devtree_getprop_cell(fdt, "/", "#size-cells");
@@ -438,7 +446,7 @@ void arm_load_kernel(ARMCPU *cpu, struct arm_boot_info *info)
/* for device tree boot, we pass the DTB directly in r2. Otherwise
* we point to the kernel args.
*/
- if (info->dtb_filename) {
+ if (info->dtb_filename || info->get_dtb) {
/* Place the DTB after the initrd in memory. Note that some
* kernels will trash anything in the 4K page the initrd
* ends in, so make sure the DTB isn't caught up in that.
diff --git a/hw/arm/integratorcp.c b/hw/arm/integratorcp.c
index c44b2a499c..a759689b44 100644
--- a/hw/arm/integratorcp.c
+++ b/hw/arm/integratorcp.c
@@ -36,6 +36,7 @@ typedef struct IntegratorCMState {
uint32_t cm_init;
uint32_t cm_flags;
uint32_t cm_nvflags;
+ uint32_t cm_refcnt_offset;
uint32_t int_level;
uint32_t irq_enabled;
uint32_t fiq_enabled;
@@ -82,9 +83,13 @@ static uint64_t integratorcm_read(void *opaque, hwaddr offset,
return s->cm_sdram;
case 9: /* CM_INIT */
return s->cm_init;
- case 10: /* CM_REFCT */
- /* ??? High frequency timer. */
- hw_error("integratorcm_read: CM_REFCT");
+ case 10: /* CM_REFCNT */
+ /* This register, CM_REFCNT, provides a 32-bit count value.
+ * The count increments at the fixed reference clock frequency of 24MHz
+ * and can be used as a real-time counter.
+ */
+ return (uint32_t)muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 24,
+ 1000) - s->cm_refcnt_offset;
case 12: /* CM_FLAGS */
return s->cm_flags;
case 14: /* CM_NVFLAGS */
@@ -257,6 +262,8 @@ static int integratorcm_init(SysBusDevice *dev)
}
memcpy(integrator_spd + 73, "QEMU-MEMORY", 11);
s->cm_init = 0x00000112;
+ s->cm_refcnt_offset = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 24,
+ 1000);
memory_region_init_ram(&s->flash, OBJECT(s), "integrator.flash", 0x100000);
vmstate_register_ram_global(&s->flash);
diff --git a/hw/arm/virt.c b/hw/arm/virt.c
new file mode 100644
index 0000000000..9531b5a574
--- /dev/null
+++ b/hw/arm/virt.c
@@ -0,0 +1,452 @@
+/*
+ * ARM mach-virt emulation
+ *
+ * Copyright (c) 2013 Linaro Limited
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Emulate a virtual board which works by passing Linux all the information
+ * it needs about what devices are present via the device tree.
+ * There are some restrictions about what we can do here:
+ * + we can only present devices whose Linux drivers will work based
+ * purely on the device tree with no platform data at all
+ * + we want to present a very stripped-down minimalist platform,
+ * both because this reduces the security attack surface from the guest
+ * and also because it reduces our exposure to being broken when
+ * the kernel updates its device tree bindings and requires further
+ * information in a device binding that we aren't providing.
+ * This is essentially the same approach kvmtool uses.
+ */
+
+#include "hw/sysbus.h"
+#include "hw/arm/arm.h"
+#include "hw/arm/primecell.h"
+#include "hw/devices.h"
+#include "net/net.h"
+#include "sysemu/device_tree.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/kvm.h"
+#include "hw/boards.h"
+#include "exec/address-spaces.h"
+#include "qemu/bitops.h"
+#include "qemu/error-report.h"
+
+#define NUM_VIRTIO_TRANSPORTS 32
+
+/* Number of external interrupt lines to configure the GIC with */
+#define NUM_IRQS 128
+
+#define GIC_FDT_IRQ_TYPE_SPI 0
+#define GIC_FDT_IRQ_TYPE_PPI 1
+
+#define GIC_FDT_IRQ_FLAGS_EDGE_LO_HI 1
+#define GIC_FDT_IRQ_FLAGS_EDGE_HI_LO 2
+#define GIC_FDT_IRQ_FLAGS_LEVEL_HI 4
+#define GIC_FDT_IRQ_FLAGS_LEVEL_LO 8
+
+#define GIC_FDT_IRQ_PPI_CPU_START 8
+#define GIC_FDT_IRQ_PPI_CPU_WIDTH 8
+
+enum {
+ VIRT_FLASH,
+ VIRT_MEM,
+ VIRT_CPUPERIPHS,
+ VIRT_GIC_DIST,
+ VIRT_GIC_CPU,
+ VIRT_UART,
+ VIRT_MMIO,
+};
+
+typedef struct MemMapEntry {
+ hwaddr base;
+ hwaddr size;
+} MemMapEntry;
+
+typedef struct VirtBoardInfo {
+ struct arm_boot_info bootinfo;
+ const char *cpu_model;
+ const char *qdevname;
+ const char *gic_compatible;
+ const MemMapEntry *memmap;
+ const int *irqmap;
+ int smp_cpus;
+ void *fdt;
+ int fdt_size;
+ uint32_t clock_phandle;
+} VirtBoardInfo;
+
+/* Addresses and sizes of our components.
+ * 0..128MB is space for a flash device so we can run bootrom code such as UEFI.
+ * 128MB..256MB is used for miscellaneous device I/O.
+ * 256MB..1GB is reserved for possible future PCI support (ie where the
+ * PCI memory window will go if we add a PCI host controller).
+ * 1GB and up is RAM (which may happily spill over into the
+ * high memory region beyond 4GB).
+ * This represents a compromise between how much RAM can be given to
+ * a 32 bit VM and leaving space for expansion and in particular for PCI.
+ */
+static const MemMapEntry a15memmap[] = {
+ /* Space up to 0x8000000 is reserved for a boot ROM */
+ [VIRT_FLASH] = { 0, 0x8000000 },
+ [VIRT_CPUPERIPHS] = { 0x8000000, 0x8000 },
+ /* GIC distributor and CPU interfaces sit inside the CPU peripheral space */
+ [VIRT_GIC_DIST] = { 0x8001000, 0x1000 },
+ [VIRT_GIC_CPU] = { 0x8002000, 0x1000 },
+ [VIRT_UART] = { 0x9000000, 0x1000 },
+ [VIRT_MMIO] = { 0xa000000, 0x200 },
+ /* ...repeating for a total of NUM_VIRTIO_TRANSPORTS, each of that size */
+ /* 0x10000000 .. 0x40000000 reserved for PCI */
+ [VIRT_MEM] = { 0x40000000, 30ULL * 1024 * 1024 * 1024 },
+};
+
+static const int a15irqmap[] = {
+ [VIRT_UART] = 1,
+ [VIRT_MMIO] = 16, /* ...to 16 + NUM_VIRTIO_TRANSPORTS - 1 */
+};
+
+static VirtBoardInfo machines[] = {
+ {
+ .cpu_model = "cortex-a15",
+ .qdevname = "a15mpcore_priv",
+ .gic_compatible = "arm,cortex-a15-gic",
+ .memmap = a15memmap,
+ .irqmap = a15irqmap,
+ },
+ {
+ .cpu_model = "host",
+ /* We use the A15 private peripheral model to get a V2 GIC */
+ .qdevname = "a15mpcore_priv",
+ .gic_compatible = "arm,cortex-a15-gic",
+ .memmap = a15memmap,
+ .irqmap = a15irqmap,
+ },
+};
+
+static VirtBoardInfo *find_machine_info(const char *cpu)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(machines); i++) {
+ if (strcmp(cpu, machines[i].cpu_model) == 0) {
+ return &machines[i];
+ }
+ }
+ return NULL;
+}
+
+static void create_fdt(VirtBoardInfo *vbi)
+{
+ void *fdt = create_device_tree(&vbi->fdt_size);
+
+ if (!fdt) {
+ error_report("create_device_tree() failed");
+ exit(1);
+ }
+
+ vbi->fdt = fdt;
+
+ /* Header */
+ qemu_devtree_setprop_string(fdt, "/", "compatible", "linux,dummy-virt");
+ qemu_devtree_setprop_cell(fdt, "/", "#address-cells", 0x2);
+ qemu_devtree_setprop_cell(fdt, "/", "#size-cells", 0x2);
+
+ /*
+ * /chosen and /memory nodes must exist for load_dtb
+ * to fill in necessary properties later
+ */
+ qemu_devtree_add_subnode(fdt, "/chosen");
+ qemu_devtree_add_subnode(fdt, "/memory");
+ qemu_devtree_setprop_string(fdt, "/memory", "device_type", "memory");
+
+ /* Clock node, for the benefit of the UART. The kernel device tree
+ * binding documentation claims the PL011 node clock properties are
+ * optional but in practice if you omit them the kernel refuses to
+ * probe for the device.
+ */
+ vbi->clock_phandle = qemu_devtree_alloc_phandle(fdt);
+ qemu_devtree_add_subnode(fdt, "/apb-pclk");
+ qemu_devtree_setprop_string(fdt, "/apb-pclk", "compatible", "fixed-clock");
+ qemu_devtree_setprop_cell(fdt, "/apb-pclk", "#clock-cells", 0x0);
+ qemu_devtree_setprop_cell(fdt, "/apb-pclk", "clock-frequency", 24000000);
+ qemu_devtree_setprop_string(fdt, "/apb-pclk", "clock-output-names",
+ "clk24mhz");
+ qemu_devtree_setprop_cell(fdt, "/apb-pclk", "phandle", vbi->clock_phandle);
+
+ /* No PSCI for TCG yet */
+ if (kvm_enabled()) {
+ qemu_devtree_add_subnode(fdt, "/psci");
+ qemu_devtree_setprop_string(fdt, "/psci", "compatible", "arm,psci");
+ qemu_devtree_setprop_string(fdt, "/psci", "method", "hvc");
+ qemu_devtree_setprop_cell(fdt, "/psci", "cpu_suspend",
+ PSCI_FN_CPU_SUSPEND);
+ qemu_devtree_setprop_cell(fdt, "/psci", "cpu_off", PSCI_FN_CPU_OFF);
+ qemu_devtree_setprop_cell(fdt, "/psci", "cpu_on", PSCI_FN_CPU_ON);
+ qemu_devtree_setprop_cell(fdt, "/psci", "migrate", PSCI_FN_MIGRATE);
+ }
+}
+
+static void fdt_add_timer_nodes(const VirtBoardInfo *vbi)
+{
+ /* Note that on A15 h/w these interrupts are level-triggered,
+ * but for the GIC implementation provided by both QEMU and KVM
+ * they are edge-triggered.
+ */
+ uint32_t irqflags = GIC_FDT_IRQ_FLAGS_EDGE_LO_HI;
+
+ irqflags = deposit32(irqflags, GIC_FDT_IRQ_PPI_CPU_START,
+ GIC_FDT_IRQ_PPI_CPU_WIDTH, (1 << vbi->smp_cpus) - 1);
+
+ qemu_devtree_add_subnode(vbi->fdt, "/timer");
+ qemu_devtree_setprop_string(vbi->fdt, "/timer",
+ "compatible", "arm,armv7-timer");
+ qemu_devtree_setprop_cells(vbi->fdt, "/timer", "interrupts",
+ GIC_FDT_IRQ_TYPE_PPI, 13, irqflags,
+ GIC_FDT_IRQ_TYPE_PPI, 14, irqflags,
+ GIC_FDT_IRQ_TYPE_PPI, 11, irqflags,
+ GIC_FDT_IRQ_TYPE_PPI, 10, irqflags);
+}
+
+static void fdt_add_cpu_nodes(const VirtBoardInfo *vbi)
+{
+ int cpu;
+
+ qemu_devtree_add_subnode(vbi->fdt, "/cpus");
+ qemu_devtree_setprop_cell(vbi->fdt, "/cpus", "#address-cells", 0x1);
+ qemu_devtree_setprop_cell(vbi->fdt, "/cpus", "#size-cells", 0x0);
+
+ for (cpu = vbi->smp_cpus - 1; cpu >= 0; cpu--) {
+ char *nodename = g_strdup_printf("/cpus/cpu@%d", cpu);
+ ARMCPU *armcpu = ARM_CPU(qemu_get_cpu(cpu));
+
+ qemu_devtree_add_subnode(vbi->fdt, nodename);
+ qemu_devtree_setprop_string(vbi->fdt, nodename, "device_type", "cpu");
+ qemu_devtree_setprop_string(vbi->fdt, nodename, "compatible",
+ armcpu->dtb_compatible);
+
+ if (vbi->smp_cpus > 1) {
+ qemu_devtree_setprop_string(vbi->fdt, nodename,
+ "enable-method", "psci");
+ }
+
+ qemu_devtree_setprop_cell(vbi->fdt, nodename, "reg", cpu);
+ g_free(nodename);
+ }
+}
+
+static void fdt_add_gic_node(const VirtBoardInfo *vbi)
+{
+ uint32_t gic_phandle;
+
+ gic_phandle = qemu_devtree_alloc_phandle(vbi->fdt);
+ qemu_devtree_setprop_cell(vbi->fdt, "/", "interrupt-parent", gic_phandle);
+
+ qemu_devtree_add_subnode(vbi->fdt, "/intc");
+ qemu_devtree_setprop_string(vbi->fdt, "/intc", "compatible",
+ vbi->gic_compatible);
+ qemu_devtree_setprop_cell(vbi->fdt, "/intc", "#interrupt-cells", 3);
+ qemu_devtree_setprop(vbi->fdt, "/intc", "interrupt-controller", NULL, 0);
+ qemu_devtree_setprop_sized_cells(vbi->fdt, "/intc", "reg",
+ 2, vbi->memmap[VIRT_GIC_DIST].base,
+ 2, vbi->memmap[VIRT_GIC_DIST].size,
+ 2, vbi->memmap[VIRT_GIC_CPU].base,
+ 2, vbi->memmap[VIRT_GIC_CPU].size);
+ qemu_devtree_setprop_cell(vbi->fdt, "/intc", "phandle", gic_phandle);
+}
+
+static void create_uart(const VirtBoardInfo *vbi, qemu_irq *pic)
+{
+ char *nodename;
+ hwaddr base = vbi->memmap[VIRT_UART].base;
+ hwaddr size = vbi->memmap[VIRT_UART].size;
+ int irq = vbi->irqmap[VIRT_UART];
+ const char compat[] = "arm,pl011\0arm,primecell";
+ const char clocknames[] = "uartclk\0apb_pclk";
+
+ sysbus_create_simple("pl011", base, pic[irq]);
+
+ nodename = g_strdup_printf("/pl011@%" PRIx64, base);
+ qemu_devtree_add_subnode(vbi->fdt, nodename);
+ /* Note that we can't use setprop_string because of the embedded NUL */
+ qemu_devtree_setprop(vbi->fdt, nodename, "compatible",
+ compat, sizeof(compat));
+ qemu_devtree_setprop_sized_cells(vbi->fdt, nodename, "reg",
+ 2, base, 2, size);
+ qemu_devtree_setprop_cells(vbi->fdt, nodename, "interrupts",
+ GIC_FDT_IRQ_TYPE_SPI, irq,
+ GIC_FDT_IRQ_FLAGS_EDGE_LO_HI);
+ qemu_devtree_setprop_cells(vbi->fdt, nodename, "clocks",
+ vbi->clock_phandle, vbi->clock_phandle);
+ qemu_devtree_setprop(vbi->fdt, nodename, "clock-names",
+ clocknames, sizeof(clocknames));
+ g_free(nodename);
+}
+
+static void create_virtio_devices(const VirtBoardInfo *vbi, qemu_irq *pic)
+{
+ int i;
+ hwaddr size = vbi->memmap[VIRT_MMIO].size;
+
+ /* Note that we have to create the transports in forwards order
+ * so that command line devices are inserted lowest address first,
+ * and then add dtb nodes in reverse order so that they appear in
+ * the finished device tree lowest address first.
+ */
+ for (i = 0; i < NUM_VIRTIO_TRANSPORTS; i++) {
+ int irq = vbi->irqmap[VIRT_MMIO] + i;
+ hwaddr base = vbi->memmap[VIRT_MMIO].base + i * size;
+
+ sysbus_create_simple("virtio-mmio", base, pic[irq]);
+ }
+
+ for (i = NUM_VIRTIO_TRANSPORTS - 1; i >= 0; i--) {
+ char *nodename;
+ int irq = vbi->irqmap[VIRT_MMIO] + i;
+ hwaddr base = vbi->memmap[VIRT_MMIO].base + i * size;
+
+ nodename = g_strdup_printf("/virtio_mmio@%" PRIx64, base);
+ qemu_devtree_add_subnode(vbi->fdt, nodename);
+ qemu_devtree_setprop_string(vbi->fdt, nodename,
+ "compatible", "virtio,mmio");
+ qemu_devtree_setprop_sized_cells(vbi->fdt, nodename, "reg",
+ 2, base, 2, size);
+ qemu_devtree_setprop_cells(vbi->fdt, nodename, "interrupts",
+ GIC_FDT_IRQ_TYPE_SPI, irq,
+ GIC_FDT_IRQ_FLAGS_EDGE_LO_HI);
+ g_free(nodename);
+ }
+}
+
+static void *machvirt_dtb(const struct arm_boot_info *binfo, int *fdt_size)
+{
+ const VirtBoardInfo *board = (const VirtBoardInfo *)binfo;
+
+ *fdt_size = board->fdt_size;
+ return board->fdt;
+}
+
+static void machvirt_init(QEMUMachineInitArgs *args)
+{
+ qemu_irq pic[NUM_IRQS];
+ MemoryRegion *sysmem = get_system_memory();
+ int n;
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ DeviceState *dev;
+ SysBusDevice *busdev;
+ const char *cpu_model = args->cpu_model;
+ VirtBoardInfo *vbi;
+
+ if (!cpu_model) {
+ cpu_model = "cortex-a15";
+ }
+
+ vbi = find_machine_info(cpu_model);
+
+ if (!vbi) {
+ error_report("mach-virt: CPU %s not supported", cpu_model);
+ exit(1);
+ }
+
+ vbi->smp_cpus = smp_cpus;
+
+ /*
+ * Only supported method of starting secondary CPUs is PSCI and
+ * PSCI is not yet supported with TCG, so limit smp_cpus to 1
+ * if we're not using KVM.
+ */
+ if (!kvm_enabled() && smp_cpus > 1) {
+ error_report("mach-virt: must enable KVM to use multiple CPUs");
+ exit(1);
+ }
+
+ if (args->ram_size > vbi->memmap[VIRT_MEM].size) {
+ error_report("mach-virt: cannot model more than 30GB RAM");
+ exit(1);
+ }
+
+ create_fdt(vbi);
+ fdt_add_timer_nodes(vbi);
+
+ for (n = 0; n < smp_cpus; n++) {
+ ObjectClass *oc = cpu_class_by_name(TYPE_ARM_CPU, cpu_model);
+ Object *cpuobj;
+
+ if (!oc) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+ cpuobj = object_new(object_class_get_name(oc));
+
+ /* Secondary CPUs start in PSCI powered-down state */
+ if (n > 0) {
+ object_property_set_bool(cpuobj, true, "start-powered-off", NULL);
+ }
+ object_property_set_bool(cpuobj, true, "realized", NULL);
+ }
+ fdt_add_cpu_nodes(vbi);
+
+ memory_region_init_ram(ram, NULL, "mach-virt.ram", args->ram_size);
+ vmstate_register_ram_global(ram);
+ memory_region_add_subregion(sysmem, vbi->memmap[VIRT_MEM].base, ram);
+
+ dev = qdev_create(NULL, vbi->qdevname);
+ qdev_prop_set_uint32(dev, "num-cpu", smp_cpus);
+ /* Note that the num-irq property counts both internal and external
+ * interrupts; there are always 32 of the former (mandated by GIC spec).
+ */
+ qdev_prop_set_uint32(dev, "num-irq", NUM_IRQS + 32);
+ qdev_init_nofail(dev);
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(busdev, 0, vbi->memmap[VIRT_CPUPERIPHS].base);
+ fdt_add_gic_node(vbi);
+ for (n = 0; n < smp_cpus; n++) {
+ DeviceState *cpudev = DEVICE(qemu_get_cpu(n));
+
+ sysbus_connect_irq(busdev, n, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ));
+ }
+
+ for (n = 0; n < NUM_IRQS; n++) {
+ pic[n] = qdev_get_gpio_in(dev, n);
+ }
+
+ create_uart(vbi, pic);
+
+ /* Create mmio transports, so the user can create virtio backends
+ * (which will be automatically plugged in to the transports). If
+ * no backend is created the transport will just sit harmlessly idle.
+ */
+ create_virtio_devices(vbi, pic);
+
+ vbi->bootinfo.ram_size = args->ram_size;
+ vbi->bootinfo.kernel_filename = args->kernel_filename;
+ vbi->bootinfo.kernel_cmdline = args->kernel_cmdline;
+ vbi->bootinfo.initrd_filename = args->initrd_filename;
+ vbi->bootinfo.nb_cpus = smp_cpus;
+ vbi->bootinfo.board_id = -1;
+ vbi->bootinfo.loader_start = vbi->memmap[VIRT_MEM].base;
+ vbi->bootinfo.get_dtb = machvirt_dtb;
+ arm_load_kernel(ARM_CPU(first_cpu), &vbi->bootinfo);
+}
+
+static QEMUMachine machvirt_a15_machine = {
+ .name = "virt",
+ .desc = "ARM Virtual Machine",
+ .init = machvirt_init,
+ .max_cpus = 4,
+};
+
+static void machvirt_machine_init(void)
+{
+ qemu_register_machine(&machvirt_a15_machine);
+}
+
+machine_init(machvirt_machine_init);
diff --git a/hw/cpu/a9mpcore.c b/hw/cpu/a9mpcore.c
index 918a7d1291..c09358c6e7 100644
--- a/hw/cpu/a9mpcore.c
+++ b/hw/cpu/a9mpcore.c
@@ -24,11 +24,14 @@ static void a9mp_priv_initfn(Object *obj)
memory_region_init(&s->container, obj, "a9mp-priv-container", 0x2000);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->container);
+ object_initialize(&s->scu, sizeof(s->scu), TYPE_A9_SCU);
+ qdev_set_parent_bus(DEVICE(&s->scu), sysbus_get_default());
+
object_initialize(&s->gic, sizeof(s->gic), TYPE_ARM_GIC);
qdev_set_parent_bus(DEVICE(&s->gic), sysbus_get_default());
- object_initialize(&s->scu, sizeof(s->scu), TYPE_A9_SCU);
- qdev_set_parent_bus(DEVICE(&s->scu), sysbus_get_default());
+ object_initialize(&s->gtimer, sizeof(s->gtimer), TYPE_A9_GTIMER);
+ qdev_set_parent_bus(DEVICE(&s->gtimer), sysbus_get_default());
object_initialize(&s->mptimer, sizeof(s->mptimer), TYPE_ARM_MPTIMER);
qdev_set_parent_bus(DEVICE(&s->mptimer), sysbus_get_default());
@@ -41,11 +44,21 @@ static void a9mp_priv_realize(DeviceState *dev, Error **errp)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
A9MPPrivState *s = A9MPCORE_PRIV(dev);
- DeviceState *gicdev, *scudev, *mptimerdev, *wdtdev;
- SysBusDevice *timerbusdev, *wdtbusdev, *gicbusdev, *scubusdev;
+ DeviceState *scudev, *gicdev, *gtimerdev, *mptimerdev, *wdtdev;
+ SysBusDevice *scubusdev, *gicbusdev, *gtimerbusdev, *mptimerbusdev,
+ *wdtbusdev;
Error *err = NULL;
int i;
+ scudev = DEVICE(&s->scu);
+ qdev_prop_set_uint32(scudev, "num-cpu", s->num_cpu);
+ object_property_set_bool(OBJECT(&s->scu), true, "realized", &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ scubusdev = SYS_BUS_DEVICE(&s->scu);
+
gicdev = DEVICE(&s->gic);
qdev_prop_set_uint32(gicdev, "num-cpu", s->num_cpu);
qdev_prop_set_uint32(gicdev, "num-irq", s->num_irq);
@@ -62,14 +75,14 @@ static void a9mp_priv_realize(DeviceState *dev, Error **errp)
/* Pass through inbound GPIO lines to the GIC */
qdev_init_gpio_in(dev, a9mp_priv_set_irq, s->num_irq - 32);
- scudev = DEVICE(&s->scu);
- qdev_prop_set_uint32(scudev, "num-cpu", s->num_cpu);
- object_property_set_bool(OBJECT(&s->scu), true, "realized", &err);
+ gtimerdev = DEVICE(&s->gtimer);
+ qdev_prop_set_uint32(gtimerdev, "num-cpu", s->num_cpu);
+ object_property_set_bool(OBJECT(&s->gtimer), true, "realized", &err);
if (err != NULL) {
error_propagate(errp, err);
return;
}
- scubusdev = SYS_BUS_DEVICE(&s->scu);
+ gtimerbusdev = SYS_BUS_DEVICE(&s->gtimer);
mptimerdev = DEVICE(&s->mptimer);
qdev_prop_set_uint32(mptimerdev, "num-cpu", s->num_cpu);
@@ -78,7 +91,7 @@ static void a9mp_priv_realize(DeviceState *dev, Error **errp)
error_propagate(errp, err);
return;
}
- timerbusdev = SYS_BUS_DEVICE(&s->mptimer);
+ mptimerbusdev = SYS_BUS_DEVICE(&s->mptimer);
wdtdev = DEVICE(&s->wdt);
qdev_prop_set_uint32(wdtdev, "num-cpu", s->num_cpu);
@@ -97,30 +110,33 @@ static void a9mp_priv_realize(DeviceState *dev, Error **errp)
* 0x0600-0x06ff -- private timers and watchdogs
* 0x0700-0x0fff -- nothing
* 0x1000-0x1fff -- GIC Distributor
- *
- * We should implement the global timer but don't currently do so.
*/
memory_region_add_subregion(&s->container, 0,
sysbus_mmio_get_region(scubusdev, 0));
/* GIC CPU interface */
memory_region_add_subregion(&s->container, 0x100,
sysbus_mmio_get_region(gicbusdev, 1));
+ memory_region_add_subregion(&s->container, 0x200,
+ sysbus_mmio_get_region(gtimerbusdev, 0));
/* Note that the A9 exposes only the "timer/watchdog for this core"
* memory region, not the "timer/watchdog for core X" ones 11MPcore has.
*/
memory_region_add_subregion(&s->container, 0x600,
- sysbus_mmio_get_region(timerbusdev, 0));
+ sysbus_mmio_get_region(mptimerbusdev, 0));
memory_region_add_subregion(&s->container, 0x620,
sysbus_mmio_get_region(wdtbusdev, 0));
memory_region_add_subregion(&s->container, 0x1000,
sysbus_mmio_get_region(gicbusdev, 0));
/* Wire up the interrupt from each watchdog and timer.
- * For each core the timer is PPI 29 and the watchdog PPI 30.
+ * For each core the global timer is PPI 27, the private
+ * timer is PPI 29 and the watchdog PPI 30.
*/
for (i = 0; i < s->num_cpu; i++) {
int ppibase = (s->num_irq - 32) + i * 32;
- sysbus_connect_irq(timerbusdev, i,
+ sysbus_connect_irq(gtimerbusdev, i,
+ qdev_get_gpio_in(gicdev, ppibase + 27));
+ sysbus_connect_irq(mptimerbusdev, i,
qdev_get_gpio_in(gicdev, ppibase + 29));
sysbus_connect_irq(wdtbusdev, i,
qdev_get_gpio_in(gicdev, ppibase + 30));
diff --git a/hw/net/cadence_gem.c b/hw/net/cadence_gem.c
index 4a355bbbef..92dc2f21fa 100644
--- a/hw/net/cadence_gem.c
+++ b/hw/net/cadence_gem.c
@@ -222,8 +222,13 @@
#define PHY_REG_INT_ST_ENERGY 0x0010
/***********************************************************************/
-#define GEM_RX_REJECT 1
-#define GEM_RX_ACCEPT 0
+#define GEM_RX_REJECT (-1)
+#define GEM_RX_PROMISCUOUS_ACCEPT (-2)
+#define GEM_RX_BROADCAST_ACCEPT (-3)
+#define GEM_RX_MULTICAST_HASH_ACCEPT (-4)
+#define GEM_RX_UNICAST_HASH_ACCEPT (-5)
+
+#define GEM_RX_SAR_ACCEPT 0
/***********************************************************************/
@@ -236,6 +241,13 @@
#define DESC_0_RX_WRAP 0x00000002
#define DESC_0_RX_OWNERSHIP 0x00000001
+#define R_DESC_1_RX_SAR_SHIFT 25
+#define R_DESC_1_RX_SAR_LENGTH 2
+#define R_DESC_1_RX_SAR_MATCH (1 << 27)
+#define R_DESC_1_RX_UNICAST_HASH (1 << 29)
+#define R_DESC_1_RX_MULTICAST_HASH (1 << 30)
+#define R_DESC_1_RX_BROADCAST (1 << 31)
+
#define DESC_1_RX_SOF 0x00004000
#define DESC_1_RX_EOF 0x00008000
@@ -315,6 +327,28 @@ static inline void rx_desc_set_length(unsigned *desc, unsigned len)
desc[1] |= len;
}
+static inline void rx_desc_set_broadcast(unsigned *desc)
+{
+ desc[1] |= R_DESC_1_RX_BROADCAST;
+}
+
+static inline void rx_desc_set_unicast_hash(unsigned *desc)
+{
+ desc[1] |= R_DESC_1_RX_UNICAST_HASH;
+}
+
+static inline void rx_desc_set_multicast_hash(unsigned *desc)
+{
+ desc[1] |= R_DESC_1_RX_MULTICAST_HASH;
+}
+
+static inline void rx_desc_set_sar(unsigned *desc, int sar_idx)
+{
+ desc[1] = deposit32(desc[1], R_DESC_1_RX_SAR_SHIFT, R_DESC_1_RX_SAR_LENGTH,
+ sar_idx);
+ desc[1] |= R_DESC_1_RX_SAR_MATCH;
+}
+
#define TYPE_CADENCE_GEM "cadence_gem"
#define GEM(obj) OBJECT_CHECK(GemState, (obj), TYPE_CADENCE_GEM)
@@ -346,6 +380,11 @@ typedef struct GemState {
uint32_t rx_desc_addr;
uint32_t tx_desc_addr;
+ uint8_t can_rx_state; /* Debug only */
+
+ unsigned rx_desc[2];
+
+ bool sar_active[4];
} GemState;
/* The broadcast MAC address: 0xFFFFFFFFFFFF */
@@ -415,13 +454,28 @@ static int gem_can_receive(NetClientState *nc)
s = qemu_get_nic_opaque(nc);
- DB_PRINT("\n");
-
/* Do nothing if receive is not enabled. */
if (!(s->regs[GEM_NWCTRL] & GEM_NWCTRL_RXENA)) {
+ if (s->can_rx_state != 1) {
+ s->can_rx_state = 1;
+ DB_PRINT("can't receive - no enable\n");
+ }
return 0;
}
+ if (rx_desc_get_ownership(s->rx_desc) == 1) {
+ if (s->can_rx_state != 2) {
+ s->can_rx_state = 2;
+ DB_PRINT("can't receive - busy buffer descriptor 0x%x\n",
+ s->rx_desc_addr);
+ }
+ return 0;
+ }
+
+ if (s->can_rx_state != 0) {
+ s->can_rx_state = 0;
+ DB_PRINT("can receive 0x%x\n", s->rx_desc_addr);
+ }
return 1;
}
@@ -527,7 +581,10 @@ static unsigned calc_mac_hash(const uint8_t *mac)
* Accept or reject this destination address?
* Returns:
* GEM_RX_REJECT: reject
- * GEM_RX_ACCEPT: accept
+ * >= 0: Specific address accept (which matched SAR is returned)
+ * others for various other modes of accept:
+ * GEM_RM_PROMISCUOUS_ACCEPT, GEM_RX_BROADCAST_ACCEPT,
+ * GEM_RX_MULTICAST_HASH_ACCEPT or GEM_RX_UNICAST_HASH_ACCEPT
*/
static int gem_mac_address_filter(GemState *s, const uint8_t *packet)
{
@@ -536,7 +593,7 @@ static int gem_mac_address_filter(GemState *s, const uint8_t *packet)
/* Promiscuous mode? */
if (s->regs[GEM_NWCFG] & GEM_NWCFG_PROMISC) {
- return GEM_RX_ACCEPT;
+ return GEM_RX_PROMISCUOUS_ACCEPT;
}
if (!memcmp(packet, broadcast_addr, 6)) {
@@ -544,7 +601,7 @@ static int gem_mac_address_filter(GemState *s, const uint8_t *packet)
if (s->regs[GEM_NWCFG] & GEM_NWCFG_BCAST_REJ) {
return GEM_RX_REJECT;
}
- return GEM_RX_ACCEPT;
+ return GEM_RX_BROADCAST_ACCEPT;
}
/* Accept packets -w- hash match? */
@@ -555,53 +612,67 @@ static int gem_mac_address_filter(GemState *s, const uint8_t *packet)
hash_index = calc_mac_hash(packet);
if (hash_index < 32) {
if (s->regs[GEM_HASHLO] & (1<<hash_index)) {
- return GEM_RX_ACCEPT;
+ return packet[0] == 0x01 ? GEM_RX_MULTICAST_HASH_ACCEPT :
+ GEM_RX_UNICAST_HASH_ACCEPT;
}
} else {
hash_index -= 32;
if (s->regs[GEM_HASHHI] & (1<<hash_index)) {
- return GEM_RX_ACCEPT;
+ return packet[0] == 0x01 ? GEM_RX_MULTICAST_HASH_ACCEPT :
+ GEM_RX_UNICAST_HASH_ACCEPT;
}
}
}
/* Check all 4 specific addresses */
gem_spaddr = (uint8_t *)&(s->regs[GEM_SPADDR1LO]);
- for (i = 0; i < 4; i++) {
- if (!memcmp(packet, gem_spaddr, 6)) {
- return GEM_RX_ACCEPT;
+ for (i = 3; i >= 0; i--) {
+ if (s->sar_active[i] && !memcmp(packet, gem_spaddr + 8 * i, 6)) {
+ return GEM_RX_SAR_ACCEPT + i;
}
-
- gem_spaddr += 8;
}
/* No address match; reject the packet */
return GEM_RX_REJECT;
}
+static void gem_get_rx_desc(GemState *s)
+{
+ DB_PRINT("read descriptor 0x%x\n", (unsigned)s->rx_desc_addr);
+ /* read current descriptor */
+ cpu_physical_memory_read(s->rx_desc_addr,
+ (uint8_t *)s->rx_desc, sizeof(s->rx_desc));
+
+ /* Descriptor owned by software ? */
+ if (rx_desc_get_ownership(s->rx_desc) == 1) {
+ DB_PRINT("descriptor 0x%x owned by sw.\n",
+ (unsigned)s->rx_desc_addr);
+ s->regs[GEM_RXSTATUS] |= GEM_RXSTATUS_NOBUF;
+ s->regs[GEM_ISR] |= GEM_INT_RXUSED & ~(s->regs[GEM_IMR]);
+ /* Handle interrupt consequences */
+ gem_update_int_status(s);
+ }
+}
+
/*
* gem_receive:
* Fit a packet handed to us by QEMU into the receive descriptor ring.
*/
static ssize_t gem_receive(NetClientState *nc, const uint8_t *buf, size_t size)
{
- unsigned desc[2];
- hwaddr packet_desc_addr, last_desc_addr;
GemState *s;
unsigned rxbufsize, bytes_to_copy;
unsigned rxbuf_offset;
uint8_t rxbuf[2048];
uint8_t *rxbuf_ptr;
+ bool first_desc = true;
+ int maf;
s = qemu_get_nic_opaque(nc);
- /* Do nothing if receive is not enabled. */
- if (!gem_can_receive(nc)) {
- return -1;
- }
-
/* Is this destination MAC address "for us" ? */
- if (gem_mac_address_filter(s, buf) == GEM_RX_REJECT) {
+ maf = gem_mac_address_filter(s, buf);
+ if (maf == GEM_RX_REJECT) {
return -1;
}
@@ -633,6 +704,14 @@ static ssize_t gem_receive(NetClientState *nc, const uint8_t *buf, size_t size)
GEM_DMACFG_RBUFSZ_S) * GEM_DMACFG_RBUFSZ_MUL;
bytes_to_copy = size;
+ /* Pad to minimum length. Assume FCS field is stripped, logic
+ * below will increment it to the real minimum of 64 when
+ * not FCS stripping
+ */
+ if (size < 60) {
+ size = 60;
+ }
+
/* Strip of FCS field ? (usually yes) */
if (s->regs[GEM_NWCFG] & GEM_NWCFG_STRIP_FCS) {
rxbuf_ptr = (void *)buf;
@@ -659,95 +738,71 @@ static ssize_t gem_receive(NetClientState *nc, const uint8_t *buf, size_t size)
size += 4;
}
- /* Pad to minimum length */
- if (size < 64) {
- size = 64;
- }
-
DB_PRINT("config bufsize: %d packet size: %ld\n", rxbufsize, size);
- packet_desc_addr = s->rx_desc_addr;
- while (1) {
- DB_PRINT("read descriptor 0x%x\n", (unsigned)packet_desc_addr);
- /* read current descriptor */
- cpu_physical_memory_read(packet_desc_addr,
- (uint8_t *)&desc[0], sizeof(desc));
-
- /* Descriptor owned by software ? */
- if (rx_desc_get_ownership(desc) == 1) {
- DB_PRINT("descriptor 0x%x owned by sw.\n",
- (unsigned)packet_desc_addr);
- s->regs[GEM_RXSTATUS] |= GEM_RXSTATUS_NOBUF;
- s->regs[GEM_ISR] |= GEM_INT_RXUSED & ~(s->regs[GEM_IMR]);
- /* Handle interrupt consequences */
- gem_update_int_status(s);
+ while (bytes_to_copy) {
+ /* Do nothing if receive is not enabled. */
+ if (!gem_can_receive(nc)) {
+ assert(!first_desc);
return -1;
}
DB_PRINT("copy %d bytes to 0x%x\n", MIN(bytes_to_copy, rxbufsize),
- rx_desc_get_buffer(desc));
-
- /*
- * Let's have QEMU lend a helping hand.
- */
- if (rx_desc_get_buffer(desc) == 0) {
- DB_PRINT("Invalid RX buffer (NULL) for descriptor 0x%x\n",
- (unsigned)packet_desc_addr);
- break;
- }
+ rx_desc_get_buffer(s->rx_desc));
/* Copy packet data to emulated DMA buffer */
- cpu_physical_memory_write(rx_desc_get_buffer(desc) + rxbuf_offset,
+ cpu_physical_memory_write(rx_desc_get_buffer(s->rx_desc) + rxbuf_offset,
rxbuf_ptr, MIN(bytes_to_copy, rxbufsize));
- bytes_to_copy -= MIN(bytes_to_copy, rxbufsize);
rxbuf_ptr += MIN(bytes_to_copy, rxbufsize);
+ bytes_to_copy -= MIN(bytes_to_copy, rxbufsize);
+
+ /* Update the descriptor. */
+ if (first_desc) {
+ rx_desc_set_sof(s->rx_desc);
+ first_desc = false;
+ }
if (bytes_to_copy == 0) {
+ rx_desc_set_eof(s->rx_desc);
+ rx_desc_set_length(s->rx_desc, size);
+ }
+ rx_desc_set_ownership(s->rx_desc);
+
+ switch (maf) {
+ case GEM_RX_PROMISCUOUS_ACCEPT:
+ break;
+ case GEM_RX_BROADCAST_ACCEPT:
+ rx_desc_set_broadcast(s->rx_desc);
+ break;
+ case GEM_RX_UNICAST_HASH_ACCEPT:
+ rx_desc_set_unicast_hash(s->rx_desc);
+ break;
+ case GEM_RX_MULTICAST_HASH_ACCEPT:
+ rx_desc_set_multicast_hash(s->rx_desc);
break;
+ case GEM_RX_REJECT:
+ abort();
+ default: /* SAR */
+ rx_desc_set_sar(s->rx_desc, maf);
}
+ /* Descriptor write-back. */
+ cpu_physical_memory_write(s->rx_desc_addr,
+ (uint8_t *)s->rx_desc, sizeof(s->rx_desc));
+
/* Next descriptor */
- if (rx_desc_get_wrap(desc)) {
- packet_desc_addr = s->regs[GEM_RXQBASE];
+ if (rx_desc_get_wrap(s->rx_desc)) {
+ DB_PRINT("wrapping RX descriptor list\n");
+ s->rx_desc_addr = s->regs[GEM_RXQBASE];
} else {
- packet_desc_addr += 8;
+ DB_PRINT("incrementing RX descriptor list\n");
+ s->rx_desc_addr += 8;
}
+ gem_get_rx_desc(s);
}
- DB_PRINT("set length: %ld, EOF on descriptor 0x%x\n", size,
- (unsigned)packet_desc_addr);
-
- /* Update last descriptor with EOF and total length */
- rx_desc_set_eof(desc);
- rx_desc_set_length(desc, size);
- cpu_physical_memory_write(packet_desc_addr,
- (uint8_t *)&desc[0], sizeof(desc));
-
- /* Advance RX packet descriptor Q */
- last_desc_addr = packet_desc_addr;
- packet_desc_addr = s->rx_desc_addr;
- s->rx_desc_addr = last_desc_addr;
- if (rx_desc_get_wrap(desc)) {
- s->rx_desc_addr = s->regs[GEM_RXQBASE];
- DB_PRINT("wrapping RX descriptor list\n");
- } else {
- DB_PRINT("incrementing RX descriptor list\n");
- s->rx_desc_addr += 8;
- }
-
- DB_PRINT("set SOF, OWN on descriptor 0x%08x\n", (unsigned)packet_desc_addr);
-
/* Count it */
gem_receive_updatestats(s, buf, size);
- /* Update first descriptor (which could also be the last) */
- /* read descriptor */
- cpu_physical_memory_read(packet_desc_addr,
- (uint8_t *)&desc[0], sizeof(desc));
- rx_desc_set_sof(desc);
- rx_desc_set_ownership(desc);
- cpu_physical_memory_write(packet_desc_addr,
- (uint8_t *)&desc[0], sizeof(desc));
-
s->regs[GEM_RXSTATUS] |= GEM_RXSTATUS_FRMRCVD;
s->regs[GEM_ISR] |= GEM_INT_RXCMPL & ~(s->regs[GEM_IMR]);
@@ -893,7 +948,7 @@ static void gem_transmit(GemState *s)
gem_transmit_updatestats(s, tx_packet, total_bytes);
/* Send the packet somewhere */
- if (s->phy_loop) {
+ if (s->phy_loop || (s->regs[GEM_NWCTRL] & GEM_NWCTRL_LOCALLOOP)) {
gem_receive(qemu_get_queue(s->nic), tx_packet, total_bytes);
} else {
qemu_send_packet(qemu_get_queue(s->nic), tx_packet,
@@ -949,6 +1004,7 @@ static void gem_phy_reset(GemState *s)
static void gem_reset(DeviceState *d)
{
+ int i;
GemState *s = GEM(d);
DB_PRINT("\n");
@@ -968,6 +1024,10 @@ static void gem_reset(DeviceState *d)
s->regs[GEM_DESCONF5] = 0x002f2145;
s->regs[GEM_DESCONF6] = 0x00000200;
+ for (i = 0; i < 4; i++) {
+ s->sar_active[i] = false;
+ }
+
gem_phy_reset(s);
gem_update_int_status(s);
@@ -1069,19 +1129,21 @@ static void gem_write(void *opaque, hwaddr offset, uint64_t val,
/* Squash bits which are read only in write value */
val &= ~(s->regs_ro[offset]);
- /* Preserve (only) bits which are read only in register */
- readonly = s->regs[offset];
- readonly &= s->regs_ro[offset];
-
- /* Squash bits which are write 1 to clear */
- val &= ~(s->regs_w1c[offset] & val);
+ /* Preserve (only) bits which are read only and wtc in register */
+ readonly = s->regs[offset] & (s->regs_ro[offset] | s->regs_w1c[offset]);
/* Copy register write to backing store */
- s->regs[offset] = val | readonly;
+ s->regs[offset] = (val & ~s->regs_w1c[offset]) | readonly;
+
+ /* do w1c */
+ s->regs[offset] &= ~(s->regs_w1c[offset] & val);
/* Handle register write side effects */
switch (offset) {
case GEM_NWCTRL:
+ if (val & GEM_NWCTRL_RXENA) {
+ gem_get_rx_desc(s);
+ }
if (val & GEM_NWCTRL_TXSTART) {
gem_transmit(s);
}
@@ -1089,7 +1151,7 @@ static void gem_write(void *opaque, hwaddr offset, uint64_t val,
/* Reset to start of Q when transmit disabled. */
s->tx_desc_addr = s->regs[GEM_TXQBASE];
}
- if (val & GEM_NWCTRL_RXENA) {
+ if (gem_can_receive(qemu_get_queue(s->nic))) {
qemu_flush_queued_packets(qemu_get_queue(s->nic));
}
break;
@@ -1114,6 +1176,18 @@ static void gem_write(void *opaque, hwaddr offset, uint64_t val,
s->regs[GEM_IMR] |= val;
gem_update_int_status(s);
break;
+ case GEM_SPADDR1LO:
+ case GEM_SPADDR2LO:
+ case GEM_SPADDR3LO:
+ case GEM_SPADDR4LO:
+ s->sar_active[(offset - GEM_SPADDR1LO) / 2] = false;
+ break;
+ case GEM_SPADDR1HI:
+ case GEM_SPADDR2HI:
+ case GEM_SPADDR3HI:
+ case GEM_SPADDR4HI:
+ s->sar_active[(offset - GEM_SPADDR1HI) / 2] = true;
+ break;
case GEM_PHYMNTNC:
if (val & GEM_PHYMNTNC_OP_W) {
uint32_t phy_addr, reg_num;
@@ -1181,15 +1255,17 @@ static int gem_init(SysBusDevice *sbd)
static const VMStateDescription vmstate_cadence_gem = {
.name = "cadence_gem",
- .version_id = 1,
- .minimum_version_id = 1,
- .minimum_version_id_old = 1,
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .minimum_version_id_old = 2,
.fields = (VMStateField[]) {
VMSTATE_UINT32_ARRAY(regs, GemState, GEM_MAXREG),
VMSTATE_UINT16_ARRAY(phy_regs, GemState, 32),
VMSTATE_UINT8(phy_loop, GemState),
VMSTATE_UINT32(rx_desc_addr, GemState),
VMSTATE_UINT32(tx_desc_addr, GemState),
+ VMSTATE_BOOL_ARRAY(sar_active, GemState, 4),
+ VMSTATE_END_OF_LIST(),
}
};
diff --git a/hw/timer/Makefile.objs b/hw/timer/Makefile.objs
index eca590570e..3ae091c95e 100644
--- a/hw/timer/Makefile.objs
+++ b/hw/timer/Makefile.objs
@@ -1,5 +1,6 @@
common-obj-$(CONFIG_ARM_TIMER) += arm_timer.o
common-obj-$(CONFIG_ARM_MPTIMER) += arm_mptimer.o
+common-obj-$(CONFIG_A9_GTIMER) += a9gtimer.o
common-obj-$(CONFIG_CADENCE) += cadence_ttc.o
common-obj-$(CONFIG_DS1338) += ds1338.o
common-obj-$(CONFIG_HPET) += hpet.o
diff --git a/hw/timer/a9gtimer.c b/hw/timer/a9gtimer.c
new file mode 100644
index 0000000000..a0656d58a1
--- /dev/null
+++ b/hw/timer/a9gtimer.c
@@ -0,0 +1,369 @@
+/*
+ * Global peripheral timer block for ARM A9MP
+ *
+ * (C) 2013 Xilinx Inc.
+ *
+ * Written by François LEGAL
+ * Written by Peter Crosthwaite <peter.crosthwaite@xilinx.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/timer/a9gtimer.h"
+#include "qemu/timer.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+
+#ifndef A9_GTIMER_ERR_DEBUG
+#define A9_GTIMER_ERR_DEBUG 0
+#endif
+
+#define DB_PRINT_L(level, ...) do { \
+ if (A9_GTIMER_ERR_DEBUG > (level)) { \
+ fprintf(stderr, ": %s: ", __func__); \
+ fprintf(stderr, ## __VA_ARGS__); \
+ } \
+} while (0);
+
+#define DB_PRINT(...) DB_PRINT_L(0, ## __VA_ARGS__)
+
+static inline int a9_gtimer_get_current_cpu(A9GTimerState *s)
+{
+ if (current_cpu->cpu_index >= s->num_cpu) {
+ hw_error("a9gtimer: num-cpu %d but this cpu is %d!\n",
+ s->num_cpu, current_cpu->cpu_index);
+ }
+ return current_cpu->cpu_index;
+}
+
+static inline uint64_t a9_gtimer_get_conv(A9GTimerState *s)
+{
+ uint64_t prescale = extract32(s->control, R_CONTROL_PRESCALER_SHIFT,
+ R_CONTROL_PRESCALER_LEN);
+
+ return (prescale + 1) * 10;
+}
+
+static A9GTimerUpdate a9_gtimer_get_update(A9GTimerState *s)
+{
+ A9GTimerUpdate ret;
+
+ ret.now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ ret.new = s->ref_counter +
+ (ret.now - s->cpu_ref_time) / a9_gtimer_get_conv(s);
+ return ret;
+}
+
+static void a9_gtimer_update(A9GTimerState *s, bool sync)
+{
+
+ A9GTimerUpdate update = a9_gtimer_get_update(s);
+ int i;
+ int64_t next_cdiff = 0;
+
+ for (i = 0; i < s->num_cpu; ++i) {
+ A9GTimerPerCPU *gtb = &s->per_cpu[i];
+ int64_t cdiff = 0;
+
+ if ((s->control & R_CONTROL_TIMER_ENABLE) &&
+ (gtb->control & R_CONTROL_COMP_ENABLE)) {
+ /* R2p0+, where the compare function is >= */
+ while (gtb->compare < update.new) {
+ DB_PRINT("Compare event happened for CPU %d\n", i);
+ gtb->status = 1;
+ if (gtb->control & R_CONTROL_AUTO_INCREMENT) {
+ DB_PRINT("Auto incrementing timer compare by %" PRId32 "\n",
+ gtb->inc);
+ gtb->compare += gtb->inc;
+ } else {
+ break;
+ }
+ }
+ cdiff = (int64_t)gtb->compare - (int64_t)update.new + 1;
+ if (cdiff > 0 && (cdiff < next_cdiff || !next_cdiff)) {
+ next_cdiff = cdiff;
+ }
+ }
+
+ qemu_set_irq(gtb->irq,
+ gtb->status && (gtb->control & R_CONTROL_IRQ_ENABLE));
+ }
+
+ timer_del(s->timer);
+ if (next_cdiff) {
+ DB_PRINT("scheduling qemu_timer to fire again in %"
+ PRIx64 " cycles\n", next_cdiff);
+ timer_mod(s->timer, update.now + next_cdiff * a9_gtimer_get_conv(s));
+ }
+
+ if (s->control & R_CONTROL_TIMER_ENABLE) {
+ s->counter = update.new;
+ }
+
+ if (sync) {
+ s->cpu_ref_time = update.now;
+ s->ref_counter = s->counter;
+ }
+}
+
+static void a9_gtimer_update_no_sync(void *opaque)
+{
+ A9GTimerState *s = A9_GTIMER(opaque);
+
+ return a9_gtimer_update(s, false);
+}
+
+static uint64_t a9_gtimer_read(void *opaque, hwaddr addr, unsigned size)
+{
+ A9GTimerPerCPU *gtb = (A9GTimerPerCPU *)opaque;
+ A9GTimerState *s = gtb->parent;
+ A9GTimerUpdate update;
+ uint64_t ret = 0;
+ int shift = 0;
+
+ switch (addr) {
+ case R_COUNTER_HI:
+ shift = 32;
+ /* fallthrough */
+ case R_COUNTER_LO:
+ update = a9_gtimer_get_update(s);
+ ret = extract64(update.new, shift, 32);
+ break;
+ case R_CONTROL:
+ ret = s->control | gtb->control;
+ break;
+ case R_INTERRUPT_STATUS:
+ ret = gtb->status;
+ break;
+ case R_COMPARATOR_HI:
+ shift = 32;
+ /* fallthrough */
+ case R_COMPARATOR_LO:
+ ret = extract64(gtb->compare, shift, 32);
+ break;
+ case R_AUTO_INCREMENT:
+ ret = gtb->inc;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "bad a9gtimer register: %x\n",
+ (unsigned)addr);
+ return 0;
+ }
+
+ DB_PRINT("addr:%#x data:%#08" PRIx64 "\n", (unsigned)addr, ret);
+ return ret;
+}
+
+static void a9_gtimer_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ A9GTimerPerCPU *gtb = (A9GTimerPerCPU *)opaque;
+ A9GTimerState *s = gtb->parent;
+ int shift = 0;
+
+ DB_PRINT("addr:%#x data:%#08" PRIx64 "\n", (unsigned)addr, value);
+
+ switch (addr) {
+ case R_COUNTER_HI:
+ shift = 32;
+ /* fallthrough */
+ case R_COUNTER_LO:
+ /*
+ * Keep it simple - ARM docco explicitly says to disable timer before
+ * modding it, so dont bother trying to do all the difficult on the fly
+ * timer modifications - (if they even work in real hardware??).
+ */
+ if (s->control & R_CONTROL_TIMER_ENABLE) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Cannot mod running ARM gtimer\n");
+ return;
+ }
+ s->counter = deposit64(s->counter, shift, 32, value);
+ return;
+ case R_CONTROL:
+ a9_gtimer_update(s, (value ^ s->control) & R_CONTROL_NEEDS_SYNC);
+ gtb->control = value & R_CONTROL_BANKED;
+ s->control = value & ~R_CONTROL_BANKED;
+ break;
+ case R_INTERRUPT_STATUS:
+ a9_gtimer_update(s, false);
+ gtb->status &= ~value;
+ break;
+ case R_COMPARATOR_HI:
+ shift = 32;
+ /* fallthrough */
+ case R_COMPARATOR_LO:
+ a9_gtimer_update(s, false);
+ gtb->compare = deposit64(gtb->compare, shift, 32, value);
+ break;
+ case R_AUTO_INCREMENT:
+ gtb->inc = value;
+ return;
+ default:
+ return;
+ }
+
+ a9_gtimer_update(s, false);
+}
+
+/* Wrapper functions to implement the "read global timer for
+ * the current CPU" memory regions.
+ */
+static uint64_t a9_gtimer_this_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ A9GTimerState *s = A9_GTIMER(opaque);
+ int id = a9_gtimer_get_current_cpu(s);
+
+ /* no \n so concatenates with message from read fn */
+ DB_PRINT("CPU:%d:", id);
+
+ return a9_gtimer_read(&s->per_cpu[id], addr, size);
+}
+
+static void a9_gtimer_this_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ A9GTimerState *s = A9_GTIMER(opaque);
+ int id = a9_gtimer_get_current_cpu(s);
+
+ /* no \n so concatenates with message from write fn */
+ DB_PRINT("CPU:%d:", id);
+
+ a9_gtimer_write(&s->per_cpu[id], addr, value, size);
+}
+
+static const MemoryRegionOps a9_gtimer_this_ops = {
+ .read = a9_gtimer_this_read,
+ .write = a9_gtimer_this_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const MemoryRegionOps a9_gtimer_ops = {
+ .read = a9_gtimer_read,
+ .write = a9_gtimer_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void a9_gtimer_reset(DeviceState *dev)
+{
+ A9GTimerState *s = A9_GTIMER(dev);
+ int i;
+
+ s->counter = 0;
+ s->control = 0;
+
+ for (i = 0; i < s->num_cpu; i++) {
+ A9GTimerPerCPU *gtb = &s->per_cpu[i];
+
+ gtb->control = 0;
+ gtb->status = 0;
+ gtb->compare = 0;
+ gtb->inc = 0;
+ }
+ a9_gtimer_update(s, false);
+}
+
+static void a9_gtimer_realize(DeviceState *dev, Error **errp)
+{
+ A9GTimerState *s = A9_GTIMER(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ int i;
+
+ if (s->num_cpu < 1 || s->num_cpu > A9_GTIMER_MAX_CPUS) {
+ error_setg(errp, "%s: num-cpu must be between 1 and %d\n",
+ __func__, A9_GTIMER_MAX_CPUS);
+ return;
+ }
+
+ memory_region_init_io(&s->iomem, OBJECT(dev), &a9_gtimer_this_ops, s,
+ "a9gtimer shared", 0x20);
+ sysbus_init_mmio(sbd, &s->iomem);
+ s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, a9_gtimer_update_no_sync, s);
+
+ for (i = 0; i < s->num_cpu; i++) {
+ A9GTimerPerCPU *gtb = &s->per_cpu[i];
+
+ gtb->parent = s;
+ sysbus_init_irq(sbd, &gtb->irq);
+ memory_region_init_io(&gtb->iomem, OBJECT(dev), &a9_gtimer_ops, gtb,
+ "a9gtimer per cpu", 0x20);
+ sysbus_init_mmio(sbd, &gtb->iomem);
+ }
+}
+
+static const VMStateDescription vmstate_a9_gtimer_per_cpu = {
+ .name = "arm.cortex-a9-global-timer.percpu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(control, A9GTimerPerCPU),
+ VMSTATE_UINT64(compare, A9GTimerPerCPU),
+ VMSTATE_UINT32(status, A9GTimerPerCPU),
+ VMSTATE_UINT32(inc, A9GTimerPerCPU),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_a9_gtimer = {
+ .name = "arm.cortex-a9-global-timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_TIMER(timer, A9GTimerState),
+ VMSTATE_UINT64(counter, A9GTimerState),
+ VMSTATE_UINT64(ref_counter, A9GTimerState),
+ VMSTATE_UINT64(cpu_ref_time, A9GTimerState),
+ VMSTATE_STRUCT_VARRAY_UINT32(per_cpu, A9GTimerState, num_cpu,
+ 1, vmstate_a9_gtimer_per_cpu,
+ A9GTimerPerCPU),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property a9_gtimer_properties[] = {
+ DEFINE_PROP_UINT32("num-cpu", A9GTimerState, num_cpu, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void a9_gtimer_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = a9_gtimer_realize;
+ dc->vmsd = &vmstate_a9_gtimer;
+ dc->reset = a9_gtimer_reset;
+ dc->props = a9_gtimer_properties;
+}
+
+static const TypeInfo a9_gtimer_info = {
+ .name = TYPE_A9_GTIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(A9GTimerState),
+ .class_init = a9_gtimer_class_init,
+};
+
+static void a9_gtimer_register_types(void)
+{
+ type_register_static(&a9_gtimer_info);
+}
+
+type_init(a9_gtimer_register_types)