diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2020-04-30 15:45:34 +0100 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2020-04-30 15:45:34 +0100 |
commit | 126eeee6c7b516e0a348dd4d60e59dbfa4b4b513 (patch) | |
tree | 2b0239fe13bdbbf7ebf002c35668402a0498203a /hw | |
parent | 16aaacb307ed607b9780c12702c44f0fe52edc7e (diff) | |
parent | 6f7b6947a6639fff15c6a0956adf0f5ec004b789 (diff) | |
download | qemu-126eeee6c7b516e0a348dd4d60e59dbfa4b4b513.zip qemu-126eeee6c7b516e0a348dd4d60e59dbfa4b4b513.tar.gz qemu-126eeee6c7b516e0a348dd4d60e59dbfa4b4b513.tar.bz2 |
Merge remote-tracking branch 'remotes/pmaydell/tags/pull-target-arm-20200430-1' into staging
target-arm queue:
* xlnx-zdma: Fix endianness handling of descriptor loading
* nrf51: Fix last GPIO CNF address
* gicv3: Use gicr_typer in arm_gicv3_icc_reset
* msf2: Add EMAC block to SmartFusion2 SoC
* New clock modelling framework
* hw/arm: versal: Setup the ADMA with 128bit bus-width
* Cadence: gem: fix wraparound in 64bit descriptors
* cadence_gem: clear RX control descriptor
* target/arm: Vectorize integer comparison vs zero
* hw/arm/virt: dt: add kaslr-seed property
* hw/arm: xlnx-zcu102: Disable unsupported FDT firmware nodes
# gpg: Signature made Thu 30 Apr 2020 15:43:54 BST
# gpg: using RSA key E1A5C593CD419DE28E8315CF3C2525ED14360CDE
# gpg: issuer "peter.maydell@linaro.org"
# gpg: Good signature from "Peter Maydell <peter.maydell@linaro.org>" [ultimate]
# gpg: aka "Peter Maydell <pmaydell@gmail.com>" [ultimate]
# gpg: aka "Peter Maydell <pmaydell@chiark.greenend.org.uk>" [ultimate]
# Primary key fingerprint: E1A5 C593 CD41 9DE2 8E83 15CF 3C25 25ED 1436 0CDE
* remotes/pmaydell/tags/pull-target-arm-20200430-1: (30 commits)
hw/arm: xlnx-zcu102: Disable unsupported FDT firmware nodes
hw/arm: xlnx-zcu102: Move arm_boot_info into XlnxZCU102
device_tree: Constify compat in qemu_fdt_node_path()
device_tree: Allow name wildcards in qemu_fdt_node_path()
target/arm/cpu: Update coding style to make checkpatch.pl happy
target/arm: Make cpu_register() available for other files
target/arm: Restrict the Address Translate write operation to TCG accel
hw/arm/virt: dt: add kaslr-seed property
hw/arm/virt: dt: move creation of /secure-chosen to create_fdt()
target/arm: Vectorize integer comparison vs zero
net: cadence_gem: clear RX control descriptor
Cadence: gem: fix wraparound in 64bit descriptors
hw/arm: versal: Setup the ADMA with 128bit bus-width
qdev-monitor: print the device's clock with info qtree
hw/arm/xilinx_zynq: connect uart clocks to slcr
hw/char/cadence_uart: add clock support
hw/misc/zynq_slcr: add clock generation for uarts
docs/clocks: add device's clock documentation
qdev-clock: introduce an init array to ease the device construction
qdev: add clock input&output support to devices.
...
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'hw')
-rw-r--r-- | hw/acpi/cpu.c | 2 | ||||
-rw-r--r-- | hw/arm/msf2-soc.c | 26 | ||||
-rw-r--r-- | hw/arm/virt.c | 20 | ||||
-rw-r--r-- | hw/arm/xilinx_zynq.c | 57 | ||||
-rw-r--r-- | hw/arm/xlnx-versal.c | 2 | ||||
-rw-r--r-- | hw/arm/xlnx-zcu102.c | 39 | ||||
-rw-r--r-- | hw/char/cadence_uart.c | 73 | ||||
-rw-r--r-- | hw/char/trace-events | 3 | ||||
-rw-r--r-- | hw/core/Makefile.objs | 2 | ||||
-rw-r--r-- | hw/core/clock-vmstate.c | 25 | ||||
-rw-r--r-- | hw/core/clock.c | 130 | ||||
-rw-r--r-- | hw/core/qdev-clock.c | 185 | ||||
-rw-r--r-- | hw/core/qdev.c | 12 | ||||
-rw-r--r-- | hw/core/trace-events | 7 | ||||
-rw-r--r-- | hw/dma/xlnx-zdma.c | 25 | ||||
-rw-r--r-- | hw/intc/arm_gicv3_kvm.c | 4 | ||||
-rw-r--r-- | hw/misc/zynq_slcr.c | 172 | ||||
-rw-r--r-- | hw/net/Makefile.objs | 1 | ||||
-rw-r--r-- | hw/net/cadence_gem.c | 16 | ||||
-rw-r--r-- | hw/net/msf2-emac.c | 589 |
20 files changed, 1348 insertions, 42 deletions
diff --git a/hw/acpi/cpu.c b/hw/acpi/cpu.c index e2c957c..3d6a500 100644 --- a/hw/acpi/cpu.c +++ b/hw/acpi/cpu.c @@ -222,7 +222,7 @@ void cpu_hotplug_hw_init(MemoryRegion *as, Object *owner, state->devs[i].arch_id = id_list->cpus[i].arch_id; } memory_region_init_io(&state->ctrl_reg, owner, &cpu_hotplug_ops, state, - "acpi-mem-hotplug", ACPI_CPU_HOTPLUG_REG_LEN); + "acpi-cpu-hotplug", ACPI_CPU_HOTPLUG_REG_LEN); memory_region_add_subregion(as, base_addr, &state->ctrl_reg); } diff --git a/hw/arm/msf2-soc.c b/hw/arm/msf2-soc.c index 588d643..a455b88 100644 --- a/hw/arm/msf2-soc.c +++ b/hw/arm/msf2-soc.c @@ -1,7 +1,7 @@ /* * SmartFusion2 SoC emulation. * - * Copyright (c) 2017 Subbaraya Sundeep <sundeep.lkml@gmail.com> + * Copyright (c) 2017-2020 Subbaraya Sundeep <sundeep.lkml@gmail.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -35,11 +35,14 @@ #define MSF2_TIMER_BASE 0x40004000 #define MSF2_SYSREG_BASE 0x40038000 +#define MSF2_EMAC_BASE 0x40041000 #define ENVM_BASE_ADDRESS 0x60000000 #define SRAM_BASE_ADDRESS 0x20000000 +#define MSF2_EMAC_IRQ 12 + #define MSF2_ENVM_MAX_SIZE (512 * KiB) /* @@ -81,6 +84,13 @@ static void m2sxxx_soc_initfn(Object *obj) sysbus_init_child_obj(obj, "spi[*]", &s->spi[i], sizeof(s->spi[i]), TYPE_MSS_SPI); } + + sysbus_init_child_obj(obj, "emac", &s->emac, sizeof(s->emac), + TYPE_MSS_EMAC); + if (nd_table[0].used) { + qemu_check_nic_model(&nd_table[0], TYPE_MSS_EMAC); + qdev_set_nic_properties(DEVICE(&s->emac), &nd_table[0]); + } } static void m2sxxx_soc_realize(DeviceState *dev_soc, Error **errp) @@ -192,6 +202,19 @@ static void m2sxxx_soc_realize(DeviceState *dev_soc, Error **errp) g_free(bus_name); } + dev = DEVICE(&s->emac); + object_property_set_link(OBJECT(&s->emac), OBJECT(get_system_memory()), + "ahb-bus", &error_abort); + object_property_set_bool(OBJECT(&s->emac), true, "realized", &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, MSF2_EMAC_BASE); + sysbus_connect_irq(busdev, 0, + qdev_get_gpio_in(armv7m, MSF2_EMAC_IRQ)); + /* Below devices are not modelled yet. */ create_unimplemented_device("i2c_0", 0x40002000, 0x1000); create_unimplemented_device("dma", 0x40003000, 0x1000); @@ -202,7 +225,6 @@ static void m2sxxx_soc_realize(DeviceState *dev_soc, Error **errp) create_unimplemented_device("can", 0x40015000, 0x1000); create_unimplemented_device("rtc", 0x40017000, 0x1000); create_unimplemented_device("apb_config", 0x40020000, 0x10000); - create_unimplemented_device("emac", 0x40041000, 0x1000); create_unimplemented_device("usb", 0x40043000, 0x1000); } diff --git a/hw/arm/virt.c b/hw/arm/virt.c index cca5316..6268225 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -77,6 +77,7 @@ #include "hw/acpi/generic_event_device.h" #include "hw/virtio/virtio-iommu.h" #include "hw/char/pl011.h" +#include "qemu/guest-random.h" #define DEFINE_VIRT_MACHINE_LATEST(major, minor, latest) \ static void virt_##major##_##minor##_class_init(ObjectClass *oc, \ @@ -213,6 +214,18 @@ static bool cpu_type_valid(const char *cpu) return false; } +static void create_kaslr_seed(VirtMachineState *vms, const char *node) +{ + Error *err = NULL; + uint64_t seed; + + if (qemu_guest_getrandom(&seed, sizeof(seed), &err)) { + error_free(err); + return; + } + qemu_fdt_setprop_u64(vms->fdt, node, "kaslr-seed", seed); +} + static void create_fdt(VirtMachineState *vms) { MachineState *ms = MACHINE(vms); @@ -233,6 +246,12 @@ static void create_fdt(VirtMachineState *vms) /* /chosen must exist for load_dtb to fill in necessary properties later */ qemu_fdt_add_subnode(fdt, "/chosen"); + create_kaslr_seed(vms, "/chosen"); + + if (vms->secure) { + qemu_fdt_add_subnode(fdt, "/secure-chosen"); + create_kaslr_seed(vms, "/secure-chosen"); + } /* Clock node, for the benefit of the UART. The kernel device tree * binding documentation claims the PL011 node clock properties are @@ -761,7 +780,6 @@ static void create_uart(const VirtMachineState *vms, int uart, qemu_fdt_setprop_string(vms->fdt, nodename, "status", "disabled"); qemu_fdt_setprop_string(vms->fdt, nodename, "secure-status", "okay"); - qemu_fdt_add_subnode(vms->fdt, "/secure-chosen"); qemu_fdt_setprop_string(vms->fdt, "/secure-chosen", "stdout-path", nodename); } diff --git a/hw/arm/xilinx_zynq.c b/hw/arm/xilinx_zynq.c index 571cdcd..91b498d 100644 --- a/hw/arm/xilinx_zynq.c +++ b/hw/arm/xilinx_zynq.c @@ -35,6 +35,15 @@ #include "hw/char/cadence_uart.h" #include "hw/net/cadence_gem.h" #include "hw/cpu/a9mpcore.h" +#include "hw/qdev-clock.h" +#include "sysemu/reset.h" + +#define TYPE_ZYNQ_MACHINE MACHINE_TYPE_NAME("xilinx-zynq-a9") +#define ZYNQ_MACHINE(obj) \ + OBJECT_CHECK(ZynqMachineState, (obj), TYPE_ZYNQ_MACHINE) + +/* board base frequency: 33.333333 MHz */ +#define PS_CLK_FREQUENCY (100 * 1000 * 1000 / 3) #define NUM_SPI_FLASHES 4 #define NUM_QSPI_FLASHES 2 @@ -75,6 +84,11 @@ static const int dma_irqs[8] = { 0xe3401000 + ARMV7_IMM16(extract32((val), 16, 16)), /* movt r1 ... */ \ 0xe5801000 + (addr) +typedef struct ZynqMachineState { + MachineState parent; + Clock *ps_clk; +} ZynqMachineState; + static void zynq_write_board_setup(ARMCPU *cpu, const struct arm_boot_info *info) { @@ -159,10 +173,11 @@ static inline void zynq_init_spi_flashes(uint32_t base_addr, qemu_irq irq, static void zynq_init(MachineState *machine) { + ZynqMachineState *zynq_machine = ZYNQ_MACHINE(machine); ARMCPU *cpu; MemoryRegion *address_space_mem = get_system_memory(); MemoryRegion *ocm_ram = g_new(MemoryRegion, 1); - DeviceState *dev; + DeviceState *dev, *slcr; SysBusDevice *busdev; qemu_irq pic[64]; int n; @@ -206,9 +221,18 @@ static void zynq_init(MachineState *machine) 1, 0x0066, 0x0022, 0x0000, 0x0000, 0x0555, 0x2aa, 0); - dev = qdev_create(NULL, "xilinx,zynq_slcr"); - qdev_init_nofail(dev); - sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xF8000000); + /* Create slcr, keep a pointer to connect clocks */ + slcr = qdev_create(NULL, "xilinx,zynq_slcr"); + qdev_init_nofail(slcr); + sysbus_mmio_map(SYS_BUS_DEVICE(slcr), 0, 0xF8000000); + + /* Create the main clock source, and feed slcr with it */ + zynq_machine->ps_clk = CLOCK(object_new(TYPE_CLOCK)); + object_property_add_child(OBJECT(zynq_machine), "ps_clk", + OBJECT(zynq_machine->ps_clk), &error_abort); + object_unref(OBJECT(zynq_machine->ps_clk)); + clock_set_hz(zynq_machine->ps_clk, PS_CLK_FREQUENCY); + qdev_connect_clock_in(slcr, "ps_clk", zynq_machine->ps_clk); dev = qdev_create(NULL, TYPE_A9MPCORE_PRIV); qdev_prop_set_uint32(dev, "num-cpu", 1); @@ -229,8 +253,12 @@ static void zynq_init(MachineState *machine) sysbus_create_simple(TYPE_CHIPIDEA, 0xE0002000, pic[53 - IRQ_OFFSET]); sysbus_create_simple(TYPE_CHIPIDEA, 0xE0003000, pic[76 - IRQ_OFFSET]); - cadence_uart_create(0xE0000000, pic[59 - IRQ_OFFSET], serial_hd(0)); - cadence_uart_create(0xE0001000, pic[82 - IRQ_OFFSET], serial_hd(1)); + dev = cadence_uart_create(0xE0000000, pic[59 - IRQ_OFFSET], serial_hd(0)); + qdev_connect_clock_in(dev, "refclk", + qdev_get_clock_out(slcr, "uart0_ref_clk")); + dev = cadence_uart_create(0xE0001000, pic[82 - IRQ_OFFSET], serial_hd(1)); + qdev_connect_clock_in(dev, "refclk", + qdev_get_clock_out(slcr, "uart1_ref_clk")); sysbus_create_varargs("cadence_ttc", 0xF8001000, pic[42-IRQ_OFFSET], pic[43-IRQ_OFFSET], pic[44-IRQ_OFFSET], NULL); @@ -308,8 +336,9 @@ static void zynq_init(MachineState *machine) arm_load_kernel(ARM_CPU(first_cpu), machine, &zynq_binfo); } -static void zynq_machine_init(MachineClass *mc) +static void zynq_machine_class_init(ObjectClass *oc, void *data) { + MachineClass *mc = MACHINE_CLASS(oc); mc->desc = "Xilinx Zynq Platform Baseboard for Cortex-A9"; mc->init = zynq_init; mc->max_cpus = 1; @@ -319,4 +348,16 @@ static void zynq_machine_init(MachineClass *mc) mc->default_ram_id = "zynq.ext_ram"; } -DEFINE_MACHINE("xilinx-zynq-a9", zynq_machine_init) +static const TypeInfo zynq_machine_type = { + .name = TYPE_ZYNQ_MACHINE, + .parent = TYPE_MACHINE, + .class_init = zynq_machine_class_init, + .instance_size = sizeof(ZynqMachineState), +}; + +static void zynq_machine_register_types(void) +{ + type_register_static(&zynq_machine_type); +} + +type_init(zynq_machine_register_types) diff --git a/hw/arm/xlnx-versal.c b/hw/arm/xlnx-versal.c index cb0122a..94460f2 100644 --- a/hw/arm/xlnx-versal.c +++ b/hw/arm/xlnx-versal.c @@ -205,6 +205,8 @@ static void versal_create_admas(Versal *s, qemu_irq *pic) dev = qdev_create(NULL, "xlnx.zdma"); s->lpd.iou.adma[i] = SYS_BUS_DEVICE(dev); + object_property_set_int(OBJECT(s->lpd.iou.adma[i]), 128, "bus-width", + &error_abort); object_property_add_child(OBJECT(s), name, OBJECT(dev), &error_fatal); qdev_init_nofail(dev); diff --git a/hw/arm/xlnx-zcu102.c b/hw/arm/xlnx-zcu102.c index bd645ad..a798e22 100644 --- a/hw/arm/xlnx-zcu102.c +++ b/hw/arm/xlnx-zcu102.c @@ -23,6 +23,7 @@ #include "qemu/error-report.h" #include "qemu/log.h" #include "sysemu/qtest.h" +#include "sysemu/device_tree.h" typedef struct XlnxZCU102 { MachineState parent_obj; @@ -31,13 +32,14 @@ typedef struct XlnxZCU102 { bool secure; bool virt; + + struct arm_boot_info binfo; } XlnxZCU102; #define TYPE_ZCU102_MACHINE MACHINE_TYPE_NAME("xlnx-zcu102") #define ZCU102_MACHINE(obj) \ OBJECT_CHECK(XlnxZCU102, (obj), TYPE_ZCU102_MACHINE) -static struct arm_boot_info xlnx_zcu102_binfo; static bool zcu102_get_secure(Object *obj, Error **errp) { @@ -67,6 +69,34 @@ static void zcu102_set_virt(Object *obj, bool value, Error **errp) s->virt = value; } +static void zcu102_modify_dtb(const struct arm_boot_info *binfo, void *fdt) +{ + XlnxZCU102 *s = container_of(binfo, XlnxZCU102, binfo); + bool method_is_hvc; + char **node_path; + const char *r; + int prop_len; + int i; + + /* If EL3 is enabled, we keep all firmware nodes active. */ + if (!s->secure) { + node_path = qemu_fdt_node_path(fdt, NULL, "xlnx,zynqmp-firmware", + &error_fatal); + + for (i = 0; node_path && node_path[i]; i++) { + r = qemu_fdt_getprop(fdt, node_path[i], "method", &prop_len, NULL); + method_is_hvc = r && !strcmp("hvc", r); + + /* Allow HVC based firmware if EL2 is enabled. */ + if (method_is_hvc && s->virt) { + continue; + } + qemu_fdt_setprop_string(fdt, node_path[i], "status", "disabled"); + } + g_strfreev(node_path); + } +} + static void xlnx_zcu102_init(MachineState *machine) { XlnxZCU102 *s = ZCU102_MACHINE(machine); @@ -166,9 +196,10 @@ static void xlnx_zcu102_init(MachineState *machine) /* TODO create and connect IDE devices for ide_drive_get() */ - xlnx_zcu102_binfo.ram_size = ram_size; - xlnx_zcu102_binfo.loader_start = 0; - arm_load_kernel(s->soc.boot_cpu_ptr, machine, &xlnx_zcu102_binfo); + s->binfo.ram_size = ram_size; + s->binfo.loader_start = 0; + s->binfo.modify_dtb = zcu102_modify_dtb; + arm_load_kernel(s->soc.boot_cpu_ptr, machine, &s->binfo); } static void xlnx_zcu102_machine_instance_init(Object *obj) diff --git a/hw/char/cadence_uart.c b/hw/char/cadence_uart.c index 22e4797..e196906 100644 --- a/hw/char/cadence_uart.c +++ b/hw/char/cadence_uart.c @@ -31,6 +31,8 @@ #include "qemu/module.h" #include "hw/char/cadence_uart.h" #include "hw/irq.h" +#include "hw/qdev-clock.h" +#include "trace.h" #ifdef CADENCE_UART_ERR_DEBUG #define DB_PRINT(...) do { \ @@ -97,7 +99,7 @@ #define LOCAL_LOOPBACK (0x2 << UART_MR_CHMODE_SH) #define REMOTE_LOOPBACK (0x3 << UART_MR_CHMODE_SH) -#define UART_INPUT_CLK 50000000 +#define UART_DEFAULT_REF_CLK (50 * 1000 * 1000) #define R_CR (0x00/4) #define R_MR (0x04/4) @@ -171,12 +173,15 @@ static void uart_send_breaks(CadenceUARTState *s) static void uart_parameters_setup(CadenceUARTState *s) { QEMUSerialSetParams ssp; - unsigned int baud_rate, packet_size; + unsigned int baud_rate, packet_size, input_clk; + input_clk = clock_get_hz(s->refclk); - baud_rate = (s->r[R_MR] & UART_MR_CLKS) ? - UART_INPUT_CLK / 8 : UART_INPUT_CLK; + baud_rate = (s->r[R_MR] & UART_MR_CLKS) ? input_clk / 8 : input_clk; + baud_rate /= (s->r[R_BRGR] * (s->r[R_BDIV] + 1)); + trace_cadence_uart_baudrate(baud_rate); + + ssp.speed = baud_rate; - ssp.speed = baud_rate / (s->r[R_BRGR] * (s->r[R_BDIV] + 1)); packet_size = 1; switch (s->r[R_MR] & UART_MR_PAR) { @@ -215,6 +220,13 @@ static void uart_parameters_setup(CadenceUARTState *s) } packet_size += ssp.data_bits + ssp.stop_bits; + if (ssp.speed == 0) { + /* + * Avoid division-by-zero below. + * TODO: find something better + */ + ssp.speed = 1; + } s->char_tx_time = (NANOSECONDS_PER_SECOND / ssp.speed) * packet_size; qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp); } @@ -340,6 +352,11 @@ static void uart_receive(void *opaque, const uint8_t *buf, int size) CadenceUARTState *s = opaque; uint32_t ch_mode = s->r[R_MR] & UART_MR_CHMODE; + /* ignore characters when unclocked or in reset */ + if (!clock_is_enabled(s->refclk) || device_is_in_reset(DEVICE(s))) { + return; + } + if (ch_mode == NORMAL_MODE || ch_mode == ECHO_MODE) { uart_write_rx_fifo(opaque, buf, size); } @@ -353,6 +370,11 @@ static void uart_event(void *opaque, QEMUChrEvent event) CadenceUARTState *s = opaque; uint8_t buf = '\0'; + /* ignore characters when unclocked or in reset */ + if (!clock_is_enabled(s->refclk) || device_is_in_reset(DEVICE(s))) { + return; + } + if (event == CHR_EVENT_BREAK) { uart_write_rx_fifo(opaque, &buf, 1); } @@ -462,9 +484,9 @@ static const MemoryRegionOps uart_ops = { .endianness = DEVICE_NATIVE_ENDIAN, }; -static void cadence_uart_reset(DeviceState *dev) +static void cadence_uart_reset_init(Object *obj, ResetType type) { - CadenceUARTState *s = CADENCE_UART(dev); + CadenceUARTState *s = CADENCE_UART(obj); s->r[R_CR] = 0x00000128; s->r[R_IMR] = 0; @@ -473,6 +495,11 @@ static void cadence_uart_reset(DeviceState *dev) s->r[R_BRGR] = 0x0000028B; s->r[R_BDIV] = 0x0000000F; s->r[R_TTRIG] = 0x00000020; +} + +static void cadence_uart_reset_hold(Object *obj) +{ + CadenceUARTState *s = CADENCE_UART(obj); uart_rx_reset(s); uart_tx_reset(s); @@ -491,6 +518,14 @@ static void cadence_uart_realize(DeviceState *dev, Error **errp) uart_event, NULL, s, NULL, true); } +static void cadence_uart_refclk_update(void *opaque) +{ + CadenceUARTState *s = opaque; + + /* recompute uart's speed on clock change */ + uart_parameters_setup(s); +} + static void cadence_uart_init(Object *obj) { SysBusDevice *sbd = SYS_BUS_DEVICE(obj); @@ -500,9 +535,23 @@ static void cadence_uart_init(Object *obj) sysbus_init_mmio(sbd, &s->iomem); sysbus_init_irq(sbd, &s->irq); + s->refclk = qdev_init_clock_in(DEVICE(obj), "refclk", + cadence_uart_refclk_update, s); + /* initialize the frequency in case the clock remains unconnected */ + clock_set_hz(s->refclk, UART_DEFAULT_REF_CLK); + s->char_tx_time = (NANOSECONDS_PER_SECOND / 9600) * 10; } +static int cadence_uart_pre_load(void *opaque) +{ + CadenceUARTState *s = opaque; + + /* the frequency will be overriden if the refclk field is present */ + clock_set_hz(s->refclk, UART_DEFAULT_REF_CLK); + return 0; +} + static int cadence_uart_post_load(void *opaque, int version_id) { CadenceUARTState *s = opaque; @@ -521,8 +570,9 @@ static int cadence_uart_post_load(void *opaque, int version_id) static const VMStateDescription vmstate_cadence_uart = { .name = "cadence_uart", - .version_id = 2, + .version_id = 3, .minimum_version_id = 2, + .pre_load = cadence_uart_pre_load, .post_load = cadence_uart_post_load, .fields = (VMStateField[]) { VMSTATE_UINT32_ARRAY(r, CadenceUARTState, CADENCE_UART_R_MAX), @@ -534,8 +584,9 @@ static const VMStateDescription vmstate_cadence_uart = { VMSTATE_UINT32(tx_count, CadenceUARTState), VMSTATE_UINT32(rx_wpos, CadenceUARTState), VMSTATE_TIMER_PTR(fifo_trigger_handle, CadenceUARTState), + VMSTATE_CLOCK_V(refclk, CadenceUARTState, 3), VMSTATE_END_OF_LIST() - } + }, }; static Property cadence_uart_properties[] = { @@ -546,10 +597,12 @@ static Property cadence_uart_properties[] = { static void cadence_uart_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); dc->realize = cadence_uart_realize; dc->vmsd = &vmstate_cadence_uart; - dc->reset = cadence_uart_reset; + rc->phases.enter = cadence_uart_reset_init; + rc->phases.hold = cadence_uart_reset_hold; device_class_set_props(dc, cadence_uart_properties); } diff --git a/hw/char/trace-events b/hw/char/trace-events index 6f93830..d20eafd 100644 --- a/hw/char/trace-events +++ b/hw/char/trace-events @@ -97,3 +97,6 @@ exynos_uart_wo_read(uint32_t channel, const char *name, uint32_t reg) "UART%d: T exynos_uart_rxsize(uint32_t channel, uint32_t size) "UART%d: Rx FIFO size: %d" exynos_uart_channel_error(uint32_t channel) "Wrong UART channel number: %d" exynos_uart_rx_timeout(uint32_t channel, uint32_t stat, uint32_t intsp) "UART%d: Rx timeout stat=0x%x intsp=0x%x" + +# hw/char/cadence_uart.c +cadence_uart_baudrate(unsigned baudrate) "baudrate %u" diff --git a/hw/core/Makefile.objs b/hw/core/Makefile.objs index 6215e7c..1d540ed 100644 --- a/hw/core/Makefile.objs +++ b/hw/core/Makefile.objs @@ -7,6 +7,7 @@ common-obj-y += hotplug.o common-obj-y += vmstate-if.o # irq.o needed for qdev GPIO handling: common-obj-y += irq.o +common-obj-y += clock.o qdev-clock.o common-obj-$(CONFIG_SOFTMMU) += reset.o common-obj-$(CONFIG_SOFTMMU) += qdev-fw.o @@ -20,6 +21,7 @@ common-obj-$(CONFIG_SOFTMMU) += null-machine.o common-obj-$(CONFIG_SOFTMMU) += loader.o common-obj-$(CONFIG_SOFTMMU) += machine-hmp-cmds.o common-obj-$(CONFIG_SOFTMMU) += numa.o +common-obj-$(CONFIG_SOFTMMU) += clock-vmstate.o obj-$(CONFIG_SOFTMMU) += machine-qmp-cmds.o common-obj-$(CONFIG_EMPTY_SLOT) += empty_slot.o diff --git a/hw/core/clock-vmstate.c b/hw/core/clock-vmstate.c new file mode 100644 index 0000000..260b13f --- /dev/null +++ b/hw/core/clock-vmstate.c @@ -0,0 +1,25 @@ +/* + * Clock migration structure + * + * Copyright GreenSocs 2019-2020 + * + * Authors: + * Damien Hedde + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "migration/vmstate.h" +#include "hw/clock.h" + +const VMStateDescription vmstate_clock = { + .name = "clock", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT64(period, Clock), + VMSTATE_END_OF_LIST() + } +}; diff --git a/hw/core/clock.c b/hw/core/clock.c new file mode 100644 index 0000000..3c0daf7 --- /dev/null +++ b/hw/core/clock.c @@ -0,0 +1,130 @@ +/* + * Hardware Clocks + * + * Copyright GreenSocs 2016-2020 + * + * Authors: + * Frederic Konrad + * Damien Hedde + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "hw/clock.h" +#include "trace.h" + +#define CLOCK_PATH(_clk) (_clk->canonical_path) + +void clock_setup_canonical_path(Clock *clk) +{ + g_free(clk->canonical_path); + clk->canonical_path = object_get_canonical_path(OBJECT(clk)); +} + +void clock_set_callback(Clock *clk, ClockCallback *cb, void *opaque) +{ + clk->callback = cb; + clk->callback_opaque = opaque; +} + +void clock_clear_callback(Clock *clk) +{ + clock_set_callback(clk, NULL, NULL); +} + +void clock_set(Clock *clk, uint64_t period) +{ + trace_clock_set(CLOCK_PATH(clk), CLOCK_PERIOD_TO_NS(clk->period), + CLOCK_PERIOD_TO_NS(period)); + clk->period = period; +} + +static void clock_propagate_period(Clock *clk, bool call_callbacks) +{ + Clock *child; + + QLIST_FOREACH(child, &clk->children, sibling) { + if (child->period != clk->period) { + child->period = clk->period; + trace_clock_update(CLOCK_PATH(child), CLOCK_PATH(clk), + CLOCK_PERIOD_TO_NS(clk->period), + call_callbacks); + if (call_callbacks && child->callback) { + child->callback(child->callback_opaque); + } + clock_propagate_period(child, call_callbacks); + } + } +} + +void clock_propagate(Clock *clk) +{ + assert(clk->source == NULL); + trace_clock_propagate(CLOCK_PATH(clk)); + clock_propagate_period(clk, true); +} + +void clock_set_source(Clock *clk, Clock *src) +{ + /* changing clock source is not supported */ + assert(!clk->source); + + trace_clock_set_source(CLOCK_PATH(clk), CLOCK_PATH(src)); + + clk->period = src->period; + QLIST_INSERT_HEAD(&src->children, clk, sibling); + clk->source = src; + clock_propagate_period(clk, false); +} + +static void clock_disconnect(Clock *clk) +{ + if (clk->source == NULL) { + return; + } + + trace_clock_disconnect(CLOCK_PATH(clk)); + + clk->source = NULL; + QLIST_REMOVE(clk, sibling); +} + +static void clock_initfn(Object *obj) +{ + Clock *clk = CLOCK(obj); + + QLIST_INIT(&clk->children); +} + +static void clock_finalizefn(Object *obj) +{ + Clock *clk = CLOCK(obj); + Clock *child, *next; + + /* clear our list of children */ + QLIST_FOREACH_SAFE(child, &clk->children, sibling, next) { + clock_disconnect(child); + } + + /* remove us from source's children list */ + clock_disconnect(clk); + + g_free(clk->canonical_path); +} + +static const TypeInfo clock_info = { + .name = TYPE_CLOCK, + .parent = TYPE_OBJECT, + .instance_size = sizeof(Clock), + .instance_init = clock_initfn, + .instance_finalize = clock_finalizefn, +}; + +static void clock_register_types(void) +{ + type_register_static(&clock_info); +} + +type_init(clock_register_types) diff --git a/hw/core/qdev-clock.c b/hw/core/qdev-clock.c new file mode 100644 index 0000000..a94cc44 --- /dev/null +++ b/hw/core/qdev-clock.c @@ -0,0 +1,185 @@ +/* + * Device's clock input and output + * + * Copyright GreenSocs 2016-2020 + * + * Authors: + * Frederic Konrad + * Damien Hedde + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "hw/qdev-clock.h" +#include "hw/qdev-core.h" +#include "qapi/error.h" + +/* + * qdev_init_clocklist: + * Add a new clock in a device + */ +static NamedClockList *qdev_init_clocklist(DeviceState *dev, const char *name, + bool output, Clock *clk) +{ + NamedClockList *ncl; + + /* + * Clock must be added before realize() so that we can compute the + * clock's canonical path during device_realize(). + */ + assert(!dev->realized); + + /* + * The ncl structure is freed by qdev_finalize_clocklist() which will + * be called during @dev's device_finalize(). + */ + ncl = g_new0(NamedClockList, 1); + ncl->name = g_strdup(name); + ncl->output = output; + ncl->alias = (clk != NULL); + + /* + * Trying to create a clock whose name clashes with some other + * clock or property is a bug in the caller and we will abort(). + */ + if (clk == NULL) { + clk = CLOCK(object_new(TYPE_CLOCK)); + object_property_add_child(OBJECT(dev), name, OBJECT(clk), &error_abort); + if (output) { + /* + * Remove object_new()'s initial reference. + * Note that for inputs, the reference created by object_new() + * will be deleted in qdev_finalize_clocklist(). + */ + object_unref(OBJECT(clk)); + } + } else { + object_property_add_link(OBJECT(dev), name, + object_get_typename(OBJECT(clk)), + (Object **) &ncl->clock, + NULL, OBJ_PROP_LINK_STRONG, &error_abort); + } + + ncl->clock = clk; + + QLIST_INSERT_HEAD(&dev->clocks, ncl, node); + return ncl; +} + +void qdev_finalize_clocklist(DeviceState *dev) +{ + /* called by @dev's device_finalize() */ + NamedClockList *ncl, *ncl_next; + + QLIST_FOREACH_SAFE(ncl, &dev->clocks, node, ncl_next) { + QLIST_REMOVE(ncl, node); + if (!ncl->output && !ncl->alias) { + /* + * We kept a reference on the input clock to ensure it lives up to + * this point so we can safely remove the callback. + * It avoids having a callback to a deleted object if ncl->clock + * is still referenced somewhere else (eg: by a clock output). + */ + clock_clear_callback(ncl->clock); + object_unref(OBJECT(ncl->clock)); + } + g_free(ncl->name); + g_free(ncl); + } +} + +Clock *qdev_init_clock_out(DeviceState *dev, const char *name) +{ + NamedClockList *ncl; + + assert(name); + + ncl = qdev_init_clocklist(dev, name, true, NULL); + + return ncl->clock; +} + +Clock *qdev_init_clock_in(DeviceState *dev, const char *name, + ClockCallback *callback, void *opaque) +{ + NamedClockList *ncl; + + assert(name); + + ncl = qdev_init_clocklist(dev, name, false, NULL); + + if (callback) { + clock_set_callback(ncl->clock, callback, opaque); + } + return ncl->clock; +} + +void qdev_init_clocks(DeviceState *dev, const ClockPortInitArray clocks) +{ + const struct ClockPortInitElem *elem; + + for (elem = &clocks[0]; elem->name != NULL; elem++) { + Clock **clkp; + /* offset cannot be inside the DeviceState part */ + assert(elem->offset > sizeof(DeviceState)); + clkp = (Clock **)(((void *) dev) + elem->offset); + if (elem->is_output) { + *clkp = qdev_init_clock_out(dev, elem->name); + } else { + *clkp = qdev_init_clock_in(dev, elem->name, elem->callback, dev); + } + } +} + +static NamedClockList *qdev_get_clocklist(DeviceState *dev, const char *name) +{ + NamedClockList *ncl; + + QLIST_FOREACH(ncl, &dev->clocks, node) { + if (strcmp(name, ncl->name) == 0) { + return ncl; + } + } + + return NULL; +} + +Clock *qdev_get_clock_in(DeviceState *dev, const char *name) +{ + NamedClockList *ncl; + + assert(name); + + ncl = qdev_get_clocklist(dev, name); + assert(!ncl->output); + + return ncl->clock; +} + +Clock *qdev_get_clock_out(DeviceState *dev, const char *name) +{ + NamedClockList *ncl; + + assert(name); + + ncl = qdev_get_clocklist(dev, name); + assert(ncl->output); + + return ncl->clock; +} + +Clock *qdev_alias_clock(DeviceState *dev, const char *name, + DeviceState *alias_dev, const char *alias_name) +{ + NamedClockList *ncl; + + assert(name && alias_name); + + ncl = qdev_get_clocklist(dev, name); + + qdev_init_clocklist(alias_dev, alias_name, ncl->output, ncl->clock); + + return ncl->clock; +} diff --git a/hw/core/qdev.c b/hw/core/qdev.c index 85f062d..dd77a56 100644 --- a/hw/core/qdev.c +++ b/hw/core/qdev.c @@ -37,6 +37,7 @@ #include "hw/qdev-properties.h" #include "hw/boards.h" #include "hw/sysbus.h" +#include "hw/qdev-clock.h" #include "migration/vmstate.h" #include "trace.h" @@ -855,6 +856,7 @@ static void device_set_realized(Object *obj, bool value, Error **errp) DeviceClass *dc = DEVICE_GET_CLASS(dev); HotplugHandler *hotplug_ctrl; BusState *bus; + NamedClockList *ncl; Error *local_err = NULL; bool unattached_parent = false; static int unattached_count; @@ -902,6 +904,13 @@ static void device_set_realized(Object *obj, bool value, Error **errp) */ g_free(dev->canonical_path); dev->canonical_path = object_get_canonical_path(OBJECT(dev)); + QLIST_FOREACH(ncl, &dev->clocks, node) { + if (ncl->alias) { + continue; + } else { + clock_setup_canonical_path(ncl->clock); + } + } if (qdev_get_vmsd(dev)) { if (vmstate_register_with_alias_id(VMSTATE_IF(dev), @@ -1025,6 +1034,7 @@ static void device_initfn(Object *obj) dev->allow_unplug_during_migration = false; QLIST_INIT(&dev->gpios); + QLIST_INIT(&dev->clocks); } static void device_post_init(Object *obj) @@ -1054,6 +1064,8 @@ static void device_finalize(Object *obj) */ } + qdev_finalize_clocklist(dev); + /* Only send event if the device had been completely realized */ if (dev->pending_deleted_event) { g_assert(dev->canonical_path); diff --git a/hw/core/trace-events b/hw/core/trace-events index aecd8e1..1ac60ed 100644 --- a/hw/core/trace-events +++ b/hw/core/trace-events @@ -27,3 +27,10 @@ resettable_phase_exit_begin(void *obj, const char *objtype, unsigned count, int resettable_phase_exit_exec(void *obj, const char *objtype, int has_method) "obj=%p(%s) method=%d" resettable_phase_exit_end(void *obj, const char *objtype, unsigned count) "obj=%p(%s) count=%d" resettable_transitional_function(void *obj, const char *objtype) "obj=%p(%s)" + +# clock.c +clock_set_source(const char *clk, const char *src) "'%s', src='%s'" +clock_disconnect(const char *clk) "'%s'" +clock_set(const char *clk, uint64_t old, uint64_t new) "'%s', ns=%"PRIu64"->%"PRIu64 +clock_propagate(const char *clk) "'%s'" +clock_update(const char *clk, const char *src, uint64_t val, int cb) "'%s', src='%s', ns=%"PRIu64", cb=%d" diff --git a/hw/dma/xlnx-zdma.c b/hw/dma/xlnx-zdma.c index 1c45367..4121a1b 100644 --- a/hw/dma/xlnx-zdma.c +++ b/hw/dma/xlnx-zdma.c @@ -299,19 +299,30 @@ static void zdma_put_regaddr64(XlnxZDMA *s, unsigned int basereg, uint64_t addr) s->regs[basereg + 1] = addr >> 32; } -static bool zdma_load_descriptor(XlnxZDMA *s, uint64_t addr, void *buf) +static void zdma_load_descriptor_reg(XlnxZDMA *s, unsigned int reg, + XlnxZDMADescr *descr) +{ + descr->addr = zdma_get_regaddr64(s, reg); + descr->size = s->regs[reg + 2]; + descr->attr = s->regs[reg + 3]; +} + +static bool zdma_load_descriptor(XlnxZDMA *s, uint64_t addr, + XlnxZDMADescr *descr) { /* ZDMA descriptors must be aligned to their own size. */ if (addr % sizeof(XlnxZDMADescr)) { qemu_log_mask(LOG_GUEST_ERROR, "zdma: unaligned descriptor at %" PRIx64, addr); - memset(buf, 0x0, sizeof(XlnxZDMADescr)); + memset(descr, 0x0, sizeof(XlnxZDMADescr)); s->error = true; return false; } - address_space_read(s->dma_as, addr, s->attr, buf, sizeof(XlnxZDMADescr)); + descr->addr = address_space_ldq_le(s->dma_as, addr, s->attr, NULL); + descr->size = address_space_ldl_le(s->dma_as, addr + 8, s->attr, NULL); + descr->attr = address_space_ldl_le(s->dma_as, addr + 12, s->attr, NULL); return true; } @@ -321,8 +332,7 @@ static void zdma_load_src_descriptor(XlnxZDMA *s) unsigned int ptype = ARRAY_FIELD_EX32(s->regs, ZDMA_CH_CTRL0, POINT_TYPE); if (ptype == PT_REG) { - memcpy(&s->dsc_src, &s->regs[R_ZDMA_CH_SRC_DSCR_WORD0], - sizeof(s->dsc_src)); + zdma_load_descriptor_reg(s, R_ZDMA_CH_SRC_DSCR_WORD0, &s->dsc_src); return; } @@ -344,7 +354,7 @@ static void zdma_update_descr_addr(XlnxZDMA *s, bool type, } else { addr = zdma_get_regaddr64(s, basereg); addr += sizeof(s->dsc_dst); - address_space_read(s->dma_as, addr, s->attr, (void *) &next, 8); + next = address_space_ldq_le(s->dma_as, addr, s->attr, NULL); } zdma_put_regaddr64(s, basereg, next); @@ -357,8 +367,7 @@ static void zdma_load_dst_descriptor(XlnxZDMA *s) bool dst_type; if (ptype == PT_REG) { - memcpy(&s->dsc_dst, &s->regs[R_ZDMA_CH_DST_DSCR_WORD0], - sizeof(s->dsc_dst)); + zdma_load_descriptor_reg(s, R_ZDMA_CH_DST_DSCR_WORD0, &s->dsc_dst); return; } diff --git a/hw/intc/arm_gicv3_kvm.c b/hw/intc/arm_gicv3_kvm.c index 49304ca..ca43bf8 100644 --- a/hw/intc/arm_gicv3_kvm.c +++ b/hw/intc/arm_gicv3_kvm.c @@ -658,13 +658,11 @@ static void kvm_arm_gicv3_get(GICv3State *s) static void arm_gicv3_icc_reset(CPUARMState *env, const ARMCPRegInfo *ri) { - ARMCPU *cpu; GICv3State *s; GICv3CPUState *c; c = (GICv3CPUState *)env->gicv3state; s = c->gic; - cpu = ARM_CPU(c->cpu); c->icc_pmr_el1 = 0; c->icc_bpr[GICV3_G0] = GIC_MIN_BPR; @@ -681,7 +679,7 @@ static void arm_gicv3_icc_reset(CPUARMState *env, const ARMCPRegInfo *ri) /* Initialize to actual HW supported configuration */ kvm_device_access(s->dev_fd, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, - KVM_VGIC_ATTR(ICC_CTLR_EL1, cpu->mp_affinity), + KVM_VGIC_ATTR(ICC_CTLR_EL1, c->gicr_typer), &c->icc_ctlr_el1[GICV3_NS], false, &error_abort); c->icc_ctlr_el1[GICV3_S] = c->icc_ctlr_el1[GICV3_NS]; diff --git a/hw/misc/zynq_slcr.c b/hw/misc/zynq_slcr.c index b9a3827..f7472d1 100644 --- a/hw/misc/zynq_slcr.c +++ b/hw/misc/zynq_slcr.c @@ -22,6 +22,7 @@ #include "qemu/log.h" #include "qemu/module.h" #include "hw/registerfields.h" +#include "hw/qdev-clock.h" #ifndef ZYNQ_SLCR_ERR_DEBUG #define ZYNQ_SLCR_ERR_DEBUG 0 @@ -45,6 +46,12 @@ REG32(LOCKSTA, 0x00c) REG32(ARM_PLL_CTRL, 0x100) REG32(DDR_PLL_CTRL, 0x104) REG32(IO_PLL_CTRL, 0x108) +/* fields for [ARM|DDR|IO]_PLL_CTRL registers */ + FIELD(xxx_PLL_CTRL, PLL_RESET, 0, 1) + FIELD(xxx_PLL_CTRL, PLL_PWRDWN, 1, 1) + FIELD(xxx_PLL_CTRL, PLL_BYPASS_QUAL, 3, 1) + FIELD(xxx_PLL_CTRL, PLL_BYPASS_FORCE, 4, 1) + FIELD(xxx_PLL_CTRL, PLL_FPDIV, 12, 7) REG32(PLL_STATUS, 0x10c) REG32(ARM_PLL_CFG, 0x110) REG32(DDR_PLL_CFG, 0x114) @@ -64,6 +71,10 @@ REG32(SMC_CLK_CTRL, 0x148) REG32(LQSPI_CLK_CTRL, 0x14c) REG32(SDIO_CLK_CTRL, 0x150) REG32(UART_CLK_CTRL, 0x154) + FIELD(UART_CLK_CTRL, CLKACT0, 0, 1) + FIELD(UART_CLK_CTRL, CLKACT1, 1, 1) + FIELD(UART_CLK_CTRL, SRCSEL, 4, 2) + FIELD(UART_CLK_CTRL, DIVISOR, 8, 6) REG32(SPI_CLK_CTRL, 0x158) REG32(CAN_CLK_CTRL, 0x15c) REG32(CAN_MIOCLK_CTRL, 0x160) @@ -179,11 +190,127 @@ typedef struct ZynqSLCRState { MemoryRegion iomem; uint32_t regs[ZYNQ_SLCR_NUM_REGS]; + + Clock *ps_clk; + Clock *uart0_ref_clk; + Clock *uart1_ref_clk; } ZynqSLCRState; -static void zynq_slcr_reset(DeviceState *d) +/* + * return the output frequency of ARM/DDR/IO pll + * using input frequency and PLL_CTRL register + */ +static uint64_t zynq_slcr_compute_pll(uint64_t input, uint32_t ctrl_reg) +{ + uint32_t mult = ((ctrl_reg & R_xxx_PLL_CTRL_PLL_FPDIV_MASK) >> + R_xxx_PLL_CTRL_PLL_FPDIV_SHIFT); + + /* first, check if pll is bypassed */ + if (ctrl_reg & R_xxx_PLL_CTRL_PLL_BYPASS_FORCE_MASK) { + return input; + } + + /* is pll disabled ? */ + if (ctrl_reg & (R_xxx_PLL_CTRL_PLL_RESET_MASK | + R_xxx_PLL_CTRL_PLL_PWRDWN_MASK)) { + return 0; + } + + /* frequency multiplier -> period division */ + return input / mult; +} + +/* + * return the output period of a clock given: + * + the periods in an array corresponding to input mux selector + * + the register xxx_CLK_CTRL value + * + enable bit index in ctrl register + * + * This function makes the assumption that the ctrl_reg value is organized as + * follows: + * + bits[13:8] clock frequency divisor + * + bits[5:4] clock mux selector (index in array) + * + bits[index] clock enable + */ +static uint64_t zynq_slcr_compute_clock(const uint64_t periods[], + uint32_t ctrl_reg, + unsigned index) +{ + uint32_t srcsel = extract32(ctrl_reg, 4, 2); /* bits [5:4] */ + uint32_t divisor = extract32(ctrl_reg, 8, 6); /* bits [13:8] */ + + /* first, check if clock is disabled */ + if (((ctrl_reg >> index) & 1u) == 0) { + return 0; + } + + /* + * according to the Zynq technical ref. manual UG585 v1.12.2 in + * Clocks chapter, section 25.10.1 page 705: + * "The 6-bit divider provides a divide range of 1 to 63" + * We follow here what is implemented in linux kernel and consider + * the 0 value as a bypass (no division). + */ + /* frequency divisor -> period multiplication */ + return periods[srcsel] * (divisor ? divisor : 1u); +} + +/* + * macro helper around zynq_slcr_compute_clock to avoid repeating + * the register name. + */ +#define ZYNQ_COMPUTE_CLK(state, plls, reg, enable_field) \ + zynq_slcr_compute_clock((plls), (state)->regs[reg], \ + reg ## _ ## enable_field ## _SHIFT) + +/** + * Compute and set the ouputs clocks periods. + * But do not propagate them further. Connected clocks + * will not receive any updates (See zynq_slcr_compute_clocks()) + */ +static void zynq_slcr_compute_clocks(ZynqSLCRState *s) +{ + uint64_t ps_clk = clock_get(s->ps_clk); + + /* consider outputs clocks are disabled while in reset */ + if (device_is_in_reset(DEVICE(s))) { + ps_clk = 0; + } + + uint64_t io_pll = zynq_slcr_compute_pll(ps_clk, s->regs[R_IO_PLL_CTRL]); + uint64_t arm_pll = zynq_slcr_compute_pll(ps_clk, s->regs[R_ARM_PLL_CTRL]); + uint64_t ddr_pll = zynq_slcr_compute_pll(ps_clk, s->regs[R_DDR_PLL_CTRL]); + + uint64_t uart_mux[4] = {io_pll, io_pll, arm_pll, ddr_pll}; + + /* compute uartX reference clocks */ + clock_set(s->uart0_ref_clk, + ZYNQ_COMPUTE_CLK(s, uart_mux, R_UART_CLK_CTRL, CLKACT0)); + clock_set(s->uart1_ref_clk, + ZYNQ_COMPUTE_CLK(s, uart_mux, R_UART_CLK_CTRL, CLKACT1)); +} + +/** + * Propagate the outputs clocks. + * zynq_slcr_compute_clocks() should have been called before + * to configure them. + */ +static void zynq_slcr_propagate_clocks(ZynqSLCRState *s) { - ZynqSLCRState *s = ZYNQ_SLCR(d); + clock_propagate(s->uart0_ref_clk); + clock_propagate(s->uart1_ref_clk); +} + +static void zynq_slcr_ps_clk_callback(void *opaque) +{ + ZynqSLCRState *s = (ZynqSLCRState *) opaque; + zynq_slcr_compute_clocks(s); + zynq_slcr_propagate_clocks(s); +} + +static void zynq_slcr_reset_init(Object *obj, ResetType type) +{ + ZynqSLCRState *s = ZYNQ_SLCR(obj); int i; DB_PRINT("RESET\n"); @@ -277,6 +404,23 @@ static void zynq_slcr_reset(DeviceState *d) s->regs[R_DDRIOB + 12] = 0x00000021; } +static void zynq_slcr_reset_hold(Object *obj) +{ + ZynqSLCRState *s = ZYNQ_SLCR(obj); + + /* will disable all output clocks */ + zynq_slcr_compute_clocks(s); + zynq_slcr_propagate_clocks(s); +} + +static void zynq_slcr_reset_exit(Object *obj) +{ + ZynqSLCRState *s = ZYNQ_SLCR(obj); + + /* will compute output clocks according to ps_clk and registers */ + zynq_slcr_compute_clocks(s); + zynq_slcr_propagate_clocks(s); +} static bool zynq_slcr_check_offset(hwaddr offset, bool rnw) { @@ -409,6 +553,13 @@ static void zynq_slcr_write(void *opaque, hwaddr offset, qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); } break; + case R_IO_PLL_CTRL: + case R_ARM_PLL_CTRL: + case R_DDR_PLL_CTRL: + case R_UART_CLK_CTRL: + zynq_slcr_compute_clocks(s); + zynq_slcr_propagate_clocks(s); + break; } } @@ -418,6 +569,13 @@ static const MemoryRegionOps slcr_ops = { .endianness = DEVICE_NATIVE_ENDIAN, }; +static const ClockPortInitArray zynq_slcr_clocks = { + QDEV_CLOCK_IN(ZynqSLCRState, ps_clk, zynq_slcr_ps_clk_callback), + QDEV_CLOCK_OUT(ZynqSLCRState, uart0_ref_clk), + QDEV_CLOCK_OUT(ZynqSLCRState, uart1_ref_clk), + QDEV_CLOCK_END +}; + static void zynq_slcr_init(Object *obj) { ZynqSLCRState *s = ZYNQ_SLCR(obj); @@ -425,14 +583,17 @@ static void zynq_slcr_init(Object *obj) memory_region_init_io(&s->iomem, obj, &slcr_ops, s, "slcr", ZYNQ_SLCR_MMIO_SIZE); sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem); + + qdev_init_clocks(DEVICE(obj), zynq_slcr_clocks); } static const VMStateDescription vmstate_zynq_slcr = { .name = "zynq_slcr", - .version_id = 2, + .version_id = 3, .minimum_version_id = 2, .fields = (VMStateField[]) { VMSTATE_UINT32_ARRAY(regs, ZynqSLCRState, ZYNQ_SLCR_NUM_REGS), + VMSTATE_CLOCK_V(ps_clk, ZynqSLCRState, 3), VMSTATE_END_OF_LIST() } }; @@ -440,9 +601,12 @@ static const VMStateDescription vmstate_zynq_slcr = { static void zynq_slcr_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); dc->vmsd = &vmstate_zynq_slcr; - dc->reset = zynq_slcr_reset; + rc->phases.enter = zynq_slcr_reset_init; + rc->phases.hold = zynq_slcr_reset_hold; + rc->phases.exit = zynq_slcr_reset_exit; } static const TypeInfo zynq_slcr_info = { diff --git a/hw/net/Makefile.objs b/hw/net/Makefile.objs index af4d194..f2b7398 100644 --- a/hw/net/Makefile.objs +++ b/hw/net/Makefile.objs @@ -55,3 +55,4 @@ common-obj-$(CONFIG_ROCKER) += rocker/rocker.o rocker/rocker_fp.o \ obj-$(call lnot,$(CONFIG_ROCKER)) += rocker/qmp-norocker.o common-obj-$(CONFIG_CAN_BUS) += can/ +common-obj-$(CONFIG_MSF2) += msf2-emac.o diff --git a/hw/net/cadence_gem.c b/hw/net/cadence_gem.c index 51ec5a0..22a0b1b 100644 --- a/hw/net/cadence_gem.c +++ b/hw/net/cadence_gem.c @@ -411,6 +411,11 @@ static inline void rx_desc_set_sof(uint32_t *desc) desc[1] |= DESC_1_RX_SOF; } +static inline void rx_desc_clear_control(uint32_t *desc) +{ + desc[1] = 0; +} + static inline void rx_desc_set_eof(uint32_t *desc) { desc[1] |= DESC_1_RX_EOF; @@ -999,6 +1004,8 @@ static ssize_t gem_receive(NetClientState *nc, const uint8_t *buf, size_t size) rxbuf_ptr += MIN(bytes_to_copy, rxbufsize); bytes_to_copy -= MIN(bytes_to_copy, rxbufsize); + rx_desc_clear_control(s->rx_desc[q]); + /* Update the descriptor. */ if (first_desc) { rx_desc_set_sof(s->rx_desc[q]); @@ -1238,7 +1245,14 @@ static void gem_transmit(CadenceGEMState *s) /* read next descriptor */ if (tx_desc_get_wrap(desc)) { tx_desc_set_last(desc); - packet_desc_addr = s->regs[GEM_TXQBASE]; + + if (s->regs[GEM_DMACFG] & GEM_DMACFG_ADDR_64B) { + packet_desc_addr = s->regs[GEM_TBQPH]; + packet_desc_addr <<= 32; + } else { + packet_desc_addr = 0; + } + packet_desc_addr |= s->regs[GEM_TXQBASE]; } else { packet_desc_addr += 4 * gem_get_desc_len(s, false); } diff --git a/hw/net/msf2-emac.c b/hw/net/msf2-emac.c new file mode 100644 index 0000000..32ba9e8 --- /dev/null +++ b/hw/net/msf2-emac.c @@ -0,0 +1,589 @@ +/* + * QEMU model of the Smartfusion2 Ethernet MAC. + * + * Copyright (c) 2020 Subbaraya Sundeep <sundeep.lkml@gmail.com>. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Refer to section Ethernet MAC in the document: + * UG0331: SmartFusion2 Microcontroller Subsystem User Guide + * Datasheet URL: + * https://www.microsemi.com/document-portal/cat_view/56661-internal-documents/ + * 56758-soc?lang=en&limit=20&limitstart=220 + */ + +#include "qemu/osdep.h" +#include "qemu-common.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "exec/address-spaces.h" +#include "hw/registerfields.h" +#include "hw/net/msf2-emac.h" +#include "hw/net/mii.h" +#include "hw/irq.h" +#include "hw/qdev-properties.h" +#include "migration/vmstate.h" + +REG32(CFG1, 0x0) + FIELD(CFG1, RESET, 31, 1) + FIELD(CFG1, RX_EN, 2, 1) + FIELD(CFG1, TX_EN, 0, 1) + FIELD(CFG1, LB_EN, 8, 1) +REG32(CFG2, 0x4) +REG32(IFG, 0x8) +REG32(HALF_DUPLEX, 0xc) +REG32(MAX_FRAME_LENGTH, 0x10) +REG32(MII_CMD, 0x24) + FIELD(MII_CMD, READ, 0, 1) +REG32(MII_ADDR, 0x28) + FIELD(MII_ADDR, REGADDR, 0, 5) + FIELD(MII_ADDR, PHYADDR, 8, 5) +REG32(MII_CTL, 0x2c) +REG32(MII_STS, 0x30) +REG32(STA1, 0x40) +REG32(STA2, 0x44) +REG32(FIFO_CFG0, 0x48) +REG32(FIFO_CFG4, 0x58) + FIELD(FIFO_CFG4, BCAST, 9, 1) + FIELD(FIFO_CFG4, MCAST, 8, 1) +REG32(FIFO_CFG5, 0x5C) + FIELD(FIFO_CFG5, BCAST, 9, 1) + FIELD(FIFO_CFG5, MCAST, 8, 1) +REG32(DMA_TX_CTL, 0x180) + FIELD(DMA_TX_CTL, EN, 0, 1) +REG32(DMA_TX_DESC, 0x184) +REG32(DMA_TX_STATUS, 0x188) + FIELD(DMA_TX_STATUS, PKTCNT, 16, 8) + FIELD(DMA_TX_STATUS, UNDERRUN, 1, 1) + FIELD(DMA_TX_STATUS, PKT_SENT, 0, 1) +REG32(DMA_RX_CTL, 0x18c) + FIELD(DMA_RX_CTL, EN, 0, 1) +REG32(DMA_RX_DESC, 0x190) +REG32(DMA_RX_STATUS, 0x194) + FIELD(DMA_RX_STATUS, PKTCNT, 16, 8) + FIELD(DMA_RX_STATUS, OVERFLOW, 2, 1) + FIELD(DMA_RX_STATUS, PKT_RCVD, 0, 1) +REG32(DMA_IRQ_MASK, 0x198) +REG32(DMA_IRQ, 0x19c) + +#define EMPTY_MASK (1 << 31) +#define PKT_SIZE 0x7FF +#define PHYADDR 0x1 +#define MAX_PKT_SIZE 2048 + +typedef struct { + uint32_t pktaddr; + uint32_t pktsize; + uint32_t next; +} EmacDesc; + +static uint32_t emac_get_isr(MSF2EmacState *s) +{ + uint32_t ier = s->regs[R_DMA_IRQ_MASK]; + uint32_t tx = s->regs[R_DMA_TX_STATUS] & 0xF; + uint32_t rx = s->regs[R_DMA_RX_STATUS] & 0xF; + uint32_t isr = (rx << 4) | tx; + + s->regs[R_DMA_IRQ] = ier & isr; + return s->regs[R_DMA_IRQ]; +} + +static void emac_update_irq(MSF2EmacState *s) +{ + bool intr = emac_get_isr(s); + + qemu_set_irq(s->irq, intr); +} + +static void emac_load_desc(MSF2EmacState *s, EmacDesc *d, hwaddr desc) +{ + address_space_read(&s->dma_as, desc, MEMTXATTRS_UNSPECIFIED, d, sizeof *d); + /* Convert from LE into host endianness. */ + d->pktaddr = le32_to_cpu(d->pktaddr); + d->pktsize = le32_to_cpu(d->pktsize); + d->next = le32_to_cpu(d->next); +} + +static void emac_store_desc(MSF2EmacState *s, EmacDesc *d, hwaddr desc) +{ + /* Convert from host endianness into LE. */ + d->pktaddr = cpu_to_le32(d->pktaddr); + d->pktsize = cpu_to_le32(d->pktsize); + d->next = cpu_to_le32(d->next); + + address_space_write(&s->dma_as, desc, MEMTXATTRS_UNSPECIFIED, d, sizeof *d); +} + +static void msf2_dma_tx(MSF2EmacState *s) +{ + NetClientState *nc = qemu_get_queue(s->nic); + hwaddr desc = s->regs[R_DMA_TX_DESC]; + uint8_t buf[MAX_PKT_SIZE]; + EmacDesc d; + int size; + uint8_t pktcnt; + uint32_t status; + + if (!(s->regs[R_CFG1] & R_CFG1_TX_EN_MASK)) { + return; + } + + while (1) { + emac_load_desc(s, &d, desc); + if (d.pktsize & EMPTY_MASK) { + break; + } + size = d.pktsize & PKT_SIZE; + address_space_read(&s->dma_as, d.pktaddr, MEMTXATTRS_UNSPECIFIED, + buf, size); + /* + * This is very basic way to send packets. Ideally there should be + * a FIFO and packets should be sent out from FIFO only when + * R_CFG1 bit 0 is set. + */ + if (s->regs[R_CFG1] & R_CFG1_LB_EN_MASK) { + nc->info->receive(nc, buf, size); + } else { + qemu_send_packet(nc, buf, size); + } + d.pktsize |= EMPTY_MASK; + emac_store_desc(s, &d, desc); + /* update sent packets count */ + status = s->regs[R_DMA_TX_STATUS]; + pktcnt = FIELD_EX32(status, DMA_TX_STATUS, PKTCNT); + pktcnt++; + s->regs[R_DMA_TX_STATUS] = FIELD_DP32(status, DMA_TX_STATUS, + PKTCNT, pktcnt); + s->regs[R_DMA_TX_STATUS] |= R_DMA_TX_STATUS_PKT_SENT_MASK; + desc = d.next; + } + s->regs[R_DMA_TX_STATUS] |= R_DMA_TX_STATUS_UNDERRUN_MASK; + s->regs[R_DMA_TX_CTL] &= ~R_DMA_TX_CTL_EN_MASK; +} + +static void msf2_phy_update_link(MSF2EmacState *s) +{ + /* Autonegotiation status mirrors link status. */ + if (qemu_get_queue(s->nic)->link_down) { + s->phy_regs[MII_BMSR] &= ~(MII_BMSR_AN_COMP | + MII_BMSR_LINK_ST); + } else { + s->phy_regs[MII_BMSR] |= (MII_BMSR_AN_COMP | + MII_BMSR_LINK_ST); + } +} + +static void msf2_phy_reset(MSF2EmacState *s) +{ + memset(&s->phy_regs[0], 0, sizeof(s->phy_regs)); + s->phy_regs[MII_BMCR] = 0x1140; + s->phy_regs[MII_BMSR] = 0x7968; + s->phy_regs[MII_PHYID1] = 0x0022; + s->phy_regs[MII_PHYID2] = 0x1550; + s->phy_regs[MII_ANAR] = 0x01E1; + s->phy_regs[MII_ANLPAR] = 0xCDE1; + + msf2_phy_update_link(s); +} + +static void write_to_phy(MSF2EmacState *s) +{ + uint8_t reg_addr = s->regs[R_MII_ADDR] & R_MII_ADDR_REGADDR_MASK; + uint8_t phy_addr = (s->regs[R_MII_ADDR] >> R_MII_ADDR_PHYADDR_SHIFT) & + R_MII_ADDR_REGADDR_MASK; + uint16_t data = s->regs[R_MII_CTL] & 0xFFFF; + + if (phy_addr != PHYADDR) { + return; + } + + switch (reg_addr) { + case MII_BMCR: + if (data & MII_BMCR_RESET) { + /* Phy reset */ + msf2_phy_reset(s); + data &= ~MII_BMCR_RESET; + } + if (data & MII_BMCR_AUTOEN) { + /* Complete autonegotiation immediately */ + data &= ~MII_BMCR_AUTOEN; + s->phy_regs[MII_BMSR] |= MII_BMSR_AN_COMP; + } + break; + } + + s->phy_regs[reg_addr] = data; +} + +static uint16_t read_from_phy(MSF2EmacState *s) +{ + uint8_t reg_addr = s->regs[R_MII_ADDR] & R_MII_ADDR_REGADDR_MASK; + uint8_t phy_addr = (s->regs[R_MII_ADDR] >> R_MII_ADDR_PHYADDR_SHIFT) & + R_MII_ADDR_REGADDR_MASK; + + if (phy_addr == PHYADDR) { + return s->phy_regs[reg_addr]; + } else { + return 0xFFFF; + } +} + +static void msf2_emac_do_reset(MSF2EmacState *s) +{ + memset(&s->regs[0], 0, sizeof(s->regs)); + s->regs[R_CFG1] = 0x80000000; + s->regs[R_CFG2] = 0x00007000; + s->regs[R_IFG] = 0x40605060; + s->regs[R_HALF_DUPLEX] = 0x00A1F037; + s->regs[R_MAX_FRAME_LENGTH] = 0x00000600; + s->regs[R_FIFO_CFG5] = 0X3FFFF; + + msf2_phy_reset(s); +} + +static uint64_t emac_read(void *opaque, hwaddr addr, unsigned int size) +{ + MSF2EmacState *s = opaque; + uint32_t r = 0; + + addr >>= 2; + + switch (addr) { + case R_DMA_IRQ: + r = emac_get_isr(s); + break; + default: + if (addr >= ARRAY_SIZE(s->regs)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, + addr * 4); + return r; + } + r = s->regs[addr]; + break; + } + return r; +} + +static void emac_write(void *opaque, hwaddr addr, uint64_t val64, + unsigned int size) +{ + MSF2EmacState *s = opaque; + uint32_t value = val64; + uint32_t enreqbits; + uint8_t pktcnt; + + addr >>= 2; + switch (addr) { + case R_DMA_TX_CTL: + s->regs[addr] = value; + if (value & R_DMA_TX_CTL_EN_MASK) { + msf2_dma_tx(s); + } + break; + case R_DMA_RX_CTL: + s->regs[addr] = value; + if (value & R_DMA_RX_CTL_EN_MASK) { + s->rx_desc = s->regs[R_DMA_RX_DESC]; + qemu_flush_queued_packets(qemu_get_queue(s->nic)); + } + break; + case R_CFG1: + s->regs[addr] = value; + if (value & R_CFG1_RESET_MASK) { + msf2_emac_do_reset(s); + } + break; + case R_FIFO_CFG0: + /* + * For our implementation, turning on modules is instantaneous, + * so the states requested via the *ENREQ bits appear in the + * *ENRPLY bits immediately. Also the reset bits to reset PE-MCXMAC + * module are not emulated here since it deals with start of frames, + * inter-packet gap and control frames. + */ + enreqbits = extract32(value, 8, 5); + s->regs[addr] = deposit32(value, 16, 5, enreqbits); + break; + case R_DMA_TX_DESC: + if (value & 0x3) { + qemu_log_mask(LOG_GUEST_ERROR, "Tx Descriptor address should be" + " 32 bit aligned\n"); + } + /* Ignore [1:0] bits */ + s->regs[addr] = value & ~3; + break; + case R_DMA_RX_DESC: + if (value & 0x3) { + qemu_log_mask(LOG_GUEST_ERROR, "Rx Descriptor address should be" + " 32 bit aligned\n"); + } + /* Ignore [1:0] bits */ + s->regs[addr] = value & ~3; + break; + case R_DMA_TX_STATUS: + if (value & R_DMA_TX_STATUS_UNDERRUN_MASK) { + s->regs[addr] &= ~R_DMA_TX_STATUS_UNDERRUN_MASK; + } + if (value & R_DMA_TX_STATUS_PKT_SENT_MASK) { + pktcnt = FIELD_EX32(s->regs[addr], DMA_TX_STATUS, PKTCNT); + pktcnt--; + s->regs[addr] = FIELD_DP32(s->regs[addr], DMA_TX_STATUS, + PKTCNT, pktcnt); + if (pktcnt == 0) { + s->regs[addr] &= ~R_DMA_TX_STATUS_PKT_SENT_MASK; + } + } + break; + case R_DMA_RX_STATUS: + if (value & R_DMA_RX_STATUS_OVERFLOW_MASK) { + s->regs[addr] &= ~R_DMA_RX_STATUS_OVERFLOW_MASK; + } + if (value & R_DMA_RX_STATUS_PKT_RCVD_MASK) { + pktcnt = FIELD_EX32(s->regs[addr], DMA_RX_STATUS, PKTCNT); + pktcnt--; + s->regs[addr] = FIELD_DP32(s->regs[addr], DMA_RX_STATUS, + PKTCNT, pktcnt); + if (pktcnt == 0) { + s->regs[addr] &= ~R_DMA_RX_STATUS_PKT_RCVD_MASK; + } + } + break; + case R_DMA_IRQ: + break; + case R_MII_CMD: + if (value & R_MII_CMD_READ_MASK) { + s->regs[R_MII_STS] = read_from_phy(s); + } + break; + case R_MII_CTL: + s->regs[addr] = value; + write_to_phy(s); + break; + case R_STA1: + s->regs[addr] = value; + /* + * R_STA1 [31:24] : octet 1 of mac address + * R_STA1 [23:16] : octet 2 of mac address + * R_STA1 [15:8] : octet 3 of mac address + * R_STA1 [7:0] : octet 4 of mac address + */ + stl_be_p(s->mac_addr, value); + break; + case R_STA2: + s->regs[addr] = value; + /* + * R_STA2 [31:24] : octet 5 of mac address + * R_STA2 [23:16] : octet 6 of mac address + */ + stw_be_p(s->mac_addr + 4, value >> 16); + break; + default: + if (addr >= ARRAY_SIZE(s->regs)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad offset 0x%" HWADDR_PRIx "\n", __func__, + addr * 4); + return; + } + s->regs[addr] = value; + break; + } + emac_update_irq(s); +} + +static const MemoryRegionOps emac_ops = { + .read = emac_read, + .write = emac_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4 + } +}; + +static bool emac_can_rx(NetClientState *nc) +{ + MSF2EmacState *s = qemu_get_nic_opaque(nc); + + return (s->regs[R_CFG1] & R_CFG1_RX_EN_MASK) && + (s->regs[R_DMA_RX_CTL] & R_DMA_RX_CTL_EN_MASK); +} + +static bool addr_filter_ok(MSF2EmacState *s, const uint8_t *buf) +{ + /* The broadcast MAC address: FF:FF:FF:FF:FF:FF */ + const uint8_t broadcast_addr[] = { 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF }; + bool bcast_en = true; + bool mcast_en = true; + + if (s->regs[R_FIFO_CFG5] & R_FIFO_CFG5_BCAST_MASK) { + bcast_en = true; /* Broadcast dont care for drop circuitry */ + } else if (s->regs[R_FIFO_CFG4] & R_FIFO_CFG4_BCAST_MASK) { + bcast_en = false; + } + + if (s->regs[R_FIFO_CFG5] & R_FIFO_CFG5_MCAST_MASK) { + mcast_en = true; /* Multicast dont care for drop circuitry */ + } else if (s->regs[R_FIFO_CFG4] & R_FIFO_CFG4_MCAST_MASK) { + mcast_en = false; + } + + if (!memcmp(buf, broadcast_addr, sizeof(broadcast_addr))) { + return bcast_en; + } + + if (buf[0] & 1) { + return mcast_en; + } + + return !memcmp(buf, s->mac_addr, sizeof(s->mac_addr)); +} + +static ssize_t emac_rx(NetClientState *nc, const uint8_t *buf, size_t size) +{ + MSF2EmacState *s = qemu_get_nic_opaque(nc); + EmacDesc d; + uint8_t pktcnt; + uint32_t status; + + if (size > (s->regs[R_MAX_FRAME_LENGTH] & 0xFFFF)) { + return size; + } + if (!addr_filter_ok(s, buf)) { + return size; + } + + emac_load_desc(s, &d, s->rx_desc); + + if (d.pktsize & EMPTY_MASK) { + address_space_write(&s->dma_as, d.pktaddr, MEMTXATTRS_UNSPECIFIED, + buf, size & PKT_SIZE); + d.pktsize = size & PKT_SIZE; + emac_store_desc(s, &d, s->rx_desc); + /* update received packets count */ + status = s->regs[R_DMA_RX_STATUS]; + pktcnt = FIELD_EX32(status, DMA_RX_STATUS, PKTCNT); + pktcnt++; + s->regs[R_DMA_RX_STATUS] = FIELD_DP32(status, DMA_RX_STATUS, + PKTCNT, pktcnt); + s->regs[R_DMA_RX_STATUS] |= R_DMA_RX_STATUS_PKT_RCVD_MASK; + s->rx_desc = d.next; + } else { + s->regs[R_DMA_RX_CTL] &= ~R_DMA_RX_CTL_EN_MASK; + s->regs[R_DMA_RX_STATUS] |= R_DMA_RX_STATUS_OVERFLOW_MASK; + } + emac_update_irq(s); + return size; +} + +static void msf2_emac_reset(DeviceState *dev) +{ + MSF2EmacState *s = MSS_EMAC(dev); + + msf2_emac_do_reset(s); +} + +static void emac_set_link(NetClientState *nc) +{ + MSF2EmacState *s = qemu_get_nic_opaque(nc); + + msf2_phy_update_link(s); +} + +static NetClientInfo net_msf2_emac_info = { + .type = NET_CLIENT_DRIVER_NIC, + .size = sizeof(NICState), + .can_receive = emac_can_rx, + .receive = emac_rx, + .link_status_changed = emac_set_link, +}; + +static void msf2_emac_realize(DeviceState *dev, Error **errp) +{ + MSF2EmacState *s = MSS_EMAC(dev); + + if (!s->dma_mr) { + error_setg(errp, "MSS_EMAC 'ahb-bus' link not set"); + return; + } + + address_space_init(&s->dma_as, s->dma_mr, "emac-ahb"); + + qemu_macaddr_default_if_unset(&s->conf.macaddr); + s->nic = qemu_new_nic(&net_msf2_emac_info, &s->conf, + object_get_typename(OBJECT(dev)), dev->id, s); + qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); +} + +static void msf2_emac_init(Object *obj) +{ + MSF2EmacState *s = MSS_EMAC(obj); + + sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq); + + memory_region_init_io(&s->mmio, obj, &emac_ops, s, + "msf2-emac", R_MAX * 4); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); +} + +static Property msf2_emac_properties[] = { + DEFINE_PROP_LINK("ahb-bus", MSF2EmacState, dma_mr, + TYPE_MEMORY_REGION, MemoryRegion *), + DEFINE_NIC_PROPERTIES(MSF2EmacState, conf), + DEFINE_PROP_END_OF_LIST(), +}; + +static const VMStateDescription vmstate_msf2_emac = { + .name = TYPE_MSS_EMAC, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8_ARRAY(mac_addr, MSF2EmacState, ETH_ALEN), + VMSTATE_UINT32(rx_desc, MSF2EmacState), + VMSTATE_UINT16_ARRAY(phy_regs, MSF2EmacState, PHY_MAX_REGS), + VMSTATE_UINT32_ARRAY(regs, MSF2EmacState, R_MAX), + VMSTATE_END_OF_LIST() + } +}; + +static void msf2_emac_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = msf2_emac_realize; + dc->reset = msf2_emac_reset; + dc->vmsd = &vmstate_msf2_emac; + device_class_set_props(dc, msf2_emac_properties); +} + +static const TypeInfo msf2_emac_info = { + .name = TYPE_MSS_EMAC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(MSF2EmacState), + .instance_init = msf2_emac_init, + .class_init = msf2_emac_class_init, +}; + +static void msf2_emac_register_types(void) +{ + type_register_static(&msf2_emac_info); +} + +type_init(msf2_emac_register_types) |