mirror of https://github.com/xemu-project/xemu.git
* hw/misc/a9scu: Do not allow invalid CPU count
* hw/misc/a9scu: Minor cleanups * hw/timer/armv7m_systick: assert that board code set system_clock_scale * decodetree: Improve identifier matching * target/arm: Clean up neon fp insn size field decode * target/arm: Remove KVM support for 32-bit Arm hosts * hw/arm/mps2: New board models mps2-an386, mps2-an500 * Deprecate Unicore32 port * Deprecate lm32 port * target/arm: Count PMU events when MDCR.SPME is set * hw/arm: versal-virt: Correct the tx/rx GEM clocks * New Nuvoton iBMC board models npcm750-evb, quanta-gsj -----BEGIN PGP SIGNATURE----- iQJNBAABCAA3FiEE4aXFk81BneKOgxXPPCUl7RQ2DN4FAl9fhe4ZHHBldGVyLm1h eWRlbGxAbGluYXJvLm9yZwAKCRA8JSXtFDYM3q+XD/47GZyfreMdEZecVVnpJIPx mUUy9IdaQddeB7WDqtB5rSRRSGUa2gtvu9bsRgLUjqVLwWHq++JeZabJYB0chCzn u0yAO3eMjeffTBooSJ318aBc2pENGoZPwg1JnMpZyL4W/JvXoPD5CUhqXZOmKBoz wSIS7nVp6U68G6kAICDOME9z7uXiTrIwA6tFoT87IQxy5BaWcK69e05vJO3coa/d GfmV1G13DNNRov9DjVVOFVY9Zwovo9rvZIKsD0+qr/cTWRAHxr7tq9XD9xyMwxsM AM3KqH9OiA3U5h771vkbch/NHTte65mYC7cGplpiq+l+4DP0txErPn3hwNX2CE2g nsYJbL+NKmbIh9Gj6xVaRgQZyzsl0KgsgS+ZOo9gRyIH8bjgdIsnphffYEnurFnA R1f8UHPxZwdJXkcS6RXNb3PYXUb4qcJDrsx1AGpccgQ9jyvBzWiGgOUaaqv6l/Ds btehspmxOl/FQd9s7YufhN/azLfsuKP5xxWdE9oYWbC2GcsmghBcyR1P00TcZQRH WERWzGUw31OQfXJhGjoBOMkw2PVIoGMVa1/BGb+wO7G8oRg9uFo7BVFz4F5YBGQB 5Ai5BmqcLlWtAQJNCHJZH5oqRtQIQN6A7poBk52ifSENlZAcocYPIKsxqf6HH9gQ CYhEsgk7IYP2YdnQyi5Lhg== =mWuB -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/pmaydell/tags/pull-target-arm-20200914-1' into staging * hw/misc/a9scu: Do not allow invalid CPU count * hw/misc/a9scu: Minor cleanups * hw/timer/armv7m_systick: assert that board code set system_clock_scale * decodetree: Improve identifier matching * target/arm: Clean up neon fp insn size field decode * target/arm: Remove KVM support for 32-bit Arm hosts * hw/arm/mps2: New board models mps2-an386, mps2-an500 * Deprecate Unicore32 port * Deprecate lm32 port * target/arm: Count PMU events when MDCR.SPME is set * hw/arm: versal-virt: Correct the tx/rx GEM clocks * New Nuvoton iBMC board models npcm750-evb, quanta-gsj # gpg: Signature made Mon 14 Sep 2020 16:02:06 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-20200914-1: (32 commits) tests/acceptance: console boot tests for quanta-gsj docs/system: Add Nuvoton machine documentation hw/arm/npcm7xx: add board setup stub for CPU and UART clocks hw/arm: Wire up BMC boot flash for npcm750-evb and quanta-gsj hw/ssi: NPCM7xx Flash Interface Unit device model hw/mem: Stubbed out NPCM7xx Memory Controller model hw/nvram: NPCM7xx OTP device model hw/arm: Load -bios image as a boot ROM for npcm7xx roms: Add virtual Boot ROM for NPCM7xx SoCs hw/arm: Add two NPCM7xx-based machines hw/arm: Add NPCM730 and NPCM750 SoC models hw/timer: Add NPCM7xx Timer device model hw/misc: Add NPCM7xx Clock Controller device model hw/misc: Add NPCM7xx System Global Control Registers device model hw/arm: versal-virt: Correct the tx/rx GEM clocks target/arm: Count PMU events when MDCR.SPME is set Deprecate lm32 port Deprecate Unicore32 port docs/system/arm/mps2.rst: Make board list consistent hw/arm/mps2: New board model mps2-an500 ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
2d2c73d0e3
|
@ -61,3 +61,6 @@
|
|||
[submodule "meson"]
|
||||
path = meson
|
||||
url = https://github.com/mesonbuild/meson/
|
||||
[submodule "roms/vbootrom"]
|
||||
path = roms/vbootrom
|
||||
url = https://github.com/google/vbootrom.git
|
||||
|
|
10
MAINTAINERS
10
MAINTAINERS
|
@ -750,6 +750,16 @@ S: Odd Fixes
|
|||
F: hw/arm/musicpal.c
|
||||
F: docs/system/arm/musicpal.rst
|
||||
|
||||
Nuvoton NPCM7xx
|
||||
M: Havard Skinnemoen <hskinnemoen@google.com>
|
||||
M: Tyrone Ting <kfting@nuvoton.com>
|
||||
L: qemu-arm@nongnu.org
|
||||
S: Supported
|
||||
F: hw/*/npcm7xx*
|
||||
F: include/hw/*/npcm7xx*
|
||||
F: pc-bios/npcm7xx_bootrom.bin
|
||||
F: roms/vbootrom
|
||||
|
||||
nSeries
|
||||
M: Andrzej Zaborowski <balrogg@gmail.com>
|
||||
M: Peter Maydell <peter.maydell@linaro.org>
|
||||
|
|
|
@ -234,7 +234,7 @@ supported_kvm_target() {
|
|||
test "$kvm" = "yes" || return 1
|
||||
glob "$1" "*-softmmu" || return 1
|
||||
case "${1%-softmmu}:$cpu" in
|
||||
arm:arm | aarch64:aarch64 | \
|
||||
aarch64:aarch64 | \
|
||||
i386:i386 | i386:x86_64 | i386:x32 | \
|
||||
x86_64:i386 | x86_64:x86_64 | x86_64:x32 | \
|
||||
mips:mips | mipsel:mips | mips64:mips | mips64el:mips | \
|
||||
|
|
|
@ -27,6 +27,7 @@ CONFIG_GUMSTIX=y
|
|||
CONFIG_SPITZ=y
|
||||
CONFIG_TOSA=y
|
||||
CONFIG_Z2=y
|
||||
CONFIG_NPCM7XX=y
|
||||
CONFIG_COLLIE=y
|
||||
CONFIG_ASPEED_SOC=y
|
||||
CONFIG_NETDUINO2=y
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Arm MPS2 boards (``mps2-an385``, ``mps2-an505``, ``mps2-an511``, ``mps2-an521``)
|
||||
================================================================================
|
||||
Arm MPS2 boards (``mps2-an385``, ``mps2-an386``, ``mps2-an500``, ``mps2-an505``, ``mps2-an511``, ``mps2-an521``)
|
||||
================================================================================================================
|
||||
|
||||
These board models all use Arm M-profile CPUs.
|
||||
|
||||
|
@ -11,17 +11,21 @@ as seen by the guest depend significantly on the FPGA image.
|
|||
QEMU models the following FPGA images:
|
||||
|
||||
``mps2-an385``
|
||||
Cortex-M3 as documented in ARM Application Note AN385
|
||||
``mps2-an511``
|
||||
Cortex-M3 'DesignStart' as documented in AN511
|
||||
Cortex-M3 as documented in Arm Application Note AN385
|
||||
``mps2-an386``
|
||||
Cortex-M4 as documented in Arm Application Note AN386
|
||||
``mps2-an500``
|
||||
Cortex-M7 as documented in Arm Application Note AN500
|
||||
``mps2-an505``
|
||||
Cortex-M33 as documented in ARM Application Note AN505
|
||||
Cortex-M33 as documented in Arm Application Note AN505
|
||||
``mps2-an511``
|
||||
Cortex-M3 'DesignStart' as documented in Arm Application Note AN511
|
||||
``mps2-an521``
|
||||
Dual Cortex-M33 as documented in Application Note AN521
|
||||
Dual Cortex-M33 as documented in Arm Application Note AN521
|
||||
|
||||
Differences between QEMU and real hardware:
|
||||
|
||||
- AN385 remapping of low 16K of memory to either ZBT SSRAM1 or to
|
||||
- AN385/AN386 remapping of low 16K of memory to either ZBT SSRAM1 or to
|
||||
block RAM is unimplemented (QEMU always maps this to ZBT SSRAM1, as
|
||||
if zbt_boot_ctrl is always zero)
|
||||
- QEMU provides a LAN9118 ethernet rather than LAN9220; the only guest
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
Nuvoton iBMC boards (``npcm750-evb``, ``quanta-gsj``)
|
||||
=====================================================
|
||||
|
||||
The `Nuvoton iBMC`_ chips (NPCM7xx) are a family of ARM-based SoCs that are
|
||||
designed to be used as Baseboard Management Controllers (BMCs) in various
|
||||
servers. They all feature one or two ARM Cortex A9 CPU cores, as well as an
|
||||
assortment of peripherals targeted for either Enterprise or Data Center /
|
||||
Hyperscale applications. The former is a superset of the latter, so NPCM750 has
|
||||
all the peripherals of NPCM730 and more.
|
||||
|
||||
.. _Nuvoton iBMC: https://www.nuvoton.com/products/cloud-computing/ibmc/
|
||||
|
||||
The NPCM750 SoC has two Cortex A9 cores and is targeted for the Enterprise
|
||||
segment. The following machines are based on this chip :
|
||||
|
||||
- ``npcm750-evb`` Nuvoton NPCM750 Evaluation board
|
||||
|
||||
The NPCM730 SoC has two Cortex A9 cores and is targeted for Data Center and
|
||||
Hyperscale applications. The following machines are based on this chip :
|
||||
|
||||
- ``quanta-gsj`` Quanta GSJ server BMC
|
||||
|
||||
There are also two more SoCs, NPCM710 and NPCM705, which are single-core
|
||||
variants of NPCM750 and NPCM730, respectively. These are currently not
|
||||
supported by QEMU.
|
||||
|
||||
Supported devices
|
||||
-----------------
|
||||
|
||||
* SMP (Dual Core Cortex-A9)
|
||||
* Cortex-A9MPCore built-in peripherals: SCU, GIC, Global Timer, Private Timer
|
||||
and Watchdog.
|
||||
* SRAM, ROM and DRAM mappings
|
||||
* System Global Control Registers (GCR)
|
||||
* Clock and reset controller (CLK)
|
||||
* Timer controller (TIM)
|
||||
* Serial ports (16550-based)
|
||||
* DDR4 memory controller (dummy interface indicating memory training is done)
|
||||
* OTP controllers (no protection features)
|
||||
* Flash Interface Unit (FIU; no protection features)
|
||||
|
||||
Missing devices
|
||||
---------------
|
||||
|
||||
* GPIO controller
|
||||
* LPC/eSPI host-to-BMC interface, including
|
||||
|
||||
* Keyboard and mouse controller interface (KBCI)
|
||||
* Keyboard Controller Style (KCS) channels
|
||||
* BIOS POST code FIFO
|
||||
* System Wake-up Control (SWC)
|
||||
* Shared memory (SHM)
|
||||
* eSPI slave interface
|
||||
|
||||
* Ethernet controllers (GMAC and EMC)
|
||||
* USB host (USBH)
|
||||
* USB device (USBD)
|
||||
* SMBus controller (SMBF)
|
||||
* Peripheral SPI controller (PSPI)
|
||||
* Analog to Digital Converter (ADC)
|
||||
* SD/MMC host
|
||||
* Random Number Generator (RNG)
|
||||
* PECI interface
|
||||
* Pulse Width Modulation (PWM)
|
||||
* Tachometer
|
||||
* PCI and PCIe root complex and bridges
|
||||
* VDM and MCTP support
|
||||
* Serial I/O expansion
|
||||
* LPC/eSPI host
|
||||
* Coprocessor
|
||||
* Graphics
|
||||
* Video capture
|
||||
* Encoding compression engine
|
||||
* Security features
|
||||
|
||||
Boot options
|
||||
------------
|
||||
|
||||
The Nuvoton machines can boot from an OpenBMC firmware image, or directly into
|
||||
a kernel using the ``-kernel`` option. OpenBMC images for `quanta-gsj` and
|
||||
possibly others can be downloaded from the OpenPOWER jenkins :
|
||||
|
||||
https://openpower.xyz/
|
||||
|
||||
The firmware image should be attached as an MTD drive. Example :
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ qemu-system-arm -machine quanta-gsj -nographic \
|
||||
-drive file=image-bmc,if=mtd,bus=0,unit=0,format=raw
|
||||
|
||||
The default root password for test images is usually ``0penBmc``.
|
|
@ -329,14 +329,6 @@ The ``compat`` property used to set backwards compatibility modes for
|
|||
the processor has been deprecated. The ``max-cpu-compat`` property of
|
||||
the ``pseries`` machine type should be used instead.
|
||||
|
||||
KVM guest support on 32-bit Arm hosts (since 5.0)
|
||||
'''''''''''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
The Linux kernel has dropped support for allowing 32-bit Arm systems
|
||||
to host KVM guests as of the 5.7 kernel. Accordingly, QEMU is deprecating
|
||||
its support for this configuration and will remove it in a future version.
|
||||
Running 32-bit guests on a 64-bit Arm host remains supported.
|
||||
|
||||
System emulator devices
|
||||
-----------------------
|
||||
|
||||
|
@ -416,6 +408,22 @@ The above, converted to the current supported format::
|
|||
linux-user mode CPUs
|
||||
--------------------
|
||||
|
||||
``lm32`` CPUs (since 5.2.0)
|
||||
'''''''''''''''''''''''''''
|
||||
|
||||
The ``lm32`` guest CPU support is deprecated and will be removed in
|
||||
a future version of QEMU. The only public user of this architecture
|
||||
was the milkymist project, which has been dead for years; there was
|
||||
never an upstream Linux port.
|
||||
|
||||
``unicore32`` CPUs (since 5.2.0)
|
||||
''''''''''''''''''''''''''''''''
|
||||
|
||||
The ``unicore32`` guest CPU support is deprecated and will be removed in
|
||||
a future version of QEMU. Support for this CPU was removed from the
|
||||
upstream Linux kernel, and there is no available upstream toolchain
|
||||
to build binaries for it.
|
||||
|
||||
``tilegx`` CPUs (since 5.1.0)
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
|
@ -543,6 +551,14 @@ should be used instead of the 1.09.1 version.
|
|||
System emulator CPUS
|
||||
--------------------
|
||||
|
||||
KVM guest support on 32-bit Arm hosts (removed in 5.2)
|
||||
''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
The Linux kernel has dropped support for allowing 32-bit Arm systems
|
||||
to host KVM guests as of the 5.7 kernel. Accordingly, QEMU is deprecating
|
||||
its support for this configuration and will remove it in a future version.
|
||||
Running 32-bit guests on a 64-bit Arm host remains supported.
|
||||
|
||||
RISC-V ISA Specific CPUs (removed in 5.1)
|
||||
'''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
|
|
|
@ -86,6 +86,7 @@ undocumented; you can get a complete list by running
|
|||
arm/musicpal
|
||||
arm/gumstix
|
||||
arm/nseries
|
||||
arm/nuvoton
|
||||
arm/orangepi
|
||||
arm/palm
|
||||
arm/xscale
|
||||
|
|
|
@ -355,6 +355,15 @@ config XLNX_VERSAL
|
|||
select VIRTIO_MMIO
|
||||
select UNIMP
|
||||
|
||||
config NPCM7XX
|
||||
bool
|
||||
select A9MPCORE
|
||||
select ARM_GIC
|
||||
select PL310 # cache controller
|
||||
select SERIAL
|
||||
select SSI
|
||||
select UNIMP
|
||||
|
||||
config FSL_IMX25
|
||||
bool
|
||||
select IMX
|
||||
|
|
|
@ -13,6 +13,7 @@ arm_ss.add(when: 'CONFIG_MICROBIT', if_true: files('microbit.c'))
|
|||
arm_ss.add(when: 'CONFIG_MUSICPAL', if_true: files('musicpal.c'))
|
||||
arm_ss.add(when: 'CONFIG_NETDUINO2', if_true: files('netduino2.c'))
|
||||
arm_ss.add(when: 'CONFIG_NETDUINOPLUS2', if_true: files('netduinoplus2.c'))
|
||||
arm_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx.c', 'npcm7xx_boards.c'))
|
||||
arm_ss.add(when: 'CONFIG_NSERIES', if_true: files('nseries.c'))
|
||||
arm_ss.add(when: 'CONFIG_SX1', if_true: files('omap_sx1.c'))
|
||||
arm_ss.add(when: 'CONFIG_CHEETAH', if_true: files('palm.c'))
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
* as seen by the guest depend significantly on the FPGA image.
|
||||
* We model the following FPGA images:
|
||||
* "mps2-an385" -- Cortex-M3 as documented in ARM Application Note AN385
|
||||
* "mps2-an386" -- Cortex-M4 as documented in ARM Application Note AN386
|
||||
* "mps2-an500" -- Cortex-M7 as documented in ARM Application Note AN500
|
||||
* "mps2-an511" -- Cortex-M3 'DesignStart' as documented in AN511
|
||||
*
|
||||
* Links to the TRM for the board itself and to the various Application
|
||||
|
@ -48,6 +50,8 @@
|
|||
|
||||
typedef enum MPS2FPGAType {
|
||||
FPGA_AN385,
|
||||
FPGA_AN386,
|
||||
FPGA_AN500,
|
||||
FPGA_AN511,
|
||||
} MPS2FPGAType;
|
||||
|
||||
|
@ -55,6 +59,9 @@ struct MPS2MachineClass {
|
|||
MachineClass parent;
|
||||
MPS2FPGAType fpga_type;
|
||||
uint32_t scc_id;
|
||||
bool has_block_ram;
|
||||
hwaddr ethernet_base;
|
||||
hwaddr psram_base;
|
||||
};
|
||||
typedef struct MPS2MachineClass MPS2MachineClass;
|
||||
|
||||
|
@ -82,6 +89,8 @@ typedef struct MPS2MachineState MPS2MachineState;
|
|||
|
||||
#define TYPE_MPS2_MACHINE "mps2"
|
||||
#define TYPE_MPS2_AN385_MACHINE MACHINE_TYPE_NAME("mps2-an385")
|
||||
#define TYPE_MPS2_AN386_MACHINE MACHINE_TYPE_NAME("mps2-an386")
|
||||
#define TYPE_MPS2_AN500_MACHINE MACHINE_TYPE_NAME("mps2-an500")
|
||||
#define TYPE_MPS2_AN511_MACHINE MACHINE_TYPE_NAME("mps2-an511")
|
||||
|
||||
DECLARE_OBJ_CHECKERS(MPS2MachineState, MPS2MachineClass,
|
||||
|
@ -139,13 +148,14 @@ static void mps2_common_init(MachineState *machine)
|
|||
* tradeoffs. For QEMU they're all just RAM, though. We arbitrarily
|
||||
* call the 16MB our "system memory", as it's the largest lump.
|
||||
*
|
||||
* Common to both boards:
|
||||
* 0x21000000..0x21ffffff : PSRAM (16MB)
|
||||
* AN385 only:
|
||||
* AN385/AN386/AN511:
|
||||
* 0x21000000 .. 0x21ffffff : PSRAM (16MB)
|
||||
* AN385/AN386/AN500:
|
||||
* 0x00000000 .. 0x003fffff : ZBT SSRAM1
|
||||
* 0x00400000 .. 0x007fffff : mirror of ZBT SSRAM1
|
||||
* 0x20000000 .. 0x203fffff : ZBT SSRAM 2&3
|
||||
* 0x20400000 .. 0x207fffff : mirror of ZBT SSRAM 2&3
|
||||
* AN385/AN386 only:
|
||||
* 0x01000000 .. 0x01003fff : block RAM (16K)
|
||||
* 0x01004000 .. 0x01007fff : mirror of above
|
||||
* 0x01008000 .. 0x0100bfff : mirror of above
|
||||
|
@ -155,21 +165,17 @@ static void mps2_common_init(MachineState *machine)
|
|||
* 0x00400000 .. 0x007fffff : ZBT SSRAM1
|
||||
* 0x20000000 .. 0x2001ffff : SRAM
|
||||
* 0x20400000 .. 0x207fffff : ZBT SSRAM 2&3
|
||||
* AN500 only:
|
||||
* 0x60000000 .. 0x60ffffff : PSRAM (16MB)
|
||||
*
|
||||
* The AN385 has a feature where the lowest 16K can be mapped
|
||||
* The AN385/AN386 has a feature where the lowest 16K can be mapped
|
||||
* either to the bottom of the ZBT SSRAM1 or to the block RAM.
|
||||
* This is of no use for QEMU so we don't implement it (as if
|
||||
* zbt_boot_ctrl is always zero).
|
||||
*/
|
||||
memory_region_add_subregion(system_memory, 0x21000000, machine->ram);
|
||||
memory_region_add_subregion(system_memory, mmc->psram_base, machine->ram);
|
||||
|
||||
switch (mmc->fpga_type) {
|
||||
case FPGA_AN385:
|
||||
make_ram(&mms->ssram1, "mps.ssram1", 0x0, 0x400000);
|
||||
make_ram_alias(&mms->ssram1_m, "mps.ssram1_m", &mms->ssram1, 0x400000);
|
||||
make_ram(&mms->ssram23, "mps.ssram23", 0x20000000, 0x400000);
|
||||
make_ram_alias(&mms->ssram23_m, "mps.ssram23_m",
|
||||
&mms->ssram23, 0x20400000);
|
||||
if (mmc->has_block_ram) {
|
||||
make_ram(&mms->blockram, "mps.blockram", 0x01000000, 0x4000);
|
||||
make_ram_alias(&mms->blockram_m1, "mps.blockram_m1",
|
||||
&mms->blockram, 0x01004000);
|
||||
|
@ -177,6 +183,17 @@ static void mps2_common_init(MachineState *machine)
|
|||
&mms->blockram, 0x01008000);
|
||||
make_ram_alias(&mms->blockram_m3, "mps.blockram_m3",
|
||||
&mms->blockram, 0x0100c000);
|
||||
}
|
||||
|
||||
switch (mmc->fpga_type) {
|
||||
case FPGA_AN385:
|
||||
case FPGA_AN386:
|
||||
case FPGA_AN500:
|
||||
make_ram(&mms->ssram1, "mps.ssram1", 0x0, 0x400000);
|
||||
make_ram_alias(&mms->ssram1_m, "mps.ssram1_m", &mms->ssram1, 0x400000);
|
||||
make_ram(&mms->ssram23, "mps.ssram23", 0x20000000, 0x400000);
|
||||
make_ram_alias(&mms->ssram23_m, "mps.ssram23_m",
|
||||
&mms->ssram23, 0x20400000);
|
||||
break;
|
||||
case FPGA_AN511:
|
||||
make_ram(&mms->blockram, "mps.blockram", 0x0, 0x40000);
|
||||
|
@ -192,6 +209,8 @@ static void mps2_common_init(MachineState *machine)
|
|||
armv7m = DEVICE(&mms->armv7m);
|
||||
switch (mmc->fpga_type) {
|
||||
case FPGA_AN385:
|
||||
case FPGA_AN386:
|
||||
case FPGA_AN500:
|
||||
qdev_prop_set_uint32(armv7m, "num-irq", 32);
|
||||
break;
|
||||
case FPGA_AN511:
|
||||
|
@ -228,6 +247,8 @@ static void mps2_common_init(MachineState *machine)
|
|||
|
||||
switch (mmc->fpga_type) {
|
||||
case FPGA_AN385:
|
||||
case FPGA_AN386:
|
||||
case FPGA_AN500:
|
||||
{
|
||||
/* The overflow IRQs for UARTs 0, 1 and 2 are ORed together.
|
||||
* Overflow for UARTs 4 and 5 doesn't trigger any interrupt.
|
||||
|
@ -377,9 +398,9 @@ static void mps2_common_init(MachineState *machine)
|
|||
/* In hardware this is a LAN9220; the LAN9118 is software compatible
|
||||
* except that it doesn't support the checksum-offload feature.
|
||||
*/
|
||||
lan9118_init(&nd_table[0], 0x40200000,
|
||||
lan9118_init(&nd_table[0], mmc->ethernet_base,
|
||||
qdev_get_gpio_in(armv7m,
|
||||
mmc->fpga_type == FPGA_AN385 ? 13 : 47));
|
||||
mmc->fpga_type == FPGA_AN511 ? 47 : 13));
|
||||
|
||||
system_clock_scale = NANOSECONDS_PER_SECOND / SYSCLK_FRQ;
|
||||
|
||||
|
@ -406,6 +427,37 @@ static void mps2_an385_class_init(ObjectClass *oc, void *data)
|
|||
mmc->fpga_type = FPGA_AN385;
|
||||
mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m3");
|
||||
mmc->scc_id = 0x41043850;
|
||||
mmc->psram_base = 0x21000000;
|
||||
mmc->ethernet_base = 0x40200000;
|
||||
mmc->has_block_ram = true;
|
||||
}
|
||||
|
||||
static void mps2_an386_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
MachineClass *mc = MACHINE_CLASS(oc);
|
||||
MPS2MachineClass *mmc = MPS2_MACHINE_CLASS(oc);
|
||||
|
||||
mc->desc = "ARM MPS2 with AN386 FPGA image for Cortex-M4";
|
||||
mmc->fpga_type = FPGA_AN386;
|
||||
mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m4");
|
||||
mmc->scc_id = 0x41043860;
|
||||
mmc->psram_base = 0x21000000;
|
||||
mmc->ethernet_base = 0x40200000;
|
||||
mmc->has_block_ram = true;
|
||||
}
|
||||
|
||||
static void mps2_an500_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
MachineClass *mc = MACHINE_CLASS(oc);
|
||||
MPS2MachineClass *mmc = MPS2_MACHINE_CLASS(oc);
|
||||
|
||||
mc->desc = "ARM MPS2 with AN500 FPGA image for Cortex-M7";
|
||||
mmc->fpga_type = FPGA_AN500;
|
||||
mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m7");
|
||||
mmc->scc_id = 0x41045000;
|
||||
mmc->psram_base = 0x60000000;
|
||||
mmc->ethernet_base = 0xa0000000;
|
||||
mmc->has_block_ram = false;
|
||||
}
|
||||
|
||||
static void mps2_an511_class_init(ObjectClass *oc, void *data)
|
||||
|
@ -417,6 +469,9 @@ static void mps2_an511_class_init(ObjectClass *oc, void *data)
|
|||
mmc->fpga_type = FPGA_AN511;
|
||||
mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m3");
|
||||
mmc->scc_id = 0x41045110;
|
||||
mmc->psram_base = 0x21000000;
|
||||
mmc->ethernet_base = 0x40200000;
|
||||
mmc->has_block_ram = false;
|
||||
}
|
||||
|
||||
static const TypeInfo mps2_info = {
|
||||
|
@ -434,6 +489,18 @@ static const TypeInfo mps2_an385_info = {
|
|||
.class_init = mps2_an385_class_init,
|
||||
};
|
||||
|
||||
static const TypeInfo mps2_an386_info = {
|
||||
.name = TYPE_MPS2_AN386_MACHINE,
|
||||
.parent = TYPE_MPS2_MACHINE,
|
||||
.class_init = mps2_an386_class_init,
|
||||
};
|
||||
|
||||
static const TypeInfo mps2_an500_info = {
|
||||
.name = TYPE_MPS2_AN500_MACHINE,
|
||||
.parent = TYPE_MPS2_MACHINE,
|
||||
.class_init = mps2_an500_class_init,
|
||||
};
|
||||
|
||||
static const TypeInfo mps2_an511_info = {
|
||||
.name = TYPE_MPS2_AN511_MACHINE,
|
||||
.parent = TYPE_MPS2_MACHINE,
|
||||
|
@ -444,6 +511,8 @@ static void mps2_machine_init(void)
|
|||
{
|
||||
type_register_static(&mps2_info);
|
||||
type_register_static(&mps2_an385_info);
|
||||
type_register_static(&mps2_an386_info);
|
||||
type_register_static(&mps2_an500_info);
|
||||
type_register_static(&mps2_an511_info);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,532 @@
|
|||
/*
|
||||
* Nuvoton NPCM7xx SoC family.
|
||||
*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "exec/address-spaces.h"
|
||||
#include "hw/arm/boot.h"
|
||||
#include "hw/arm/npcm7xx.h"
|
||||
#include "hw/char/serial.h"
|
||||
#include "hw/loader.h"
|
||||
#include "hw/misc/unimp.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/units.h"
|
||||
#include "sysemu/sysemu.h"
|
||||
|
||||
/*
|
||||
* This covers the whole MMIO space. We'll use this to catch any MMIO accesses
|
||||
* that aren't handled by any device.
|
||||
*/
|
||||
#define NPCM7XX_MMIO_BA (0x80000000)
|
||||
#define NPCM7XX_MMIO_SZ (0x7ffd0000)
|
||||
|
||||
/* OTP key storage and fuse strap array */
|
||||
#define NPCM7XX_OTP1_BA (0xf0189000)
|
||||
#define NPCM7XX_OTP2_BA (0xf018a000)
|
||||
|
||||
/* Core system modules. */
|
||||
#define NPCM7XX_L2C_BA (0xf03fc000)
|
||||
#define NPCM7XX_CPUP_BA (0xf03fe000)
|
||||
#define NPCM7XX_GCR_BA (0xf0800000)
|
||||
#define NPCM7XX_CLK_BA (0xf0801000)
|
||||
#define NPCM7XX_MC_BA (0xf0824000)
|
||||
|
||||
/* Internal AHB SRAM */
|
||||
#define NPCM7XX_RAM3_BA (0xc0008000)
|
||||
#define NPCM7XX_RAM3_SZ (4 * KiB)
|
||||
|
||||
/* Memory blocks at the end of the address space */
|
||||
#define NPCM7XX_RAM2_BA (0xfffd0000)
|
||||
#define NPCM7XX_RAM2_SZ (128 * KiB)
|
||||
#define NPCM7XX_ROM_BA (0xffff0000)
|
||||
#define NPCM7XX_ROM_SZ (64 * KiB)
|
||||
|
||||
/* Clock configuration values to be fixed up when bypassing bootloader */
|
||||
|
||||
/* Run PLL1 at 1600 MHz */
|
||||
#define NPCM7XX_PLLCON1_FIXUP_VAL (0x00402101)
|
||||
/* Run the CPU from PLL1 and UART from PLL2 */
|
||||
#define NPCM7XX_CLKSEL_FIXUP_VAL (0x004aaba9)
|
||||
|
||||
/*
|
||||
* Interrupt lines going into the GIC. This does not include internal Cortex-A9
|
||||
* interrupts.
|
||||
*/
|
||||
enum NPCM7xxInterrupt {
|
||||
NPCM7XX_UART0_IRQ = 2,
|
||||
NPCM7XX_UART1_IRQ,
|
||||
NPCM7XX_UART2_IRQ,
|
||||
NPCM7XX_UART3_IRQ,
|
||||
NPCM7XX_TIMER0_IRQ = 32, /* Timer Module 0 */
|
||||
NPCM7XX_TIMER1_IRQ,
|
||||
NPCM7XX_TIMER2_IRQ,
|
||||
NPCM7XX_TIMER3_IRQ,
|
||||
NPCM7XX_TIMER4_IRQ,
|
||||
NPCM7XX_TIMER5_IRQ, /* Timer Module 1 */
|
||||
NPCM7XX_TIMER6_IRQ,
|
||||
NPCM7XX_TIMER7_IRQ,
|
||||
NPCM7XX_TIMER8_IRQ,
|
||||
NPCM7XX_TIMER9_IRQ,
|
||||
NPCM7XX_TIMER10_IRQ, /* Timer Module 2 */
|
||||
NPCM7XX_TIMER11_IRQ,
|
||||
NPCM7XX_TIMER12_IRQ,
|
||||
NPCM7XX_TIMER13_IRQ,
|
||||
NPCM7XX_TIMER14_IRQ,
|
||||
};
|
||||
|
||||
/* Total number of GIC interrupts, including internal Cortex-A9 interrupts. */
|
||||
#define NPCM7XX_NUM_IRQ (160)
|
||||
|
||||
/* Register base address for each Timer Module */
|
||||
static const hwaddr npcm7xx_tim_addr[] = {
|
||||
0xf0008000,
|
||||
0xf0009000,
|
||||
0xf000a000,
|
||||
};
|
||||
|
||||
/* Register base address for each 16550 UART */
|
||||
static const hwaddr npcm7xx_uart_addr[] = {
|
||||
0xf0001000,
|
||||
0xf0002000,
|
||||
0xf0003000,
|
||||
0xf0004000,
|
||||
};
|
||||
|
||||
/* Direct memory-mapped access to SPI0 CS0-1. */
|
||||
static const hwaddr npcm7xx_fiu0_flash_addr[] = {
|
||||
0x80000000, /* CS0 */
|
||||
0x88000000, /* CS1 */
|
||||
};
|
||||
|
||||
/* Direct memory-mapped access to SPI3 CS0-3. */
|
||||
static const hwaddr npcm7xx_fiu3_flash_addr[] = {
|
||||
0xa0000000, /* CS0 */
|
||||
0xa8000000, /* CS1 */
|
||||
0xb0000000, /* CS2 */
|
||||
0xb8000000, /* CS3 */
|
||||
};
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
hwaddr regs_addr;
|
||||
int cs_count;
|
||||
const hwaddr *flash_addr;
|
||||
} npcm7xx_fiu[] = {
|
||||
{
|
||||
.name = "fiu0",
|
||||
.regs_addr = 0xfb000000,
|
||||
.cs_count = ARRAY_SIZE(npcm7xx_fiu0_flash_addr),
|
||||
.flash_addr = npcm7xx_fiu0_flash_addr,
|
||||
}, {
|
||||
.name = "fiu3",
|
||||
.regs_addr = 0xc0000000,
|
||||
.cs_count = ARRAY_SIZE(npcm7xx_fiu3_flash_addr),
|
||||
.flash_addr = npcm7xx_fiu3_flash_addr,
|
||||
},
|
||||
};
|
||||
|
||||
static void npcm7xx_write_board_setup(ARMCPU *cpu,
|
||||
const struct arm_boot_info *info)
|
||||
{
|
||||
uint32_t board_setup[] = {
|
||||
0xe59f0010, /* ldr r0, clk_base_addr */
|
||||
0xe59f1010, /* ldr r1, pllcon1_value */
|
||||
0xe5801010, /* str r1, [r0, #16] */
|
||||
0xe59f100c, /* ldr r1, clksel_value */
|
||||
0xe5801004, /* str r1, [r0, #4] */
|
||||
0xe12fff1e, /* bx lr */
|
||||
NPCM7XX_CLK_BA,
|
||||
NPCM7XX_PLLCON1_FIXUP_VAL,
|
||||
NPCM7XX_CLKSEL_FIXUP_VAL,
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(board_setup); i++) {
|
||||
board_setup[i] = tswap32(board_setup[i]);
|
||||
}
|
||||
rom_add_blob_fixed("board-setup", board_setup, sizeof(board_setup),
|
||||
info->board_setup_addr);
|
||||
}
|
||||
|
||||
static void npcm7xx_write_secondary_boot(ARMCPU *cpu,
|
||||
const struct arm_boot_info *info)
|
||||
{
|
||||
/*
|
||||
* The default smpboot stub halts the secondary CPU with a 'wfi'
|
||||
* instruction, but the arch/arm/mach-npcm/platsmp.c in the Linux kernel
|
||||
* does not send an IPI to wake it up, so the second CPU fails to boot. So
|
||||
* we need to provide our own smpboot stub that can not use 'wfi', it has
|
||||
* to spin the secondary CPU until the first CPU writes to the SCRPAD reg.
|
||||
*/
|
||||
uint32_t smpboot[] = {
|
||||
0xe59f2018, /* ldr r2, bootreg_addr */
|
||||
0xe3a00000, /* mov r0, #0 */
|
||||
0xe5820000, /* str r0, [r2] */
|
||||
0xe320f002, /* wfe */
|
||||
0xe5921000, /* ldr r1, [r2] */
|
||||
0xe1110001, /* tst r1, r1 */
|
||||
0x0afffffb, /* beq <wfe> */
|
||||
0xe12fff11, /* bx r1 */
|
||||
NPCM7XX_SMP_BOOTREG_ADDR,
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(smpboot); i++) {
|
||||
smpboot[i] = tswap32(smpboot[i]);
|
||||
}
|
||||
|
||||
rom_add_blob_fixed("smpboot", smpboot, sizeof(smpboot),
|
||||
NPCM7XX_SMP_LOADER_START);
|
||||
}
|
||||
|
||||
static struct arm_boot_info npcm7xx_binfo = {
|
||||
.loader_start = NPCM7XX_LOADER_START,
|
||||
.smp_loader_start = NPCM7XX_SMP_LOADER_START,
|
||||
.smp_bootreg_addr = NPCM7XX_SMP_BOOTREG_ADDR,
|
||||
.gic_cpu_if_addr = NPCM7XX_GIC_CPU_IF_ADDR,
|
||||
.write_secondary_boot = npcm7xx_write_secondary_boot,
|
||||
.board_id = -1,
|
||||
.board_setup_addr = NPCM7XX_BOARD_SETUP_ADDR,
|
||||
.write_board_setup = npcm7xx_write_board_setup,
|
||||
};
|
||||
|
||||
void npcm7xx_load_kernel(MachineState *machine, NPCM7xxState *soc)
|
||||
{
|
||||
NPCM7xxClass *sc = NPCM7XX_GET_CLASS(soc);
|
||||
|
||||
npcm7xx_binfo.ram_size = machine->ram_size;
|
||||
npcm7xx_binfo.nb_cpus = sc->num_cpus;
|
||||
|
||||
arm_load_kernel(&soc->cpu[0], machine, &npcm7xx_binfo);
|
||||
}
|
||||
|
||||
static void npcm7xx_init_fuses(NPCM7xxState *s)
|
||||
{
|
||||
NPCM7xxClass *nc = NPCM7XX_GET_CLASS(s);
|
||||
uint32_t value;
|
||||
|
||||
/*
|
||||
* The initial mask of disabled modules indicates the chip derivative (e.g.
|
||||
* NPCM750 or NPCM730).
|
||||
*/
|
||||
value = tswap32(nc->disabled_modules);
|
||||
npcm7xx_otp_array_write(&s->fuse_array, &value, NPCM7XX_FUSE_DERIVATIVE,
|
||||
sizeof(value));
|
||||
}
|
||||
|
||||
static qemu_irq npcm7xx_irq(NPCM7xxState *s, int n)
|
||||
{
|
||||
return qdev_get_gpio_in(DEVICE(&s->a9mpcore), n);
|
||||
}
|
||||
|
||||
static void npcm7xx_init(Object *obj)
|
||||
{
|
||||
NPCM7xxState *s = NPCM7XX(obj);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NPCM7XX_MAX_NUM_CPUS; i++) {
|
||||
object_initialize_child(obj, "cpu[*]", &s->cpu[i],
|
||||
ARM_CPU_TYPE_NAME("cortex-a9"));
|
||||
}
|
||||
|
||||
object_initialize_child(obj, "a9mpcore", &s->a9mpcore, TYPE_A9MPCORE_PRIV);
|
||||
object_initialize_child(obj, "gcr", &s->gcr, TYPE_NPCM7XX_GCR);
|
||||
object_property_add_alias(obj, "power-on-straps", OBJECT(&s->gcr),
|
||||
"power-on-straps");
|
||||
object_initialize_child(obj, "clk", &s->clk, TYPE_NPCM7XX_CLK);
|
||||
object_initialize_child(obj, "otp1", &s->key_storage,
|
||||
TYPE_NPCM7XX_KEY_STORAGE);
|
||||
object_initialize_child(obj, "otp2", &s->fuse_array,
|
||||
TYPE_NPCM7XX_FUSE_ARRAY);
|
||||
object_initialize_child(obj, "mc", &s->mc, TYPE_NPCM7XX_MC);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(s->tim); i++) {
|
||||
object_initialize_child(obj, "tim[*]", &s->tim[i], TYPE_NPCM7XX_TIMER);
|
||||
}
|
||||
|
||||
QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_fiu) != ARRAY_SIZE(s->fiu));
|
||||
for (i = 0; i < ARRAY_SIZE(s->fiu); i++) {
|
||||
object_initialize_child(obj, npcm7xx_fiu[i].name, &s->fiu[i],
|
||||
TYPE_NPCM7XX_FIU);
|
||||
}
|
||||
}
|
||||
|
||||
static void npcm7xx_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
NPCM7xxState *s = NPCM7XX(dev);
|
||||
NPCM7xxClass *nc = NPCM7XX_GET_CLASS(s);
|
||||
int i;
|
||||
|
||||
if (memory_region_size(s->dram) > NPCM7XX_DRAM_SZ) {
|
||||
error_setg(errp, "%s: NPCM7xx cannot address more than %" PRIu64
|
||||
" MiB of DRAM", __func__, NPCM7XX_DRAM_SZ / MiB);
|
||||
return;
|
||||
}
|
||||
|
||||
/* CPUs */
|
||||
for (i = 0; i < nc->num_cpus; i++) {
|
||||
object_property_set_int(OBJECT(&s->cpu[i]), "mp-affinity",
|
||||
arm_cpu_mp_affinity(i, NPCM7XX_MAX_NUM_CPUS),
|
||||
&error_abort);
|
||||
object_property_set_int(OBJECT(&s->cpu[i]), "reset-cbar",
|
||||
NPCM7XX_GIC_CPU_IF_ADDR, &error_abort);
|
||||
object_property_set_bool(OBJECT(&s->cpu[i]), "reset-hivecs", true,
|
||||
&error_abort);
|
||||
|
||||
/* Disable security extensions. */
|
||||
object_property_set_bool(OBJECT(&s->cpu[i]), "has_el3", false,
|
||||
&error_abort);
|
||||
|
||||
if (!qdev_realize(DEVICE(&s->cpu[i]), NULL, errp)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* A9MPCORE peripherals. Can only fail if we pass bad parameters here. */
|
||||
object_property_set_int(OBJECT(&s->a9mpcore), "num-cpu", nc->num_cpus,
|
||||
&error_abort);
|
||||
object_property_set_int(OBJECT(&s->a9mpcore), "num-irq", NPCM7XX_NUM_IRQ,
|
||||
&error_abort);
|
||||
sysbus_realize(SYS_BUS_DEVICE(&s->a9mpcore), &error_abort);
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->a9mpcore), 0, NPCM7XX_CPUP_BA);
|
||||
|
||||
for (i = 0; i < nc->num_cpus; i++) {
|
||||
sysbus_connect_irq(SYS_BUS_DEVICE(&s->a9mpcore), i,
|
||||
qdev_get_gpio_in(DEVICE(&s->cpu[i]), ARM_CPU_IRQ));
|
||||
sysbus_connect_irq(SYS_BUS_DEVICE(&s->a9mpcore), i + nc->num_cpus,
|
||||
qdev_get_gpio_in(DEVICE(&s->cpu[i]), ARM_CPU_FIQ));
|
||||
}
|
||||
|
||||
/* L2 cache controller */
|
||||
sysbus_create_simple("l2x0", NPCM7XX_L2C_BA, NULL);
|
||||
|
||||
/* System Global Control Registers (GCR). Can fail due to user input. */
|
||||
object_property_set_int(OBJECT(&s->gcr), "disabled-modules",
|
||||
nc->disabled_modules, &error_abort);
|
||||
object_property_add_const_link(OBJECT(&s->gcr), "dram-mr", OBJECT(s->dram));
|
||||
if (!sysbus_realize(SYS_BUS_DEVICE(&s->gcr), errp)) {
|
||||
return;
|
||||
}
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->gcr), 0, NPCM7XX_GCR_BA);
|
||||
|
||||
/* Clock Control Registers (CLK). Cannot fail. */
|
||||
sysbus_realize(SYS_BUS_DEVICE(&s->clk), &error_abort);
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->clk), 0, NPCM7XX_CLK_BA);
|
||||
|
||||
/* OTP key storage and fuse strap array. Cannot fail. */
|
||||
sysbus_realize(SYS_BUS_DEVICE(&s->key_storage), &error_abort);
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->key_storage), 0, NPCM7XX_OTP1_BA);
|
||||
sysbus_realize(SYS_BUS_DEVICE(&s->fuse_array), &error_abort);
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->fuse_array), 0, NPCM7XX_OTP2_BA);
|
||||
npcm7xx_init_fuses(s);
|
||||
|
||||
/* Fake Memory Controller (MC). Cannot fail. */
|
||||
sysbus_realize(SYS_BUS_DEVICE(&s->mc), &error_abort);
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->mc), 0, NPCM7XX_MC_BA);
|
||||
|
||||
/* Timer Modules (TIM). Cannot fail. */
|
||||
QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_tim_addr) != ARRAY_SIZE(s->tim));
|
||||
for (i = 0; i < ARRAY_SIZE(s->tim); i++) {
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(&s->tim[i]);
|
||||
int first_irq;
|
||||
int j;
|
||||
|
||||
sysbus_realize(sbd, &error_abort);
|
||||
sysbus_mmio_map(sbd, 0, npcm7xx_tim_addr[i]);
|
||||
|
||||
first_irq = NPCM7XX_TIMER0_IRQ + i * NPCM7XX_TIMERS_PER_CTRL;
|
||||
for (j = 0; j < NPCM7XX_TIMERS_PER_CTRL; j++) {
|
||||
qemu_irq irq = npcm7xx_irq(s, first_irq + j);
|
||||
sysbus_connect_irq(sbd, j, irq);
|
||||
}
|
||||
}
|
||||
|
||||
/* UART0..3 (16550 compatible) */
|
||||
for (i = 0; i < ARRAY_SIZE(npcm7xx_uart_addr); i++) {
|
||||
serial_mm_init(get_system_memory(), npcm7xx_uart_addr[i], 2,
|
||||
npcm7xx_irq(s, NPCM7XX_UART0_IRQ + i), 115200,
|
||||
serial_hd(i), DEVICE_LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
/*
|
||||
* Flash Interface Unit (FIU). Can fail if incorrect number of chip selects
|
||||
* specified, but this is a programming error.
|
||||
*/
|
||||
QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_fiu) != ARRAY_SIZE(s->fiu));
|
||||
for (i = 0; i < ARRAY_SIZE(s->fiu); i++) {
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(&s->fiu[i]);
|
||||
int j;
|
||||
|
||||
object_property_set_int(OBJECT(sbd), "cs-count",
|
||||
npcm7xx_fiu[i].cs_count, &error_abort);
|
||||
sysbus_realize(sbd, &error_abort);
|
||||
|
||||
sysbus_mmio_map(sbd, 0, npcm7xx_fiu[i].regs_addr);
|
||||
for (j = 0; j < npcm7xx_fiu[i].cs_count; j++) {
|
||||
sysbus_mmio_map(sbd, j + 1, npcm7xx_fiu[i].flash_addr[j]);
|
||||
}
|
||||
}
|
||||
|
||||
/* RAM2 (SRAM) */
|
||||
memory_region_init_ram(&s->sram, OBJECT(dev), "ram2",
|
||||
NPCM7XX_RAM2_SZ, &error_abort);
|
||||
memory_region_add_subregion(get_system_memory(), NPCM7XX_RAM2_BA, &s->sram);
|
||||
|
||||
/* RAM3 (SRAM) */
|
||||
memory_region_init_ram(&s->ram3, OBJECT(dev), "ram3",
|
||||
NPCM7XX_RAM3_SZ, &error_abort);
|
||||
memory_region_add_subregion(get_system_memory(), NPCM7XX_RAM3_BA, &s->ram3);
|
||||
|
||||
/* Internal ROM */
|
||||
memory_region_init_rom(&s->irom, OBJECT(dev), "irom", NPCM7XX_ROM_SZ,
|
||||
&error_abort);
|
||||
memory_region_add_subregion(get_system_memory(), NPCM7XX_ROM_BA, &s->irom);
|
||||
|
||||
create_unimplemented_device("npcm7xx.shm", 0xc0001000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.vdmx", 0xe0800000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.pcierc", 0xe1000000, 64 * KiB);
|
||||
create_unimplemented_device("npcm7xx.kcs", 0xf0007000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.rng", 0xf000b000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.adc", 0xf000c000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.gfxi", 0xf000e000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.gpio[0]", 0xf0010000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.gpio[1]", 0xf0011000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.gpio[2]", 0xf0012000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.gpio[3]", 0xf0013000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.gpio[4]", 0xf0014000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.gpio[5]", 0xf0015000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.gpio[6]", 0xf0016000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.gpio[7]", 0xf0017000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.smbus[0]", 0xf0080000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.smbus[1]", 0xf0081000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.smbus[2]", 0xf0082000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.smbus[3]", 0xf0083000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.smbus[4]", 0xf0084000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.smbus[5]", 0xf0085000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.smbus[6]", 0xf0086000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.smbus[7]", 0xf0087000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.smbus[8]", 0xf0088000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.smbus[9]", 0xf0089000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.smbus[10]", 0xf008a000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.smbus[11]", 0xf008b000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.smbus[12]", 0xf008c000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.smbus[13]", 0xf008d000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.smbus[14]", 0xf008e000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.smbus[15]", 0xf008f000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.espi", 0xf009f000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.peci", 0xf0100000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.siox[1]", 0xf0101000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.siox[2]", 0xf0102000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.pwm[0]", 0xf0103000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.pwm[1]", 0xf0104000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.mft[0]", 0xf0180000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.mft[1]", 0xf0181000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.mft[2]", 0xf0182000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.mft[3]", 0xf0183000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.mft[4]", 0xf0184000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.mft[5]", 0xf0185000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.mft[6]", 0xf0186000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.mft[7]", 0xf0187000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.pspi1", 0xf0200000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.pspi2", 0xf0201000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.ahbpci", 0xf0400000, 1 * MiB);
|
||||
create_unimplemented_device("npcm7xx.mcphy", 0xf05f0000, 64 * KiB);
|
||||
create_unimplemented_device("npcm7xx.gmac1", 0xf0802000, 8 * KiB);
|
||||
create_unimplemented_device("npcm7xx.gmac2", 0xf0804000, 8 * KiB);
|
||||
create_unimplemented_device("npcm7xx.ehci", 0xf0806000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.ohci", 0xf0807000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.vcd", 0xf0810000, 64 * KiB);
|
||||
create_unimplemented_device("npcm7xx.ece", 0xf0820000, 8 * KiB);
|
||||
create_unimplemented_device("npcm7xx.vdma", 0xf0822000, 8 * KiB);
|
||||
create_unimplemented_device("npcm7xx.emc1", 0xf0825000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.emc2", 0xf0826000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.usbd[0]", 0xf0830000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.usbd[1]", 0xf0831000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.usbd[2]", 0xf0832000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.usbd[3]", 0xf0833000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.usbd[4]", 0xf0834000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.usbd[5]", 0xf0835000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.usbd[6]", 0xf0836000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.usbd[7]", 0xf0837000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.usbd[8]", 0xf0838000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.usbd[9]", 0xf0839000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.sd", 0xf0840000, 8 * KiB);
|
||||
create_unimplemented_device("npcm7xx.mmc", 0xf0842000, 8 * KiB);
|
||||
create_unimplemented_device("npcm7xx.pcimbx", 0xf0848000, 512 * KiB);
|
||||
create_unimplemented_device("npcm7xx.aes", 0xf0858000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.des", 0xf0859000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.sha", 0xf085a000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.secacc", 0xf085b000, 4 * KiB);
|
||||
create_unimplemented_device("npcm7xx.spixcs0", 0xf8000000, 16 * MiB);
|
||||
create_unimplemented_device("npcm7xx.spixcs1", 0xf9000000, 16 * MiB);
|
||||
create_unimplemented_device("npcm7xx.spix", 0xfb001000, 4 * KiB);
|
||||
}
|
||||
|
||||
static Property npcm7xx_properties[] = {
|
||||
DEFINE_PROP_LINK("dram-mr", NPCM7xxState, dram, TYPE_MEMORY_REGION,
|
||||
MemoryRegion *),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void npcm7xx_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(oc);
|
||||
|
||||
dc->realize = npcm7xx_realize;
|
||||
dc->user_creatable = false;
|
||||
device_class_set_props(dc, npcm7xx_properties);
|
||||
}
|
||||
|
||||
static void npcm730_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
NPCM7xxClass *nc = NPCM7XX_CLASS(oc);
|
||||
|
||||
/* NPCM730 is optimized for data center use, so no graphics, etc. */
|
||||
nc->disabled_modules = 0x00300395;
|
||||
nc->num_cpus = 2;
|
||||
}
|
||||
|
||||
static void npcm750_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
NPCM7xxClass *nc = NPCM7XX_CLASS(oc);
|
||||
|
||||
/* NPCM750 has 2 cores and a full set of peripherals */
|
||||
nc->disabled_modules = 0x00000000;
|
||||
nc->num_cpus = 2;
|
||||
}
|
||||
|
||||
static const TypeInfo npcm7xx_soc_types[] = {
|
||||
{
|
||||
.name = TYPE_NPCM7XX,
|
||||
.parent = TYPE_DEVICE,
|
||||
.instance_size = sizeof(NPCM7xxState),
|
||||
.instance_init = npcm7xx_init,
|
||||
.class_size = sizeof(NPCM7xxClass),
|
||||
.class_init = npcm7xx_class_init,
|
||||
.abstract = true,
|
||||
}, {
|
||||
.name = TYPE_NPCM730,
|
||||
.parent = TYPE_NPCM7XX,
|
||||
.class_init = npcm730_class_init,
|
||||
}, {
|
||||
.name = TYPE_NPCM750,
|
||||
.parent = TYPE_NPCM7XX,
|
||||
.class_init = npcm750_class_init,
|
||||
},
|
||||
};
|
||||
|
||||
DEFINE_TYPES(npcm7xx_soc_types);
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* Machine definitions for boards featuring an NPCM7xx SoC.
|
||||
*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "exec/address-spaces.h"
|
||||
#include "hw/arm/npcm7xx.h"
|
||||
#include "hw/core/cpu.h"
|
||||
#include "hw/loader.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu-common.h"
|
||||
#include "qemu/units.h"
|
||||
#include "sysemu/sysemu.h"
|
||||
|
||||
#define NPCM750_EVB_POWER_ON_STRAPS 0x00001ff7
|
||||
#define QUANTA_GSJ_POWER_ON_STRAPS 0x00001fff
|
||||
|
||||
static const char npcm7xx_default_bootrom[] = "npcm7xx_bootrom.bin";
|
||||
|
||||
static void npcm7xx_load_bootrom(MachineState *machine, NPCM7xxState *soc)
|
||||
{
|
||||
g_autofree char *filename = NULL;
|
||||
int ret;
|
||||
|
||||
if (!bios_name) {
|
||||
bios_name = npcm7xx_default_bootrom;
|
||||
}
|
||||
|
||||
filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
|
||||
if (!filename) {
|
||||
error_report("Could not find ROM image '%s'", bios_name);
|
||||
if (!machine->kernel_filename) {
|
||||
/* We can't boot without a bootrom or a kernel image. */
|
||||
exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
ret = load_image_mr(filename, &soc->irom);
|
||||
if (ret < 0) {
|
||||
error_report("Failed to load ROM image '%s'", filename);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void npcm7xx_connect_flash(NPCM7xxFIUState *fiu, int cs_no,
|
||||
const char *flash_type, DriveInfo *dinfo)
|
||||
{
|
||||
DeviceState *flash;
|
||||
qemu_irq flash_cs;
|
||||
|
||||
flash = qdev_new(flash_type);
|
||||
if (dinfo) {
|
||||
qdev_prop_set_drive(flash, "drive", blk_by_legacy_dinfo(dinfo));
|
||||
}
|
||||
qdev_realize_and_unref(flash, BUS(fiu->spi), &error_fatal);
|
||||
|
||||
flash_cs = qdev_get_gpio_in_named(flash, SSI_GPIO_CS, 0);
|
||||
qdev_connect_gpio_out_named(DEVICE(fiu), "cs", cs_no, flash_cs);
|
||||
}
|
||||
|
||||
static void npcm7xx_connect_dram(NPCM7xxState *soc, MemoryRegion *dram)
|
||||
{
|
||||
memory_region_add_subregion(get_system_memory(), NPCM7XX_DRAM_BA, dram);
|
||||
|
||||
object_property_set_link(OBJECT(soc), "dram-mr", OBJECT(dram),
|
||||
&error_abort);
|
||||
}
|
||||
|
||||
static NPCM7xxState *npcm7xx_create_soc(MachineState *machine,
|
||||
uint32_t hw_straps)
|
||||
{
|
||||
NPCM7xxMachineClass *nmc = NPCM7XX_MACHINE_GET_CLASS(machine);
|
||||
MachineClass *mc = &nmc->parent;
|
||||
Object *obj;
|
||||
|
||||
if (strcmp(machine->cpu_type, mc->default_cpu_type) != 0) {
|
||||
error_report("This board can only be used with %s",
|
||||
mc->default_cpu_type);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
obj = object_new_with_props(nmc->soc_type, OBJECT(machine), "soc",
|
||||
&error_abort, NULL);
|
||||
object_property_set_uint(obj, "power-on-straps", hw_straps, &error_abort);
|
||||
|
||||
return NPCM7XX(obj);
|
||||
}
|
||||
|
||||
static void npcm750_evb_init(MachineState *machine)
|
||||
{
|
||||
NPCM7xxState *soc;
|
||||
|
||||
soc = npcm7xx_create_soc(machine, NPCM750_EVB_POWER_ON_STRAPS);
|
||||
npcm7xx_connect_dram(soc, machine->ram);
|
||||
qdev_realize(DEVICE(soc), NULL, &error_fatal);
|
||||
|
||||
npcm7xx_load_bootrom(machine, soc);
|
||||
npcm7xx_connect_flash(&soc->fiu[0], 0, "w25q256", drive_get(IF_MTD, 0, 0));
|
||||
npcm7xx_load_kernel(machine, soc);
|
||||
}
|
||||
|
||||
static void quanta_gsj_init(MachineState *machine)
|
||||
{
|
||||
NPCM7xxState *soc;
|
||||
|
||||
soc = npcm7xx_create_soc(machine, QUANTA_GSJ_POWER_ON_STRAPS);
|
||||
npcm7xx_connect_dram(soc, machine->ram);
|
||||
qdev_realize(DEVICE(soc), NULL, &error_fatal);
|
||||
|
||||
npcm7xx_load_bootrom(machine, soc);
|
||||
npcm7xx_connect_flash(&soc->fiu[0], 0, "mx25l25635e",
|
||||
drive_get(IF_MTD, 0, 0));
|
||||
npcm7xx_load_kernel(machine, soc);
|
||||
}
|
||||
|
||||
static void npcm7xx_set_soc_type(NPCM7xxMachineClass *nmc, const char *type)
|
||||
{
|
||||
NPCM7xxClass *sc = NPCM7XX_CLASS(object_class_by_name(type));
|
||||
MachineClass *mc = MACHINE_CLASS(nmc);
|
||||
|
||||
nmc->soc_type = type;
|
||||
mc->default_cpus = mc->min_cpus = mc->max_cpus = sc->num_cpus;
|
||||
}
|
||||
|
||||
static void npcm7xx_machine_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
MachineClass *mc = MACHINE_CLASS(oc);
|
||||
|
||||
mc->no_floppy = 1;
|
||||
mc->no_cdrom = 1;
|
||||
mc->no_parallel = 1;
|
||||
mc->default_ram_id = "ram";
|
||||
mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-a9");
|
||||
}
|
||||
|
||||
/*
|
||||
* Schematics:
|
||||
* https://github.com/Nuvoton-Israel/nuvoton-info/blob/master/npcm7xx-poleg/evaluation-board/board_deliverables/NPCM750x_EB_ver.A1.1_COMPLETE.pdf
|
||||
*/
|
||||
static void npcm750_evb_machine_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
NPCM7xxMachineClass *nmc = NPCM7XX_MACHINE_CLASS(oc);
|
||||
MachineClass *mc = MACHINE_CLASS(oc);
|
||||
|
||||
npcm7xx_set_soc_type(nmc, TYPE_NPCM750);
|
||||
|
||||
mc->desc = "Nuvoton NPCM750 Evaluation Board (Cortex A9)";
|
||||
mc->init = npcm750_evb_init;
|
||||
mc->default_ram_size = 512 * MiB;
|
||||
};
|
||||
|
||||
static void gsj_machine_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
NPCM7xxMachineClass *nmc = NPCM7XX_MACHINE_CLASS(oc);
|
||||
MachineClass *mc = MACHINE_CLASS(oc);
|
||||
|
||||
npcm7xx_set_soc_type(nmc, TYPE_NPCM730);
|
||||
|
||||
mc->desc = "Quanta GSJ (Cortex A9)";
|
||||
mc->init = quanta_gsj_init;
|
||||
mc->default_ram_size = 512 * MiB;
|
||||
};
|
||||
|
||||
static const TypeInfo npcm7xx_machine_types[] = {
|
||||
{
|
||||
.name = TYPE_NPCM7XX_MACHINE,
|
||||
.parent = TYPE_MACHINE,
|
||||
.instance_size = sizeof(NPCM7xxMachine),
|
||||
.class_size = sizeof(NPCM7xxMachineClass),
|
||||
.class_init = npcm7xx_machine_class_init,
|
||||
.abstract = true,
|
||||
}, {
|
||||
.name = MACHINE_TYPE_NAME("npcm750-evb"),
|
||||
.parent = TYPE_NPCM7XX_MACHINE,
|
||||
.class_init = npcm750_evb_machine_class_init,
|
||||
}, {
|
||||
.name = MACHINE_TYPE_NAME("quanta-gsj"),
|
||||
.parent = TYPE_NPCM7XX_MACHINE,
|
||||
.class_init = gsj_machine_class_init,
|
||||
},
|
||||
};
|
||||
|
||||
DEFINE_TYPES(npcm7xx_machine_types)
|
|
@ -214,7 +214,7 @@ static void fdt_add_gem_nodes(VersalVirt *s)
|
|||
s->phandle.ethernet_phy[i]);
|
||||
qemu_fdt_setprop_cells(s->fdt, name, "clocks",
|
||||
s->phandle.clk_25Mhz, s->phandle.clk_25Mhz,
|
||||
s->phandle.clk_25Mhz, s->phandle.clk_25Mhz);
|
||||
s->phandle.clk_125Mhz, s->phandle.clk_125Mhz);
|
||||
qemu_fdt_setprop(s->fdt, name, "clock-names",
|
||||
clocknames, sizeof(clocknames));
|
||||
qemu_fdt_setprop_cells(s->fdt, name, "interrupts",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
mem_ss = ss.source_set()
|
||||
mem_ss.add(files('memory-device.c'))
|
||||
mem_ss.add(when: 'CONFIG_DIMM', if_true: files('pc-dimm.c'))
|
||||
mem_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_mc.c'))
|
||||
mem_ss.add(when: 'CONFIG_NVDIMM', if_true: files('nvdimm.c'))
|
||||
|
||||
softmmu_ss.add_all(when: 'CONFIG_MEM_DEVICE', if_true: mem_ss)
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Nuvoton NPCM7xx Memory Controller stub
|
||||
*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "hw/mem/npcm7xx_mc.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/units.h"
|
||||
|
||||
#define NPCM7XX_MC_REGS_SIZE (4 * KiB)
|
||||
|
||||
static uint64_t npcm7xx_mc_read(void *opaque, hwaddr addr, unsigned int size)
|
||||
{
|
||||
/*
|
||||
* If bits 8..11 @ offset 0 are not zero, the boot block thinks the memory
|
||||
* controller has already been initialized and will skip DDR training.
|
||||
*/
|
||||
if (addr == 0) {
|
||||
return 0x100;
|
||||
}
|
||||
|
||||
qemu_log_mask(LOG_UNIMP, "%s: mostly unimplemented\n", __func__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void npcm7xx_mc_write(void *opaque, hwaddr addr, uint64_t v,
|
||||
unsigned int size)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "%s: mostly unimplemented\n", __func__);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps npcm7xx_mc_ops = {
|
||||
.read = npcm7xx_mc_read,
|
||||
.write = npcm7xx_mc_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
.unaligned = false,
|
||||
},
|
||||
};
|
||||
|
||||
static void npcm7xx_mc_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
NPCM7xxMCState *s = NPCM7XX_MC(dev);
|
||||
|
||||
memory_region_init_io(&s->mmio, OBJECT(s), &npcm7xx_mc_ops, s, "regs",
|
||||
NPCM7XX_MC_REGS_SIZE);
|
||||
sysbus_init_mmio(&s->parent, &s->mmio);
|
||||
}
|
||||
|
||||
static void npcm7xx_mc_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->desc = "NPCM7xx Memory Controller stub";
|
||||
dc->realize = npcm7xx_mc_realize;
|
||||
}
|
||||
|
||||
static const TypeInfo npcm7xx_mc_types[] = {
|
||||
{
|
||||
.name = TYPE_NPCM7XX_MC,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(NPCM7xxMCState),
|
||||
.class_init = npcm7xx_mc_class_init,
|
||||
},
|
||||
};
|
||||
DEFINE_TYPES(npcm7xx_mc_types);
|
|
@ -12,8 +12,12 @@
|
|||
#include "hw/misc/a9scu.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/module.h"
|
||||
|
||||
#define A9_SCU_CPU_MAX 4
|
||||
|
||||
static uint64_t a9_scu_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
|
@ -25,12 +29,6 @@ static uint64_t a9_scu_read(void *opaque, hwaddr offset,
|
|||
return (((1 << s->num_cpu) - 1) << 4) | (s->num_cpu - 1);
|
||||
case 0x08: /* CPU Power Status */
|
||||
return s->status;
|
||||
case 0x09: /* CPU status. */
|
||||
return s->status >> 8;
|
||||
case 0x0a: /* CPU status. */
|
||||
return s->status >> 16;
|
||||
case 0x0b: /* CPU status. */
|
||||
return s->status >> 24;
|
||||
case 0x0c: /* Invalidate All Registers In Secure State */
|
||||
return 0;
|
||||
case 0x40: /* Filtering Start Address Register */
|
||||
|
@ -41,6 +39,8 @@ static uint64_t a9_scu_read(void *opaque, hwaddr offset,
|
|||
case 0x54: /* SCU Non-secure Access Control Register */
|
||||
/* unimplemented, fall through */
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP, "%s: Unsupported offset 0x%"HWADDR_PRIx"\n",
|
||||
__func__, offset);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -49,23 +49,6 @@ static void a9_scu_write(void *opaque, hwaddr offset,
|
|||
uint64_t value, unsigned size)
|
||||
{
|
||||
A9SCUState *s = (A9SCUState *)opaque;
|
||||
uint32_t mask;
|
||||
uint32_t shift;
|
||||
switch (size) {
|
||||
case 1:
|
||||
mask = 0xff;
|
||||
break;
|
||||
case 2:
|
||||
mask = 0xffff;
|
||||
break;
|
||||
case 4:
|
||||
mask = 0xffffffff;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Invalid size %u in write to a9 scu register %x\n",
|
||||
size, (unsigned)offset);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (offset) {
|
||||
case 0x00: /* Control */
|
||||
|
@ -74,9 +57,7 @@ static void a9_scu_write(void *opaque, hwaddr offset,
|
|||
case 0x4: /* Configuration: RO */
|
||||
break;
|
||||
case 0x08: case 0x09: case 0x0A: case 0x0B: /* Power Control */
|
||||
shift = (offset - 0x8) * 8;
|
||||
s->status &= ~(mask << shift);
|
||||
s->status |= ((value & mask) << shift);
|
||||
s->status = value;
|
||||
break;
|
||||
case 0x0c: /* Invalidate All Registers In Secure State */
|
||||
/* no-op as we do not implement caches */
|
||||
|
@ -89,6 +70,9 @@ static void a9_scu_write(void *opaque, hwaddr offset,
|
|||
case 0x54: /* SCU Non-secure Access Control Register */
|
||||
/* unimplemented, fall through */
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP, "%s: Unsupported offset 0x%"HWADDR_PRIx
|
||||
" value 0x%"PRIx64"\n",
|
||||
__func__, offset, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -96,6 +80,14 @@ static void a9_scu_write(void *opaque, hwaddr offset,
|
|||
static const MemoryRegionOps a9_scu_ops = {
|
||||
.read = a9_scu_read,
|
||||
.write = a9_scu_write,
|
||||
.impl = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.valid = {
|
||||
.min_access_size = 1,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
};
|
||||
|
||||
|
@ -105,12 +97,17 @@ static void a9_scu_reset(DeviceState *dev)
|
|||
s->control = 0;
|
||||
}
|
||||
|
||||
static void a9_scu_init(Object *obj)
|
||||
static void a9_scu_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
A9SCUState *s = A9_SCU(obj);
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
A9SCUState *s = A9_SCU(dev);
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &a9_scu_ops, s,
|
||||
if (!s->num_cpu || s->num_cpu > A9_SCU_CPU_MAX) {
|
||||
error_setg(errp, "Illegal CPU count: %u", s->num_cpu);
|
||||
return;
|
||||
}
|
||||
|
||||
memory_region_init_io(&s->iomem, OBJECT(s), &a9_scu_ops, s,
|
||||
"a9-scu", 0x100);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
}
|
||||
|
@ -138,13 +135,13 @@ static void a9_scu_class_init(ObjectClass *klass, void *data)
|
|||
device_class_set_props(dc, a9_scu_properties);
|
||||
dc->vmsd = &vmstate_a9_scu;
|
||||
dc->reset = a9_scu_reset;
|
||||
dc->realize = a9_scu_realize;
|
||||
}
|
||||
|
||||
static const TypeInfo a9_scu_info = {
|
||||
.name = TYPE_A9_SCU,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(A9SCUState),
|
||||
.instance_init = a9_scu_init,
|
||||
.class_init = a9_scu_class_init,
|
||||
};
|
||||
|
||||
|
|
|
@ -56,6 +56,10 @@ softmmu_ss.add(when: 'CONFIG_IMX', if_true: files(
|
|||
))
|
||||
softmmu_ss.add(when: 'CONFIG_MILKYMIST', if_true: files('milkymist-hpdmc.c', 'milkymist-pfpu.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_MAINSTONE', if_true: files('mst_fpga.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files(
|
||||
'npcm7xx_clk.c',
|
||||
'npcm7xx_gcr.c',
|
||||
))
|
||||
softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files(
|
||||
'omap_clk.c',
|
||||
'omap_gpmc.c',
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
* Nuvoton NPCM7xx Clock Control Registers.
|
||||
*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "hw/misc/npcm7xx_clk.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "qemu/units.h"
|
||||
#include "trace.h"
|
||||
|
||||
#define PLLCON_LOKI BIT(31)
|
||||
#define PLLCON_LOKS BIT(30)
|
||||
#define PLLCON_PWDEN BIT(12)
|
||||
|
||||
enum NPCM7xxCLKRegisters {
|
||||
NPCM7XX_CLK_CLKEN1,
|
||||
NPCM7XX_CLK_CLKSEL,
|
||||
NPCM7XX_CLK_CLKDIV1,
|
||||
NPCM7XX_CLK_PLLCON0,
|
||||
NPCM7XX_CLK_PLLCON1,
|
||||
NPCM7XX_CLK_SWRSTR,
|
||||
NPCM7XX_CLK_IPSRST1 = 0x20 / sizeof(uint32_t),
|
||||
NPCM7XX_CLK_IPSRST2,
|
||||
NPCM7XX_CLK_CLKEN2,
|
||||
NPCM7XX_CLK_CLKDIV2,
|
||||
NPCM7XX_CLK_CLKEN3,
|
||||
NPCM7XX_CLK_IPSRST3,
|
||||
NPCM7XX_CLK_WD0RCR,
|
||||
NPCM7XX_CLK_WD1RCR,
|
||||
NPCM7XX_CLK_WD2RCR,
|
||||
NPCM7XX_CLK_SWRSTC1,
|
||||
NPCM7XX_CLK_SWRSTC2,
|
||||
NPCM7XX_CLK_SWRSTC3,
|
||||
NPCM7XX_CLK_SWRSTC4,
|
||||
NPCM7XX_CLK_PLLCON2,
|
||||
NPCM7XX_CLK_CLKDIV3,
|
||||
NPCM7XX_CLK_CORSTC,
|
||||
NPCM7XX_CLK_PLLCONG,
|
||||
NPCM7XX_CLK_AHBCKFI,
|
||||
NPCM7XX_CLK_SECCNT,
|
||||
NPCM7XX_CLK_CNTR25M,
|
||||
NPCM7XX_CLK_REGS_END,
|
||||
};
|
||||
|
||||
/*
|
||||
* These reset values were taken from version 0.91 of the NPCM750R data sheet.
|
||||
*
|
||||
* All are loaded on power-up reset. CLKENx and SWRSTR should also be loaded on
|
||||
* core domain reset, but this reset type is not yet supported by QEMU.
|
||||
*/
|
||||
static const uint32_t cold_reset_values[NPCM7XX_CLK_NR_REGS] = {
|
||||
[NPCM7XX_CLK_CLKEN1] = 0xffffffff,
|
||||
[NPCM7XX_CLK_CLKSEL] = 0x004aaaaa,
|
||||
[NPCM7XX_CLK_CLKDIV1] = 0x5413f855,
|
||||
[NPCM7XX_CLK_PLLCON0] = 0x00222101 | PLLCON_LOKI,
|
||||
[NPCM7XX_CLK_PLLCON1] = 0x00202101 | PLLCON_LOKI,
|
||||
[NPCM7XX_CLK_IPSRST1] = 0x00001000,
|
||||
[NPCM7XX_CLK_IPSRST2] = 0x80000000,
|
||||
[NPCM7XX_CLK_CLKEN2] = 0xffffffff,
|
||||
[NPCM7XX_CLK_CLKDIV2] = 0xaa4f8f9f,
|
||||
[NPCM7XX_CLK_CLKEN3] = 0xffffffff,
|
||||
[NPCM7XX_CLK_IPSRST3] = 0x03000000,
|
||||
[NPCM7XX_CLK_WD0RCR] = 0xffffffff,
|
||||
[NPCM7XX_CLK_WD1RCR] = 0xffffffff,
|
||||
[NPCM7XX_CLK_WD2RCR] = 0xffffffff,
|
||||
[NPCM7XX_CLK_SWRSTC1] = 0x00000003,
|
||||
[NPCM7XX_CLK_PLLCON2] = 0x00c02105 | PLLCON_LOKI,
|
||||
[NPCM7XX_CLK_CORSTC] = 0x04000003,
|
||||
[NPCM7XX_CLK_PLLCONG] = 0x01228606 | PLLCON_LOKI,
|
||||
[NPCM7XX_CLK_AHBCKFI] = 0x000000c8,
|
||||
};
|
||||
|
||||
static uint64_t npcm7xx_clk_read(void *opaque, hwaddr offset, unsigned size)
|
||||
{
|
||||
uint32_t reg = offset / sizeof(uint32_t);
|
||||
NPCM7xxCLKState *s = opaque;
|
||||
int64_t now_ns;
|
||||
uint32_t value = 0;
|
||||
|
||||
if (reg >= NPCM7XX_CLK_NR_REGS) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: offset 0x%04" HWADDR_PRIx " out of range\n",
|
||||
__func__, offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (reg) {
|
||||
case NPCM7XX_CLK_SWRSTR:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: register @ 0x%04" HWADDR_PRIx " is write-only\n",
|
||||
__func__, offset);
|
||||
break;
|
||||
|
||||
case NPCM7XX_CLK_SECCNT:
|
||||
now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
value = (now_ns - s->ref_ns) / NANOSECONDS_PER_SECOND;
|
||||
break;
|
||||
|
||||
case NPCM7XX_CLK_CNTR25M:
|
||||
now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
/*
|
||||
* This register counts 25 MHz cycles, updating every 640 ns. It rolls
|
||||
* over to zero every second.
|
||||
*
|
||||
* The 4 LSBs are always zero: (1e9 / 640) << 4 = 25000000.
|
||||
*/
|
||||
value = (((now_ns - s->ref_ns) / 640) << 4) % NPCM7XX_TIMER_REF_HZ;
|
||||
break;
|
||||
|
||||
default:
|
||||
value = s->regs[reg];
|
||||
break;
|
||||
};
|
||||
|
||||
trace_npcm7xx_clk_read(offset, value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static void npcm7xx_clk_write(void *opaque, hwaddr offset,
|
||||
uint64_t v, unsigned size)
|
||||
{
|
||||
uint32_t reg = offset / sizeof(uint32_t);
|
||||
NPCM7xxCLKState *s = opaque;
|
||||
uint32_t value = v;
|
||||
|
||||
trace_npcm7xx_clk_write(offset, value);
|
||||
|
||||
if (reg >= NPCM7XX_CLK_NR_REGS) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: offset 0x%04" HWADDR_PRIx " out of range\n",
|
||||
__func__, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (reg) {
|
||||
case NPCM7XX_CLK_SWRSTR:
|
||||
qemu_log_mask(LOG_UNIMP, "%s: SW reset not implemented: 0x%02x\n",
|
||||
__func__, value);
|
||||
value = 0;
|
||||
break;
|
||||
|
||||
case NPCM7XX_CLK_PLLCON0:
|
||||
case NPCM7XX_CLK_PLLCON1:
|
||||
case NPCM7XX_CLK_PLLCON2:
|
||||
case NPCM7XX_CLK_PLLCONG:
|
||||
if (value & PLLCON_PWDEN) {
|
||||
/* Power down -- clear lock and indicate loss of lock */
|
||||
value &= ~PLLCON_LOKI;
|
||||
value |= PLLCON_LOKS;
|
||||
} else {
|
||||
/* Normal mode -- assume always locked */
|
||||
value |= PLLCON_LOKI;
|
||||
/* Keep LOKS unchanged unless cleared by writing 1 */
|
||||
if (value & PLLCON_LOKS) {
|
||||
value &= ~PLLCON_LOKS;
|
||||
} else {
|
||||
value |= (value & PLLCON_LOKS);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NPCM7XX_CLK_CNTR25M:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: register @ 0x%04" HWADDR_PRIx " is read-only\n",
|
||||
__func__, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
s->regs[reg] = value;
|
||||
}
|
||||
|
||||
static const struct MemoryRegionOps npcm7xx_clk_ops = {
|
||||
.read = npcm7xx_clk_read,
|
||||
.write = npcm7xx_clk_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
.unaligned = false,
|
||||
},
|
||||
};
|
||||
|
||||
static void npcm7xx_clk_enter_reset(Object *obj, ResetType type)
|
||||
{
|
||||
NPCM7xxCLKState *s = NPCM7XX_CLK(obj);
|
||||
|
||||
QEMU_BUILD_BUG_ON(sizeof(s->regs) != sizeof(cold_reset_values));
|
||||
|
||||
switch (type) {
|
||||
case RESET_TYPE_COLD:
|
||||
memcpy(s->regs, cold_reset_values, sizeof(cold_reset_values));
|
||||
s->ref_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* A small number of registers need to be reset on a core domain reset,
|
||||
* but no such reset type exists yet.
|
||||
*/
|
||||
qemu_log_mask(LOG_UNIMP, "%s: reset type %d not implemented.",
|
||||
__func__, type);
|
||||
}
|
||||
|
||||
static void npcm7xx_clk_init(Object *obj)
|
||||
{
|
||||
NPCM7xxCLKState *s = NPCM7XX_CLK(obj);
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &npcm7xx_clk_ops, s,
|
||||
TYPE_NPCM7XX_CLK, 4 * KiB);
|
||||
sysbus_init_mmio(&s->parent, &s->iomem);
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_npcm7xx_clk = {
|
||||
.name = "npcm7xx-clk",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32_ARRAY(regs, NPCM7xxCLKState, NPCM7XX_CLK_NR_REGS),
|
||||
VMSTATE_INT64(ref_ns, NPCM7xxCLKState),
|
||||
VMSTATE_END_OF_LIST(),
|
||||
},
|
||||
};
|
||||
|
||||
static void npcm7xx_clk_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
ResettableClass *rc = RESETTABLE_CLASS(klass);
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
QEMU_BUILD_BUG_ON(NPCM7XX_CLK_REGS_END > NPCM7XX_CLK_NR_REGS);
|
||||
|
||||
dc->desc = "NPCM7xx Clock Control Registers";
|
||||
dc->vmsd = &vmstate_npcm7xx_clk;
|
||||
rc->phases.enter = npcm7xx_clk_enter_reset;
|
||||
}
|
||||
|
||||
static const TypeInfo npcm7xx_clk_info = {
|
||||
.name = TYPE_NPCM7XX_CLK,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(NPCM7xxCLKState),
|
||||
.instance_init = npcm7xx_clk_init,
|
||||
.class_init = npcm7xx_clk_class_init,
|
||||
};
|
||||
|
||||
static void npcm7xx_clk_register_type(void)
|
||||
{
|
||||
type_register_static(&npcm7xx_clk_info);
|
||||
}
|
||||
type_init(npcm7xx_clk_register_type);
|
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
* Nuvoton NPCM7xx System Global Control Registers.
|
||||
*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "hw/misc/npcm7xx_gcr.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/units.h"
|
||||
|
||||
#include "trace.h"
|
||||
|
||||
#define NPCM7XX_GCR_MIN_DRAM_SIZE (128 * MiB)
|
||||
#define NPCM7XX_GCR_MAX_DRAM_SIZE (2 * GiB)
|
||||
|
||||
enum NPCM7xxGCRRegisters {
|
||||
NPCM7XX_GCR_PDID,
|
||||
NPCM7XX_GCR_PWRON,
|
||||
NPCM7XX_GCR_MFSEL1 = 0x0c / sizeof(uint32_t),
|
||||
NPCM7XX_GCR_MFSEL2,
|
||||
NPCM7XX_GCR_MISCPE,
|
||||
NPCM7XX_GCR_SPSWC = 0x038 / sizeof(uint32_t),
|
||||
NPCM7XX_GCR_INTCR,
|
||||
NPCM7XX_GCR_INTSR,
|
||||
NPCM7XX_GCR_HIFCR = 0x050 / sizeof(uint32_t),
|
||||
NPCM7XX_GCR_INTCR2 = 0x060 / sizeof(uint32_t),
|
||||
NPCM7XX_GCR_MFSEL3,
|
||||
NPCM7XX_GCR_SRCNT,
|
||||
NPCM7XX_GCR_RESSR,
|
||||
NPCM7XX_GCR_RLOCKR1,
|
||||
NPCM7XX_GCR_FLOCKR1,
|
||||
NPCM7XX_GCR_DSCNT,
|
||||
NPCM7XX_GCR_MDLR,
|
||||
NPCM7XX_GCR_SCRPAD3,
|
||||
NPCM7XX_GCR_SCRPAD2,
|
||||
NPCM7XX_GCR_DAVCLVLR = 0x098 / sizeof(uint32_t),
|
||||
NPCM7XX_GCR_INTCR3,
|
||||
NPCM7XX_GCR_VSINTR = 0x0ac / sizeof(uint32_t),
|
||||
NPCM7XX_GCR_MFSEL4,
|
||||
NPCM7XX_GCR_CPBPNTR = 0x0c4 / sizeof(uint32_t),
|
||||
NPCM7XX_GCR_CPCTL = 0x0d0 / sizeof(uint32_t),
|
||||
NPCM7XX_GCR_CP2BST,
|
||||
NPCM7XX_GCR_B2CPNT,
|
||||
NPCM7XX_GCR_CPPCTL,
|
||||
NPCM7XX_GCR_I2CSEGSEL,
|
||||
NPCM7XX_GCR_I2CSEGCTL,
|
||||
NPCM7XX_GCR_VSRCR,
|
||||
NPCM7XX_GCR_MLOCKR,
|
||||
NPCM7XX_GCR_SCRPAD = 0x013c / sizeof(uint32_t),
|
||||
NPCM7XX_GCR_USB1PHYCTL,
|
||||
NPCM7XX_GCR_USB2PHYCTL,
|
||||
NPCM7XX_GCR_REGS_END,
|
||||
};
|
||||
|
||||
static const uint32_t cold_reset_values[NPCM7XX_GCR_NR_REGS] = {
|
||||
[NPCM7XX_GCR_PDID] = 0x04a92750, /* Poleg A1 */
|
||||
[NPCM7XX_GCR_MISCPE] = 0x0000ffff,
|
||||
[NPCM7XX_GCR_SPSWC] = 0x00000003,
|
||||
[NPCM7XX_GCR_INTCR] = 0x0000035e,
|
||||
[NPCM7XX_GCR_HIFCR] = 0x0000004e,
|
||||
[NPCM7XX_GCR_INTCR2] = (1U << 19), /* DDR initialized */
|
||||
[NPCM7XX_GCR_RESSR] = 0x80000000,
|
||||
[NPCM7XX_GCR_DSCNT] = 0x000000c0,
|
||||
[NPCM7XX_GCR_DAVCLVLR] = 0x5a00f3cf,
|
||||
[NPCM7XX_GCR_SCRPAD] = 0x00000008,
|
||||
[NPCM7XX_GCR_USB1PHYCTL] = 0x034730e4,
|
||||
[NPCM7XX_GCR_USB2PHYCTL] = 0x034730e4,
|
||||
};
|
||||
|
||||
static uint64_t npcm7xx_gcr_read(void *opaque, hwaddr offset, unsigned size)
|
||||
{
|
||||
uint32_t reg = offset / sizeof(uint32_t);
|
||||
NPCM7xxGCRState *s = opaque;
|
||||
|
||||
if (reg >= NPCM7XX_GCR_NR_REGS) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: offset 0x%04" HWADDR_PRIx " out of range\n",
|
||||
__func__, offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
trace_npcm7xx_gcr_read(offset, s->regs[reg]);
|
||||
|
||||
return s->regs[reg];
|
||||
}
|
||||
|
||||
static void npcm7xx_gcr_write(void *opaque, hwaddr offset,
|
||||
uint64_t v, unsigned size)
|
||||
{
|
||||
uint32_t reg = offset / sizeof(uint32_t);
|
||||
NPCM7xxGCRState *s = opaque;
|
||||
uint32_t value = v;
|
||||
|
||||
trace_npcm7xx_gcr_write(offset, value);
|
||||
|
||||
if (reg >= NPCM7XX_GCR_NR_REGS) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: offset 0x%04" HWADDR_PRIx " out of range\n",
|
||||
__func__, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (reg) {
|
||||
case NPCM7XX_GCR_PDID:
|
||||
case NPCM7XX_GCR_PWRON:
|
||||
case NPCM7XX_GCR_INTSR:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: register @ 0x%04" HWADDR_PRIx " is read-only\n",
|
||||
__func__, offset);
|
||||
return;
|
||||
|
||||
case NPCM7XX_GCR_RESSR:
|
||||
case NPCM7XX_GCR_CP2BST:
|
||||
/* Write 1 to clear */
|
||||
value = s->regs[reg] & ~value;
|
||||
break;
|
||||
|
||||
case NPCM7XX_GCR_RLOCKR1:
|
||||
case NPCM7XX_GCR_MDLR:
|
||||
/* Write 1 to set */
|
||||
value |= s->regs[reg];
|
||||
break;
|
||||
};
|
||||
|
||||
s->regs[reg] = value;
|
||||
}
|
||||
|
||||
static const struct MemoryRegionOps npcm7xx_gcr_ops = {
|
||||
.read = npcm7xx_gcr_read,
|
||||
.write = npcm7xx_gcr_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
.unaligned = false,
|
||||
},
|
||||
};
|
||||
|
||||
static void npcm7xx_gcr_enter_reset(Object *obj, ResetType type)
|
||||
{
|
||||
NPCM7xxGCRState *s = NPCM7XX_GCR(obj);
|
||||
|
||||
QEMU_BUILD_BUG_ON(sizeof(s->regs) != sizeof(cold_reset_values));
|
||||
|
||||
switch (type) {
|
||||
case RESET_TYPE_COLD:
|
||||
memcpy(s->regs, cold_reset_values, sizeof(s->regs));
|
||||
s->regs[NPCM7XX_GCR_PWRON] = s->reset_pwron;
|
||||
s->regs[NPCM7XX_GCR_MDLR] = s->reset_mdlr;
|
||||
s->regs[NPCM7XX_GCR_INTCR3] = s->reset_intcr3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void npcm7xx_gcr_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
NPCM7xxGCRState *s = NPCM7XX_GCR(dev);
|
||||
uint64_t dram_size;
|
||||
Object *obj;
|
||||
|
||||
obj = object_property_get_link(OBJECT(dev), "dram-mr", errp);
|
||||
if (!obj) {
|
||||
error_prepend(errp, "%s: required dram-mr link not found: ", __func__);
|
||||
return;
|
||||
}
|
||||
dram_size = memory_region_size(MEMORY_REGION(obj));
|
||||
if (!is_power_of_2(dram_size) ||
|
||||
dram_size < NPCM7XX_GCR_MIN_DRAM_SIZE ||
|
||||
dram_size > NPCM7XX_GCR_MAX_DRAM_SIZE) {
|
||||
g_autofree char *sz = size_to_str(dram_size);
|
||||
g_autofree char *min_sz = size_to_str(NPCM7XX_GCR_MIN_DRAM_SIZE);
|
||||
g_autofree char *max_sz = size_to_str(NPCM7XX_GCR_MAX_DRAM_SIZE);
|
||||
error_setg(errp, "%s: unsupported DRAM size %s", __func__, sz);
|
||||
error_append_hint(errp,
|
||||
"DRAM size must be a power of two between %s and %s,"
|
||||
" inclusive.\n", min_sz, max_sz);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Power-on reset value */
|
||||
s->reset_intcr3 = 0x00001002;
|
||||
|
||||
/*
|
||||
* The GMMAP (Graphics Memory Map) field is used by u-boot to detect the
|
||||
* DRAM size, and is normally initialized by the boot block as part of DRAM
|
||||
* training. However, since we don't have a complete emulation of the
|
||||
* memory controller and try to make it look like it has already been
|
||||
* initialized, the boot block will skip this initialization, and we need
|
||||
* to make sure this field is set correctly up front.
|
||||
*
|
||||
* WARNING: some versions of u-boot only looks at bits 8 and 9, so 2 GiB of
|
||||
* DRAM will be interpreted as 128 MiB.
|
||||
*
|
||||
* https://github.com/Nuvoton-Israel/u-boot/blob/2aef993bd2aafeb5408dbaad0f3ce099ee40c4aa/board/nuvoton/poleg/poleg.c#L244
|
||||
*/
|
||||
s->reset_intcr3 |= ctz64(dram_size / NPCM7XX_GCR_MIN_DRAM_SIZE) << 8;
|
||||
}
|
||||
|
||||
static void npcm7xx_gcr_init(Object *obj)
|
||||
{
|
||||
NPCM7xxGCRState *s = NPCM7XX_GCR(obj);
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &npcm7xx_gcr_ops, s,
|
||||
TYPE_NPCM7XX_GCR, 4 * KiB);
|
||||
sysbus_init_mmio(&s->parent, &s->iomem);
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_npcm7xx_gcr = {
|
||||
.name = "npcm7xx-gcr",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32_ARRAY(regs, NPCM7xxGCRState, NPCM7XX_GCR_NR_REGS),
|
||||
VMSTATE_END_OF_LIST(),
|
||||
},
|
||||
};
|
||||
|
||||
static Property npcm7xx_gcr_properties[] = {
|
||||
DEFINE_PROP_UINT32("disabled-modules", NPCM7xxGCRState, reset_mdlr, 0),
|
||||
DEFINE_PROP_UINT32("power-on-straps", NPCM7xxGCRState, reset_pwron, 0),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void npcm7xx_gcr_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
ResettableClass *rc = RESETTABLE_CLASS(klass);
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
QEMU_BUILD_BUG_ON(NPCM7XX_GCR_REGS_END > NPCM7XX_GCR_NR_REGS);
|
||||
|
||||
dc->desc = "NPCM7xx System Global Control Registers";
|
||||
dc->realize = npcm7xx_gcr_realize;
|
||||
dc->vmsd = &vmstate_npcm7xx_gcr;
|
||||
rc->phases.enter = npcm7xx_gcr_enter_reset;
|
||||
|
||||
device_class_set_props(dc, npcm7xx_gcr_properties);
|
||||
}
|
||||
|
||||
static const TypeInfo npcm7xx_gcr_info = {
|
||||
.name = TYPE_NPCM7XX_GCR,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(NPCM7xxGCRState),
|
||||
.instance_init = npcm7xx_gcr_init,
|
||||
.class_init = npcm7xx_gcr_class_init,
|
||||
};
|
||||
|
||||
static void npcm7xx_gcr_register_type(void)
|
||||
{
|
||||
type_register_static(&npcm7xx_gcr_info);
|
||||
}
|
||||
type_init(npcm7xx_gcr_register_type);
|
|
@ -110,6 +110,14 @@ mos6522_set_sr_int(void) "set sr_int"
|
|||
mos6522_write(uint64_t addr, uint64_t val) "reg=0x%"PRIx64 " val=0x%"PRIx64
|
||||
mos6522_read(uint64_t addr, unsigned val) "reg=0x%"PRIx64 " val=0x%x"
|
||||
|
||||
# npcm7xx_clk.c
|
||||
npcm7xx_clk_read(uint64_t offset, uint32_t value) " offset: 0x%04" PRIx64 " value: 0x%08" PRIx32
|
||||
npcm7xx_clk_write(uint64_t offset, uint32_t value) "offset: 0x%04" PRIx64 " value: 0x%08" PRIx32
|
||||
|
||||
# npcm7xx_gcr.c
|
||||
npcm7xx_gcr_read(uint64_t offset, uint32_t value) " offset: 0x%04" PRIx64 " value: 0x%08" PRIx32
|
||||
npcm7xx_gcr_write(uint64_t offset, uint32_t value) "offset: 0x%04" PRIx64 " value: 0x%08" PRIx32
|
||||
|
||||
# stm32f4xx_syscfg.c
|
||||
stm32f4xx_syscfg_set_irq(int gpio, int line, int level) "Interupt: GPIO: %d, Line: %d; Level: %d"
|
||||
stm32f4xx_pulse_exti(int irq) "Pulse EXTI: %d"
|
||||
|
|
|
@ -4,6 +4,7 @@ softmmu_ss.add(when: 'CONFIG_DS1225Y', if_true: files('ds1225y.c'))
|
|||
softmmu_ss.add(when: 'CONFIG_NMC93XX_EEPROM', if_true: files('eeprom93xx.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_AT24C', if_true: files('eeprom_at24c.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_MAC_NVRAM', if_true: files('mac_nvram.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_otp.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('nrf51_nvm.c'))
|
||||
|
||||
specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr_nvram.c'))
|
||||
|
|
|
@ -0,0 +1,440 @@
|
|||
/*
|
||||
* Nuvoton NPCM7xx OTP (Fuse Array) Interface
|
||||
*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "hw/nvram/npcm7xx_otp.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/bitops.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/units.h"
|
||||
|
||||
/* Each module has 4 KiB of register space. Only a fraction of it is used. */
|
||||
#define NPCM7XX_OTP_REGS_SIZE (4 * KiB)
|
||||
|
||||
/* 32-bit register indices. */
|
||||
typedef enum NPCM7xxOTPRegister {
|
||||
NPCM7XX_OTP_FST,
|
||||
NPCM7XX_OTP_FADDR,
|
||||
NPCM7XX_OTP_FDATA,
|
||||
NPCM7XX_OTP_FCFG,
|
||||
/* Offset 0x10 is FKEYIND in OTP1, FUSTRAP in OTP2 */
|
||||
NPCM7XX_OTP_FKEYIND = 0x0010 / sizeof(uint32_t),
|
||||
NPCM7XX_OTP_FUSTRAP = 0x0010 / sizeof(uint32_t),
|
||||
NPCM7XX_OTP_FCTL,
|
||||
NPCM7XX_OTP_REGS_END,
|
||||
} NPCM7xxOTPRegister;
|
||||
|
||||
/* Register field definitions. */
|
||||
#define FST_RIEN BIT(2)
|
||||
#define FST_RDST BIT(1)
|
||||
#define FST_RDY BIT(0)
|
||||
#define FST_RO_MASK (FST_RDST | FST_RDY)
|
||||
|
||||
#define FADDR_BYTEADDR(rv) extract32((rv), 0, 10)
|
||||
#define FADDR_BITPOS(rv) extract32((rv), 10, 3)
|
||||
|
||||
#define FDATA_CLEAR 0x00000001
|
||||
|
||||
#define FCFG_FDIS BIT(31)
|
||||
#define FCFG_FCFGLK_MASK 0x00ff0000
|
||||
|
||||
#define FCTL_PROG_CMD1 0x00000001
|
||||
#define FCTL_PROG_CMD2 0xbf79e5d0
|
||||
#define FCTL_READ_CMD 0x00000002
|
||||
|
||||
/**
|
||||
* struct NPCM7xxOTPClass - OTP module class.
|
||||
* @parent: System bus device class.
|
||||
* @mmio_ops: MMIO register operations for this type of module.
|
||||
*
|
||||
* The two OTP modules (key-storage and fuse-array) have slightly different
|
||||
* behavior, so we give them different MMIO register operations.
|
||||
*/
|
||||
struct NPCM7xxOTPClass {
|
||||
SysBusDeviceClass parent;
|
||||
|
||||
const MemoryRegionOps *mmio_ops;
|
||||
};
|
||||
|
||||
#define NPCM7XX_OTP_CLASS(klass) \
|
||||
OBJECT_CLASS_CHECK(NPCM7xxOTPClass, (klass), TYPE_NPCM7XX_OTP)
|
||||
#define NPCM7XX_OTP_GET_CLASS(obj) \
|
||||
OBJECT_GET_CLASS(NPCM7xxOTPClass, (obj), TYPE_NPCM7XX_OTP)
|
||||
|
||||
static uint8_t ecc_encode_nibble(uint8_t n)
|
||||
{
|
||||
uint8_t result = n;
|
||||
|
||||
result |= (((n >> 0) & 1) ^ ((n >> 1) & 1)) << 4;
|
||||
result |= (((n >> 2) & 1) ^ ((n >> 3) & 1)) << 5;
|
||||
result |= (((n >> 0) & 1) ^ ((n >> 2) & 1)) << 6;
|
||||
result |= (((n >> 1) & 1) ^ ((n >> 3) & 1)) << 7;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void npcm7xx_otp_array_write(NPCM7xxOTPState *s, const void *data,
|
||||
unsigned int offset, unsigned int len)
|
||||
{
|
||||
const uint8_t *src = data;
|
||||
uint8_t *dst = &s->array[offset];
|
||||
|
||||
while (len-- > 0) {
|
||||
uint8_t c = *src++;
|
||||
|
||||
*dst++ = ecc_encode_nibble(extract8(c, 0, 4));
|
||||
*dst++ = ecc_encode_nibble(extract8(c, 4, 4));
|
||||
}
|
||||
}
|
||||
|
||||
/* Common register read handler for both OTP classes. */
|
||||
static uint64_t npcm7xx_otp_read(NPCM7xxOTPState *s, NPCM7xxOTPRegister reg)
|
||||
{
|
||||
uint32_t value = 0;
|
||||
|
||||
switch (reg) {
|
||||
case NPCM7XX_OTP_FST:
|
||||
case NPCM7XX_OTP_FADDR:
|
||||
case NPCM7XX_OTP_FDATA:
|
||||
case NPCM7XX_OTP_FCFG:
|
||||
value = s->regs[reg];
|
||||
break;
|
||||
|
||||
case NPCM7XX_OTP_FCTL:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: read from write-only FCTL register\n",
|
||||
DEVICE(s)->canonical_path);
|
||||
break;
|
||||
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: read from invalid offset 0x%zx\n",
|
||||
DEVICE(s)->canonical_path, reg * sizeof(uint32_t));
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Read a byte from the OTP array into the data register. */
|
||||
static void npcm7xx_otp_read_array(NPCM7xxOTPState *s)
|
||||
{
|
||||
uint32_t faddr = s->regs[NPCM7XX_OTP_FADDR];
|
||||
|
||||
s->regs[NPCM7XX_OTP_FDATA] = s->array[FADDR_BYTEADDR(faddr)];
|
||||
s->regs[NPCM7XX_OTP_FST] |= FST_RDST | FST_RDY;
|
||||
}
|
||||
|
||||
/* Program a byte from the data register into the OTP array. */
|
||||
static void npcm7xx_otp_program_array(NPCM7xxOTPState *s)
|
||||
{
|
||||
uint32_t faddr = s->regs[NPCM7XX_OTP_FADDR];
|
||||
|
||||
/* Bits can only go 0->1, never 1->0. */
|
||||
s->array[FADDR_BYTEADDR(faddr)] |= (1U << FADDR_BITPOS(faddr));
|
||||
s->regs[NPCM7XX_OTP_FST] |= FST_RDST | FST_RDY;
|
||||
}
|
||||
|
||||
/* Compute the next value of the FCFG register. */
|
||||
static uint32_t npcm7xx_otp_compute_fcfg(uint32_t cur_value, uint32_t new_value)
|
||||
{
|
||||
uint32_t lock_mask;
|
||||
uint32_t value;
|
||||
|
||||
/*
|
||||
* FCFGLK holds sticky bits 16..23, indicating which bits in FPRGLK (8..15)
|
||||
* and FRDLK (0..7) that are read-only.
|
||||
*/
|
||||
lock_mask = (cur_value & FCFG_FCFGLK_MASK) >> 8;
|
||||
lock_mask |= lock_mask >> 8;
|
||||
/* FDIS and FCFGLK bits are sticky (write 1 to set; can't clear). */
|
||||
value = cur_value & (FCFG_FDIS | FCFG_FCFGLK_MASK);
|
||||
/* Preserve read-only bits in FPRGLK and FRDLK */
|
||||
value |= cur_value & lock_mask;
|
||||
/* Set all bits that aren't read-only. */
|
||||
value |= new_value & ~lock_mask;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Common register write handler for both OTP classes. */
|
||||
static void npcm7xx_otp_write(NPCM7xxOTPState *s, NPCM7xxOTPRegister reg,
|
||||
uint32_t value)
|
||||
{
|
||||
switch (reg) {
|
||||
case NPCM7XX_OTP_FST:
|
||||
/* RDST is cleared by writing 1 to it. */
|
||||
if (value & FST_RDST) {
|
||||
s->regs[NPCM7XX_OTP_FST] &= ~FST_RDST;
|
||||
}
|
||||
/* Preserve read-only and write-one-to-clear bits */
|
||||
value &= ~FST_RO_MASK;
|
||||
value |= s->regs[NPCM7XX_OTP_FST] & FST_RO_MASK;
|
||||
break;
|
||||
|
||||
case NPCM7XX_OTP_FADDR:
|
||||
break;
|
||||
|
||||
case NPCM7XX_OTP_FDATA:
|
||||
/*
|
||||
* This register is cleared by writing a magic value to it; no other
|
||||
* values can be written.
|
||||
*/
|
||||
if (value == FDATA_CLEAR) {
|
||||
value = 0;
|
||||
} else {
|
||||
value = s->regs[NPCM7XX_OTP_FDATA];
|
||||
}
|
||||
break;
|
||||
|
||||
case NPCM7XX_OTP_FCFG:
|
||||
value = npcm7xx_otp_compute_fcfg(s->regs[NPCM7XX_OTP_FCFG], value);
|
||||
break;
|
||||
|
||||
case NPCM7XX_OTP_FCTL:
|
||||
switch (value) {
|
||||
case FCTL_READ_CMD:
|
||||
npcm7xx_otp_read_array(s);
|
||||
break;
|
||||
|
||||
case FCTL_PROG_CMD1:
|
||||
/*
|
||||
* Programming requires writing two separate magic values to this
|
||||
* register; this is the first one. Just store it so it can be
|
||||
* verified later when the second magic value is received.
|
||||
*/
|
||||
break;
|
||||
|
||||
case FCTL_PROG_CMD2:
|
||||
/*
|
||||
* Only initiate programming if we received the first half of the
|
||||
* command immediately before this one.
|
||||
*/
|
||||
if (s->regs[NPCM7XX_OTP_FCTL] == FCTL_PROG_CMD1) {
|
||||
npcm7xx_otp_program_array(s);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: unrecognized FCNTL value 0x%" PRIx32 "\n",
|
||||
DEVICE(s)->canonical_path, value);
|
||||
break;
|
||||
}
|
||||
if (value != FCTL_PROG_CMD1) {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: write to invalid offset 0x%zx\n",
|
||||
DEVICE(s)->canonical_path, reg * sizeof(uint32_t));
|
||||
return;
|
||||
}
|
||||
|
||||
s->regs[reg] = value;
|
||||
}
|
||||
|
||||
/* Register read handler specific to the fuse array OTP module. */
|
||||
static uint64_t npcm7xx_fuse_array_read(void *opaque, hwaddr addr,
|
||||
unsigned int size)
|
||||
{
|
||||
NPCM7xxOTPRegister reg = addr / sizeof(uint32_t);
|
||||
NPCM7xxOTPState *s = opaque;
|
||||
uint32_t value;
|
||||
|
||||
/*
|
||||
* Only the Fuse Strap register needs special handling; all other registers
|
||||
* work the same way for both kinds of OTP modules.
|
||||
*/
|
||||
if (reg != NPCM7XX_OTP_FUSTRAP) {
|
||||
value = npcm7xx_otp_read(s, reg);
|
||||
} else {
|
||||
/* FUSTRAP is stored as three copies in the OTP array. */
|
||||
uint32_t fustrap[3];
|
||||
|
||||
memcpy(fustrap, &s->array[0], sizeof(fustrap));
|
||||
|
||||
/* Determine value by a majority vote on each bit. */
|
||||
value = (fustrap[0] & fustrap[1]) | (fustrap[0] & fustrap[2]) |
|
||||
(fustrap[1] & fustrap[2]);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Register write handler specific to the fuse array OTP module. */
|
||||
static void npcm7xx_fuse_array_write(void *opaque, hwaddr addr, uint64_t v,
|
||||
unsigned int size)
|
||||
{
|
||||
NPCM7xxOTPRegister reg = addr / sizeof(uint32_t);
|
||||
NPCM7xxOTPState *s = opaque;
|
||||
|
||||
/*
|
||||
* The Fuse Strap register is read-only. Other registers are handled by
|
||||
* common code.
|
||||
*/
|
||||
if (reg != NPCM7XX_OTP_FUSTRAP) {
|
||||
npcm7xx_otp_write(s, reg, v);
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps npcm7xx_fuse_array_ops = {
|
||||
.read = npcm7xx_fuse_array_read,
|
||||
.write = npcm7xx_fuse_array_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
.unaligned = false,
|
||||
},
|
||||
};
|
||||
|
||||
/* Register read handler specific to the key storage OTP module. */
|
||||
static uint64_t npcm7xx_key_storage_read(void *opaque, hwaddr addr,
|
||||
unsigned int size)
|
||||
{
|
||||
NPCM7xxOTPRegister reg = addr / sizeof(uint32_t);
|
||||
NPCM7xxOTPState *s = opaque;
|
||||
|
||||
/*
|
||||
* Only the Fuse Key Index register needs special handling; all other
|
||||
* registers work the same way for both kinds of OTP modules.
|
||||
*/
|
||||
if (reg != NPCM7XX_OTP_FKEYIND) {
|
||||
return npcm7xx_otp_read(s, reg);
|
||||
}
|
||||
|
||||
qemu_log_mask(LOG_UNIMP, "%s: FKEYIND is not implemented\n", __func__);
|
||||
|
||||
return s->regs[NPCM7XX_OTP_FKEYIND];
|
||||
}
|
||||
|
||||
/* Register write handler specific to the key storage OTP module. */
|
||||
static void npcm7xx_key_storage_write(void *opaque, hwaddr addr, uint64_t v,
|
||||
unsigned int size)
|
||||
{
|
||||
NPCM7xxOTPRegister reg = addr / sizeof(uint32_t);
|
||||
NPCM7xxOTPState *s = opaque;
|
||||
|
||||
/*
|
||||
* Only the Fuse Key Index register needs special handling; all other
|
||||
* registers work the same way for both kinds of OTP modules.
|
||||
*/
|
||||
if (reg != NPCM7XX_OTP_FKEYIND) {
|
||||
npcm7xx_otp_write(s, reg, v);
|
||||
return;
|
||||
}
|
||||
|
||||
qemu_log_mask(LOG_UNIMP, "%s: FKEYIND is not implemented\n", __func__);
|
||||
|
||||
s->regs[NPCM7XX_OTP_FKEYIND] = v;
|
||||
}
|
||||
|
||||
static const MemoryRegionOps npcm7xx_key_storage_ops = {
|
||||
.read = npcm7xx_key_storage_read,
|
||||
.write = npcm7xx_key_storage_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
.unaligned = false,
|
||||
},
|
||||
};
|
||||
|
||||
static void npcm7xx_otp_enter_reset(Object *obj, ResetType type)
|
||||
{
|
||||
NPCM7xxOTPState *s = NPCM7XX_OTP(obj);
|
||||
|
||||
memset(s->regs, 0, sizeof(s->regs));
|
||||
|
||||
s->regs[NPCM7XX_OTP_FST] = 0x00000001;
|
||||
s->regs[NPCM7XX_OTP_FCFG] = 0x20000000;
|
||||
}
|
||||
|
||||
static void npcm7xx_otp_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
NPCM7xxOTPClass *oc = NPCM7XX_OTP_GET_CLASS(dev);
|
||||
NPCM7xxOTPState *s = NPCM7XX_OTP(dev);
|
||||
SysBusDevice *sbd = &s->parent;
|
||||
|
||||
memset(s->array, 0, sizeof(s->array));
|
||||
|
||||
memory_region_init_io(&s->mmio, OBJECT(s), oc->mmio_ops, s, "regs",
|
||||
NPCM7XX_OTP_REGS_SIZE);
|
||||
sysbus_init_mmio(sbd, &s->mmio);
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_npcm7xx_otp = {
|
||||
.name = "npcm7xx-otp",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32_ARRAY(regs, NPCM7xxOTPState, NPCM7XX_OTP_NR_REGS),
|
||||
VMSTATE_UINT8_ARRAY(array, NPCM7xxOTPState, NPCM7XX_OTP_ARRAY_BYTES),
|
||||
VMSTATE_END_OF_LIST(),
|
||||
},
|
||||
};
|
||||
|
||||
static void npcm7xx_otp_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
ResettableClass *rc = RESETTABLE_CLASS(klass);
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
QEMU_BUILD_BUG_ON(NPCM7XX_OTP_REGS_END > NPCM7XX_OTP_NR_REGS);
|
||||
|
||||
dc->realize = npcm7xx_otp_realize;
|
||||
dc->vmsd = &vmstate_npcm7xx_otp;
|
||||
rc->phases.enter = npcm7xx_otp_enter_reset;
|
||||
}
|
||||
|
||||
static void npcm7xx_key_storage_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
NPCM7xxOTPClass *oc = NPCM7XX_OTP_CLASS(klass);
|
||||
|
||||
oc->mmio_ops = &npcm7xx_key_storage_ops;
|
||||
}
|
||||
|
||||
static void npcm7xx_fuse_array_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
NPCM7xxOTPClass *oc = NPCM7XX_OTP_CLASS(klass);
|
||||
|
||||
oc->mmio_ops = &npcm7xx_fuse_array_ops;
|
||||
}
|
||||
|
||||
static const TypeInfo npcm7xx_otp_types[] = {
|
||||
{
|
||||
.name = TYPE_NPCM7XX_OTP,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(NPCM7xxOTPState),
|
||||
.class_size = sizeof(NPCM7xxOTPClass),
|
||||
.class_init = npcm7xx_otp_class_init,
|
||||
.abstract = true,
|
||||
},
|
||||
{
|
||||
.name = TYPE_NPCM7XX_KEY_STORAGE,
|
||||
.parent = TYPE_NPCM7XX_OTP,
|
||||
.class_init = npcm7xx_key_storage_class_init,
|
||||
},
|
||||
{
|
||||
.name = TYPE_NPCM7XX_FUSE_ARRAY,
|
||||
.parent = TYPE_NPCM7XX_OTP,
|
||||
.class_init = npcm7xx_fuse_array_class_init,
|
||||
},
|
||||
};
|
||||
DEFINE_TYPES(npcm7xx_otp_types);
|
|
@ -1,5 +1,6 @@
|
|||
softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_smc.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_MSF2', if_true: files('mss-spi.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_fiu.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_PL022', if_true: files('pl022.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_SSI', if_true: files('ssi.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_STM32F2XX_SPI', if_true: files('stm32f2xx_spi.c'))
|
||||
|
|
|
@ -0,0 +1,572 @@
|
|||
/*
|
||||
* Nuvoton NPCM7xx Flash Interface Unit (FIU)
|
||||
*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "hw/irq.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "hw/ssi/npcm7xx_fiu.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/units.h"
|
||||
|
||||
#include "trace.h"
|
||||
|
||||
/* Up to 128 MiB of flash may be accessed directly as memory. */
|
||||
#define NPCM7XX_FIU_FLASH_WINDOW_SIZE (128 * MiB)
|
||||
|
||||
/* Each module has 4 KiB of register space. Only a fraction of it is used. */
|
||||
#define NPCM7XX_FIU_CTRL_REGS_SIZE (4 * KiB)
|
||||
|
||||
/* 32-bit FIU register indices. */
|
||||
enum NPCM7xxFIURegister {
|
||||
NPCM7XX_FIU_DRD_CFG,
|
||||
NPCM7XX_FIU_DWR_CFG,
|
||||
NPCM7XX_FIU_UMA_CFG,
|
||||
NPCM7XX_FIU_UMA_CTS,
|
||||
NPCM7XX_FIU_UMA_CMD,
|
||||
NPCM7XX_FIU_UMA_ADDR,
|
||||
NPCM7XX_FIU_PRT_CFG,
|
||||
NPCM7XX_FIU_UMA_DW0 = 0x0020 / sizeof(uint32_t),
|
||||
NPCM7XX_FIU_UMA_DW1,
|
||||
NPCM7XX_FIU_UMA_DW2,
|
||||
NPCM7XX_FIU_UMA_DW3,
|
||||
NPCM7XX_FIU_UMA_DR0,
|
||||
NPCM7XX_FIU_UMA_DR1,
|
||||
NPCM7XX_FIU_UMA_DR2,
|
||||
NPCM7XX_FIU_UMA_DR3,
|
||||
NPCM7XX_FIU_PRT_CMD0,
|
||||
NPCM7XX_FIU_PRT_CMD1,
|
||||
NPCM7XX_FIU_PRT_CMD2,
|
||||
NPCM7XX_FIU_PRT_CMD3,
|
||||
NPCM7XX_FIU_PRT_CMD4,
|
||||
NPCM7XX_FIU_PRT_CMD5,
|
||||
NPCM7XX_FIU_PRT_CMD6,
|
||||
NPCM7XX_FIU_PRT_CMD7,
|
||||
NPCM7XX_FIU_PRT_CMD8,
|
||||
NPCM7XX_FIU_PRT_CMD9,
|
||||
NPCM7XX_FIU_CFG = 0x78 / sizeof(uint32_t),
|
||||
NPCM7XX_FIU_REGS_END,
|
||||
};
|
||||
|
||||
/* FIU_{DRD,DWR,UMA,PTR}_CFG cannot be written when this bit is set. */
|
||||
#define NPCM7XX_FIU_CFG_LCK BIT(31)
|
||||
|
||||
/* Direct Read configuration register fields. */
|
||||
#define FIU_DRD_CFG_ADDSIZ(rv) extract32(rv, 16, 2)
|
||||
#define FIU_ADDSIZ_3BYTES 0
|
||||
#define FIU_ADDSIZ_4BYTES 1
|
||||
#define FIU_DRD_CFG_DBW(rv) extract32(rv, 12, 2)
|
||||
#define FIU_DRD_CFG_ACCTYPE(rv) extract32(rv, 8, 2)
|
||||
#define FIU_DRD_CFG_RDCMD(rv) extract32(rv, 0, 8)
|
||||
|
||||
/* Direct Write configuration register fields. */
|
||||
#define FIU_DWR_CFG_ADDSIZ(rv) extract32(rv, 16, 2)
|
||||
#define FIU_DWR_CFG_WRCMD(rv) extract32(rv, 0, 8)
|
||||
|
||||
/* User-Mode Access register fields. */
|
||||
|
||||
/* Command Mode Lock and the bits protected by it. */
|
||||
#define FIU_UMA_CFG_CMMLCK BIT(30)
|
||||
#define FIU_UMA_CFG_CMMLCK_MASK 0x00000403
|
||||
|
||||
#define FIU_UMA_CFG_RDATSIZ(rv) extract32(rv, 24, 5)
|
||||
#define FIU_UMA_CFG_DBSIZ(rv) extract32(rv, 21, 3)
|
||||
#define FIU_UMA_CFG_WDATSIZ(rv) extract32(rv, 16, 5)
|
||||
#define FIU_UMA_CFG_ADDSIZ(rv) extract32(rv, 11, 3)
|
||||
#define FIU_UMA_CFG_CMDSIZ(rv) extract32(rv, 10, 1)
|
||||
#define FIU_UMA_CFG_DBPCK(rv) extract32(rv, 6, 2)
|
||||
|
||||
#define FIU_UMA_CTS_RDYIE BIT(25)
|
||||
#define FIU_UMA_CTS_RDYST BIT(24)
|
||||
#define FIU_UMA_CTS_SW_CS BIT(16)
|
||||
#define FIU_UMA_CTS_DEV_NUM(rv) extract32(rv, 8, 2)
|
||||
#define FIU_UMA_CTS_EXEC_DONE BIT(0)
|
||||
|
||||
/*
|
||||
* Returns the index of flash in the fiu->flash array. This corresponds to the
|
||||
* chip select ID of the flash.
|
||||
*/
|
||||
static int npcm7xx_fiu_cs_index(NPCM7xxFIUState *fiu, NPCM7xxFIUFlash *flash)
|
||||
{
|
||||
int index = flash - fiu->flash;
|
||||
|
||||
g_assert(index >= 0 && index < fiu->cs_count);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/* Assert the chip select specified in the UMA Control/Status Register. */
|
||||
static void npcm7xx_fiu_select(NPCM7xxFIUState *s, int cs_id)
|
||||
{
|
||||
trace_npcm7xx_fiu_select(DEVICE(s)->canonical_path, cs_id);
|
||||
|
||||
if (cs_id < s->cs_count) {
|
||||
qemu_irq_lower(s->cs_lines[cs_id]);
|
||||
} else {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: UMA to CS%d; this module has only %d chip selects",
|
||||
DEVICE(s)->canonical_path, cs_id, s->cs_count);
|
||||
cs_id = -1;
|
||||
}
|
||||
|
||||
s->active_cs = cs_id;
|
||||
}
|
||||
|
||||
/* Deassert the currently active chip select. */
|
||||
static void npcm7xx_fiu_deselect(NPCM7xxFIUState *s)
|
||||
{
|
||||
if (s->active_cs < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
trace_npcm7xx_fiu_deselect(DEVICE(s)->canonical_path, s->active_cs);
|
||||
|
||||
qemu_irq_raise(s->cs_lines[s->active_cs]);
|
||||
s->active_cs = -1;
|
||||
}
|
||||
|
||||
/* Direct flash memory read handler. */
|
||||
static uint64_t npcm7xx_fiu_flash_read(void *opaque, hwaddr addr,
|
||||
unsigned int size)
|
||||
{
|
||||
NPCM7xxFIUFlash *f = opaque;
|
||||
NPCM7xxFIUState *fiu = f->fiu;
|
||||
uint64_t value = 0;
|
||||
uint32_t drd_cfg;
|
||||
int dummy_cycles;
|
||||
int i;
|
||||
|
||||
if (fiu->active_cs != -1) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: direct flash read with CS%d already active",
|
||||
DEVICE(fiu)->canonical_path, fiu->active_cs);
|
||||
}
|
||||
|
||||
npcm7xx_fiu_select(fiu, npcm7xx_fiu_cs_index(fiu, f));
|
||||
|
||||
drd_cfg = fiu->regs[NPCM7XX_FIU_DRD_CFG];
|
||||
ssi_transfer(fiu->spi, FIU_DRD_CFG_RDCMD(drd_cfg));
|
||||
|
||||
switch (FIU_DRD_CFG_ADDSIZ(drd_cfg)) {
|
||||
case FIU_ADDSIZ_4BYTES:
|
||||
ssi_transfer(fiu->spi, extract32(addr, 24, 8));
|
||||
/* fall through */
|
||||
case FIU_ADDSIZ_3BYTES:
|
||||
ssi_transfer(fiu->spi, extract32(addr, 16, 8));
|
||||
ssi_transfer(fiu->spi, extract32(addr, 8, 8));
|
||||
ssi_transfer(fiu->spi, extract32(addr, 0, 8));
|
||||
break;
|
||||
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: bad address size %d\n",
|
||||
DEVICE(fiu)->canonical_path, FIU_DRD_CFG_ADDSIZ(drd_cfg));
|
||||
break;
|
||||
}
|
||||
|
||||
/* Flash chip model expects one transfer per dummy bit, not byte */
|
||||
dummy_cycles =
|
||||
(FIU_DRD_CFG_DBW(drd_cfg) * 8) >> FIU_DRD_CFG_ACCTYPE(drd_cfg);
|
||||
for (i = 0; i < dummy_cycles; i++) {
|
||||
ssi_transfer(fiu->spi, 0);
|
||||
}
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
value = deposit64(value, 8 * i, 8, ssi_transfer(fiu->spi, 0));
|
||||
}
|
||||
|
||||
trace_npcm7xx_fiu_flash_read(DEVICE(fiu)->canonical_path, fiu->active_cs,
|
||||
addr, size, value);
|
||||
|
||||
npcm7xx_fiu_deselect(fiu);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Direct flash memory write handler. */
|
||||
static void npcm7xx_fiu_flash_write(void *opaque, hwaddr addr, uint64_t v,
|
||||
unsigned int size)
|
||||
{
|
||||
NPCM7xxFIUFlash *f = opaque;
|
||||
NPCM7xxFIUState *fiu = f->fiu;
|
||||
uint32_t dwr_cfg;
|
||||
int cs_id;
|
||||
int i;
|
||||
|
||||
if (fiu->active_cs != -1) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: direct flash write with CS%d already active",
|
||||
DEVICE(fiu)->canonical_path, fiu->active_cs);
|
||||
}
|
||||
|
||||
cs_id = npcm7xx_fiu_cs_index(fiu, f);
|
||||
trace_npcm7xx_fiu_flash_write(DEVICE(fiu)->canonical_path, cs_id, addr,
|
||||
size, v);
|
||||
npcm7xx_fiu_select(fiu, cs_id);
|
||||
|
||||
dwr_cfg = fiu->regs[NPCM7XX_FIU_DWR_CFG];
|
||||
ssi_transfer(fiu->spi, FIU_DWR_CFG_WRCMD(dwr_cfg));
|
||||
|
||||
switch (FIU_DWR_CFG_ADDSIZ(dwr_cfg)) {
|
||||
case FIU_ADDSIZ_4BYTES:
|
||||
ssi_transfer(fiu->spi, extract32(addr, 24, 8));
|
||||
/* fall through */
|
||||
case FIU_ADDSIZ_3BYTES:
|
||||
ssi_transfer(fiu->spi, extract32(addr, 16, 8));
|
||||
ssi_transfer(fiu->spi, extract32(addr, 8, 8));
|
||||
ssi_transfer(fiu->spi, extract32(addr, 0, 8));
|
||||
break;
|
||||
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: bad address size %d\n",
|
||||
DEVICE(fiu)->canonical_path, FIU_DWR_CFG_ADDSIZ(dwr_cfg));
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
ssi_transfer(fiu->spi, extract64(v, i * 8, 8));
|
||||
}
|
||||
|
||||
npcm7xx_fiu_deselect(fiu);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps npcm7xx_fiu_flash_ops = {
|
||||
.read = npcm7xx_fiu_flash_read,
|
||||
.write = npcm7xx_fiu_flash_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 1,
|
||||
.max_access_size = 8,
|
||||
.unaligned = true,
|
||||
},
|
||||
};
|
||||
|
||||
/* Control register read handler. */
|
||||
static uint64_t npcm7xx_fiu_ctrl_read(void *opaque, hwaddr addr,
|
||||
unsigned int size)
|
||||
{
|
||||
hwaddr reg = addr / sizeof(uint32_t);
|
||||
NPCM7xxFIUState *s = opaque;
|
||||
uint32_t value;
|
||||
|
||||
if (reg < NPCM7XX_FIU_NR_REGS) {
|
||||
value = s->regs[reg];
|
||||
} else {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: read from invalid offset 0x%" PRIx64 "\n",
|
||||
DEVICE(s)->canonical_path, addr);
|
||||
value = 0;
|
||||
}
|
||||
|
||||
trace_npcm7xx_fiu_ctrl_read(DEVICE(s)->canonical_path, addr, value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Send the specified number of address bytes from the UMA address register. */
|
||||
static void send_address(SSIBus *spi, unsigned int addsiz, uint32_t addr)
|
||||
{
|
||||
switch (addsiz) {
|
||||
case 4:
|
||||
ssi_transfer(spi, extract32(addr, 24, 8));
|
||||
/* fall through */
|
||||
case 3:
|
||||
ssi_transfer(spi, extract32(addr, 16, 8));
|
||||
/* fall through */
|
||||
case 2:
|
||||
ssi_transfer(spi, extract32(addr, 8, 8));
|
||||
/* fall through */
|
||||
case 1:
|
||||
ssi_transfer(spi, extract32(addr, 0, 8));
|
||||
/* fall through */
|
||||
case 0:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Send the number of dummy bits specified in the UMA config register. */
|
||||
static void send_dummy_bits(SSIBus *spi, uint32_t uma_cfg, uint32_t uma_cmd)
|
||||
{
|
||||
unsigned int bits_per_clock = 1U << FIU_UMA_CFG_DBPCK(uma_cfg);
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < FIU_UMA_CFG_DBSIZ(uma_cfg); i++) {
|
||||
/* Use bytes 0 and 1 first, then keep repeating byte 2 */
|
||||
unsigned int field = (i < 2) ? ((i + 1) * 8) : 24;
|
||||
unsigned int j;
|
||||
|
||||
for (j = 0; j < 8; j += bits_per_clock) {
|
||||
ssi_transfer(spi, extract32(uma_cmd, field + j, bits_per_clock));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Perform a User-Mode Access transaction. */
|
||||
static void npcm7xx_fiu_uma_transaction(NPCM7xxFIUState *s)
|
||||
{
|
||||
uint32_t uma_cts = s->regs[NPCM7XX_FIU_UMA_CTS];
|
||||
uint32_t uma_cfg;
|
||||
unsigned int i;
|
||||
|
||||
/* SW_CS means the CS is already forced low, so don't touch it. */
|
||||
if (uma_cts & FIU_UMA_CTS_SW_CS) {
|
||||
int cs_id = FIU_UMA_CTS_DEV_NUM(s->regs[NPCM7XX_FIU_UMA_CTS]);
|
||||
npcm7xx_fiu_select(s, cs_id);
|
||||
}
|
||||
|
||||
/* Send command, if present. */
|
||||
uma_cfg = s->regs[NPCM7XX_FIU_UMA_CFG];
|
||||
if (FIU_UMA_CFG_CMDSIZ(uma_cfg) > 0) {
|
||||
ssi_transfer(s->spi, extract32(s->regs[NPCM7XX_FIU_UMA_CMD], 0, 8));
|
||||
}
|
||||
|
||||
/* Send address, if present. */
|
||||
send_address(s->spi, FIU_UMA_CFG_ADDSIZ(uma_cfg),
|
||||
s->regs[NPCM7XX_FIU_UMA_ADDR]);
|
||||
|
||||
/* Write data, if present. */
|
||||
for (i = 0; i < FIU_UMA_CFG_WDATSIZ(uma_cfg); i++) {
|
||||
unsigned int reg =
|
||||
(i < 16) ? (NPCM7XX_FIU_UMA_DW0 + i / 4) : NPCM7XX_FIU_UMA_DW3;
|
||||
unsigned int field = (i % 4) * 8;
|
||||
|
||||
ssi_transfer(s->spi, extract32(s->regs[reg], field, 8));
|
||||
}
|
||||
|
||||
/* Send dummy bits, if present. */
|
||||
send_dummy_bits(s->spi, uma_cfg, s->regs[NPCM7XX_FIU_UMA_CMD]);
|
||||
|
||||
/* Read data, if present. */
|
||||
for (i = 0; i < FIU_UMA_CFG_RDATSIZ(uma_cfg); i++) {
|
||||
unsigned int reg = NPCM7XX_FIU_UMA_DR0 + i / 4;
|
||||
unsigned int field = (i % 4) * 8;
|
||||
uint8_t c;
|
||||
|
||||
c = ssi_transfer(s->spi, 0);
|
||||
if (reg <= NPCM7XX_FIU_UMA_DR3) {
|
||||
s->regs[reg] = deposit32(s->regs[reg], field, 8, c);
|
||||
}
|
||||
}
|
||||
|
||||
/* Again, don't touch CS if the user is forcing it low. */
|
||||
if (uma_cts & FIU_UMA_CTS_SW_CS) {
|
||||
npcm7xx_fiu_deselect(s);
|
||||
}
|
||||
|
||||
/* RDYST means a command has completed since it was cleared. */
|
||||
s->regs[NPCM7XX_FIU_UMA_CTS] |= FIU_UMA_CTS_RDYST;
|
||||
/* EXEC_DONE means Execute Command / Not Done, so clear it here. */
|
||||
s->regs[NPCM7XX_FIU_UMA_CTS] &= ~FIU_UMA_CTS_EXEC_DONE;
|
||||
}
|
||||
|
||||
/* Control register write handler. */
|
||||
static void npcm7xx_fiu_ctrl_write(void *opaque, hwaddr addr, uint64_t v,
|
||||
unsigned int size)
|
||||
{
|
||||
hwaddr reg = addr / sizeof(uint32_t);
|
||||
NPCM7xxFIUState *s = opaque;
|
||||
uint32_t value = v;
|
||||
|
||||
trace_npcm7xx_fiu_ctrl_write(DEVICE(s)->canonical_path, addr, value);
|
||||
|
||||
switch (reg) {
|
||||
case NPCM7XX_FIU_UMA_CFG:
|
||||
if (s->regs[reg] & FIU_UMA_CFG_CMMLCK) {
|
||||
value &= ~FIU_UMA_CFG_CMMLCK_MASK;
|
||||
value |= (s->regs[reg] & FIU_UMA_CFG_CMMLCK_MASK);
|
||||
}
|
||||
/* fall through */
|
||||
case NPCM7XX_FIU_DRD_CFG:
|
||||
case NPCM7XX_FIU_DWR_CFG:
|
||||
if (s->regs[reg] & NPCM7XX_FIU_CFG_LCK) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: write to locked register @ 0x%" PRIx64 "\n",
|
||||
DEVICE(s)->canonical_path, addr);
|
||||
return;
|
||||
}
|
||||
s->regs[reg] = value;
|
||||
break;
|
||||
|
||||
case NPCM7XX_FIU_UMA_CTS:
|
||||
if (value & FIU_UMA_CTS_RDYST) {
|
||||
value &= ~FIU_UMA_CTS_RDYST;
|
||||
} else {
|
||||
value |= s->regs[reg] & FIU_UMA_CTS_RDYST;
|
||||
}
|
||||
if ((s->regs[reg] ^ value) & FIU_UMA_CTS_SW_CS) {
|
||||
if (value & FIU_UMA_CTS_SW_CS) {
|
||||
/*
|
||||
* Don't drop CS if there's a transfer in progress, or we're
|
||||
* about to start one.
|
||||
*/
|
||||
if (!((value | s->regs[reg]) & FIU_UMA_CTS_EXEC_DONE)) {
|
||||
npcm7xx_fiu_deselect(s);
|
||||
}
|
||||
} else {
|
||||
int cs_id = FIU_UMA_CTS_DEV_NUM(s->regs[NPCM7XX_FIU_UMA_CTS]);
|
||||
npcm7xx_fiu_select(s, cs_id);
|
||||
}
|
||||
}
|
||||
s->regs[reg] = value | (s->regs[reg] & FIU_UMA_CTS_EXEC_DONE);
|
||||
if (value & FIU_UMA_CTS_EXEC_DONE) {
|
||||
npcm7xx_fiu_uma_transaction(s);
|
||||
}
|
||||
break;
|
||||
|
||||
case NPCM7XX_FIU_UMA_DR0 ... NPCM7XX_FIU_UMA_DR3:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: write to read-only register @ 0x%" PRIx64 "\n",
|
||||
DEVICE(s)->canonical_path, addr);
|
||||
return;
|
||||
|
||||
case NPCM7XX_FIU_PRT_CFG:
|
||||
case NPCM7XX_FIU_PRT_CMD0 ... NPCM7XX_FIU_PRT_CMD9:
|
||||
qemu_log_mask(LOG_UNIMP, "%s: PRT is not implemented\n", __func__);
|
||||
break;
|
||||
|
||||
case NPCM7XX_FIU_UMA_CMD:
|
||||
case NPCM7XX_FIU_UMA_ADDR:
|
||||
case NPCM7XX_FIU_UMA_DW0 ... NPCM7XX_FIU_UMA_DW3:
|
||||
case NPCM7XX_FIU_CFG:
|
||||
s->regs[reg] = value;
|
||||
break;
|
||||
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: write to invalid offset 0x%" PRIx64 "\n",
|
||||
DEVICE(s)->canonical_path, addr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps npcm7xx_fiu_ctrl_ops = {
|
||||
.read = npcm7xx_fiu_ctrl_read,
|
||||
.write = npcm7xx_fiu_ctrl_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
.unaligned = false,
|
||||
},
|
||||
};
|
||||
|
||||
static void npcm7xx_fiu_enter_reset(Object *obj, ResetType type)
|
||||
{
|
||||
NPCM7xxFIUState *s = NPCM7XX_FIU(obj);
|
||||
|
||||
trace_npcm7xx_fiu_enter_reset(DEVICE(obj)->canonical_path, type);
|
||||
|
||||
memset(s->regs, 0, sizeof(s->regs));
|
||||
|
||||
s->regs[NPCM7XX_FIU_DRD_CFG] = 0x0300100b;
|
||||
s->regs[NPCM7XX_FIU_DWR_CFG] = 0x03000002;
|
||||
s->regs[NPCM7XX_FIU_UMA_CFG] = 0x00000400;
|
||||
s->regs[NPCM7XX_FIU_UMA_CTS] = 0x00010000;
|
||||
s->regs[NPCM7XX_FIU_UMA_CMD] = 0x0000000b;
|
||||
s->regs[NPCM7XX_FIU_PRT_CFG] = 0x00000400;
|
||||
s->regs[NPCM7XX_FIU_CFG] = 0x0000000b;
|
||||
}
|
||||
|
||||
static void npcm7xx_fiu_hold_reset(Object *obj)
|
||||
{
|
||||
NPCM7xxFIUState *s = NPCM7XX_FIU(obj);
|
||||
int i;
|
||||
|
||||
trace_npcm7xx_fiu_hold_reset(DEVICE(obj)->canonical_path);
|
||||
|
||||
for (i = 0; i < s->cs_count; i++) {
|
||||
qemu_irq_raise(s->cs_lines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void npcm7xx_fiu_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
NPCM7xxFIUState *s = NPCM7XX_FIU(dev);
|
||||
SysBusDevice *sbd = &s->parent;
|
||||
int i;
|
||||
|
||||
if (s->cs_count <= 0) {
|
||||
error_setg(errp, "%s: %d chip selects specified, need at least one",
|
||||
dev->canonical_path, s->cs_count);
|
||||
return;
|
||||
}
|
||||
|
||||
s->spi = ssi_create_bus(dev, "spi");
|
||||
s->cs_lines = g_new0(qemu_irq, s->cs_count);
|
||||
qdev_init_gpio_out_named(DEVICE(s), s->cs_lines, "cs", s->cs_count);
|
||||
s->flash = g_new0(NPCM7xxFIUFlash, s->cs_count);
|
||||
|
||||
/*
|
||||
* Register the control registers region first. It may be followed by one
|
||||
* or more direct flash access regions.
|
||||
*/
|
||||
memory_region_init_io(&s->mmio, OBJECT(s), &npcm7xx_fiu_ctrl_ops, s, "ctrl",
|
||||
NPCM7XX_FIU_CTRL_REGS_SIZE);
|
||||
sysbus_init_mmio(sbd, &s->mmio);
|
||||
|
||||
for (i = 0; i < s->cs_count; i++) {
|
||||
NPCM7xxFIUFlash *flash = &s->flash[i];
|
||||
flash->fiu = s;
|
||||
memory_region_init_io(&flash->direct_access, OBJECT(s),
|
||||
&npcm7xx_fiu_flash_ops, &s->flash[i], "flash",
|
||||
NPCM7XX_FIU_FLASH_WINDOW_SIZE);
|
||||
sysbus_init_mmio(sbd, &flash->direct_access);
|
||||
}
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_npcm7xx_fiu = {
|
||||
.name = "npcm7xx-fiu",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_INT32(active_cs, NPCM7xxFIUState),
|
||||
VMSTATE_UINT32_ARRAY(regs, NPCM7xxFIUState, NPCM7XX_FIU_NR_REGS),
|
||||
VMSTATE_END_OF_LIST(),
|
||||
},
|
||||
};
|
||||
|
||||
static Property npcm7xx_fiu_properties[] = {
|
||||
DEFINE_PROP_INT32("cs-count", NPCM7xxFIUState, cs_count, 0),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void npcm7xx_fiu_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
ResettableClass *rc = RESETTABLE_CLASS(klass);
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
QEMU_BUILD_BUG_ON(NPCM7XX_FIU_REGS_END > NPCM7XX_FIU_NR_REGS);
|
||||
|
||||
dc->desc = "NPCM7xx Flash Interface Unit";
|
||||
dc->realize = npcm7xx_fiu_realize;
|
||||
dc->vmsd = &vmstate_npcm7xx_fiu;
|
||||
rc->phases.enter = npcm7xx_fiu_enter_reset;
|
||||
rc->phases.hold = npcm7xx_fiu_hold_reset;
|
||||
device_class_set_props(dc, npcm7xx_fiu_properties);
|
||||
}
|
||||
|
||||
static const TypeInfo npcm7xx_fiu_types[] = {
|
||||
{
|
||||
.name = TYPE_NPCM7XX_FIU,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(NPCM7xxFIUState),
|
||||
.class_init = npcm7xx_fiu_class_init,
|
||||
},
|
||||
};
|
||||
DEFINE_TYPES(npcm7xx_fiu_types);
|
|
@ -9,3 +9,14 @@ aspeed_smc_dma_checksum(uint32_t addr, uint32_t data) "0x%08x: 0x%08x"
|
|||
aspeed_smc_dma_rw(const char *dir, uint32_t flash_addr, uint32_t dram_addr, uint32_t size) "%s flash:@0x%08x dram:@0x%08x size:0x%08x"
|
||||
aspeed_smc_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size %u: 0x%" PRIx64
|
||||
aspeed_smc_flash_select(int cs, const char *prefix) "CS%d %sselect"
|
||||
|
||||
# npcm7xx_fiu.c
|
||||
|
||||
npcm7xx_fiu_enter_reset(const char *id, int reset_type) "%s reset type: %d"
|
||||
npcm7xx_fiu_hold_reset(const char *id) "%s"
|
||||
npcm7xx_fiu_select(const char *id, int cs) "%s select CS%d"
|
||||
npcm7xx_fiu_deselect(const char *id, int cs) "%s deselect CS%d"
|
||||
npcm7xx_fiu_ctrl_read(const char *id, uint64_t addr, uint32_t data) "%s offset: 0x%04" PRIx64 " value: 0x%08" PRIx32
|
||||
npcm7xx_fiu_ctrl_write(const char *id, uint64_t addr, uint32_t data) "%s offset: 0x%04" PRIx64 " value: 0x%08" PRIx32
|
||||
npcm7xx_fiu_flash_read(const char *id, int cs, uint64_t addr, unsigned int size, uint64_t value) "%s[%d] offset: 0x%08" PRIx64 " size: %u value: 0x%" PRIx64
|
||||
npcm7xx_fiu_flash_write(const char *id, int cs, uint64_t addr, unsigned int size, uint64_t value) "%s[%d] offset: 0x%08" PRIx64 " size: %u value: 0x%" PRIx64
|
||||
|
|
|
@ -202,6 +202,14 @@ static void systick_reset(DeviceState *dev)
|
|||
{
|
||||
SysTickState *s = SYSTICK(dev);
|
||||
|
||||
/*
|
||||
* Forgetting to set system_clock_scale is always a board code
|
||||
* bug. We can't check this earlier because for some boards
|
||||
* (like stellaris) it is not yet configured at the point where
|
||||
* the systick device is realized.
|
||||
*/
|
||||
assert(system_clock_scale != 0);
|
||||
|
||||
s->control = 0;
|
||||
s->reload = 0;
|
||||
s->tick = 0;
|
||||
|
|
|
@ -23,6 +23,7 @@ softmmu_ss.add(when: 'CONFIG_LM32', if_true: files('lm32_timer.c'))
|
|||
softmmu_ss.add(when: 'CONFIG_MILKYMIST', if_true: files('milkymist-sysctl.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_MIPS_CPS', if_true: files('mips_gictimer.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_MSF2', if_true: files('mss-timer.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_timer.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('nrf51_timer.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_gptimer.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_synctimer.c'))
|
||||
|
|
|
@ -0,0 +1,543 @@
|
|||
/*
|
||||
* Nuvoton NPCM7xx Timer Controller
|
||||
*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "hw/irq.h"
|
||||
#include "hw/misc/npcm7xx_clk.h"
|
||||
#include "hw/timer/npcm7xx_timer.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qemu/bitops.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "qemu/units.h"
|
||||
#include "trace.h"
|
||||
|
||||
/* 32-bit register indices. */
|
||||
enum NPCM7xxTimerRegisters {
|
||||
NPCM7XX_TIMER_TCSR0,
|
||||
NPCM7XX_TIMER_TCSR1,
|
||||
NPCM7XX_TIMER_TICR0,
|
||||
NPCM7XX_TIMER_TICR1,
|
||||
NPCM7XX_TIMER_TDR0,
|
||||
NPCM7XX_TIMER_TDR1,
|
||||
NPCM7XX_TIMER_TISR,
|
||||
NPCM7XX_TIMER_WTCR,
|
||||
NPCM7XX_TIMER_TCSR2,
|
||||
NPCM7XX_TIMER_TCSR3,
|
||||
NPCM7XX_TIMER_TICR2,
|
||||
NPCM7XX_TIMER_TICR3,
|
||||
NPCM7XX_TIMER_TDR2,
|
||||
NPCM7XX_TIMER_TDR3,
|
||||
NPCM7XX_TIMER_TCSR4 = 0x0040 / sizeof(uint32_t),
|
||||
NPCM7XX_TIMER_TICR4 = 0x0048 / sizeof(uint32_t),
|
||||
NPCM7XX_TIMER_TDR4 = 0x0050 / sizeof(uint32_t),
|
||||
NPCM7XX_TIMER_REGS_END,
|
||||
};
|
||||
|
||||
/* Register field definitions. */
|
||||
#define NPCM7XX_TCSR_CEN BIT(30)
|
||||
#define NPCM7XX_TCSR_IE BIT(29)
|
||||
#define NPCM7XX_TCSR_PERIODIC BIT(27)
|
||||
#define NPCM7XX_TCSR_CRST BIT(26)
|
||||
#define NPCM7XX_TCSR_CACT BIT(25)
|
||||
#define NPCM7XX_TCSR_RSVD 0x01ffff00
|
||||
#define NPCM7XX_TCSR_PRESCALE_START 0
|
||||
#define NPCM7XX_TCSR_PRESCALE_LEN 8
|
||||
|
||||
/*
|
||||
* Returns the index of timer in the tc->timer array. This can be used to
|
||||
* locate the registers that belong to this timer.
|
||||
*/
|
||||
static int npcm7xx_timer_index(NPCM7xxTimerCtrlState *tc, NPCM7xxTimer *timer)
|
||||
{
|
||||
int index = timer - tc->timer;
|
||||
|
||||
g_assert(index >= 0 && index < NPCM7XX_TIMERS_PER_CTRL);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/* Return the value by which to divide the reference clock rate. */
|
||||
static uint32_t npcm7xx_tcsr_prescaler(uint32_t tcsr)
|
||||
{
|
||||
return extract32(tcsr, NPCM7XX_TCSR_PRESCALE_START,
|
||||
NPCM7XX_TCSR_PRESCALE_LEN) + 1;
|
||||
}
|
||||
|
||||
/* Convert a timer cycle count to a time interval in nanoseconds. */
|
||||
static int64_t npcm7xx_timer_count_to_ns(NPCM7xxTimer *t, uint32_t count)
|
||||
{
|
||||
int64_t ns = count;
|
||||
|
||||
ns *= NANOSECONDS_PER_SECOND / NPCM7XX_TIMER_REF_HZ;
|
||||
ns *= npcm7xx_tcsr_prescaler(t->tcsr);
|
||||
|
||||
return ns;
|
||||
}
|
||||
|
||||
/* Convert a time interval in nanoseconds to a timer cycle count. */
|
||||
static uint32_t npcm7xx_timer_ns_to_count(NPCM7xxTimer *t, int64_t ns)
|
||||
{
|
||||
int64_t count;
|
||||
|
||||
count = ns / (NANOSECONDS_PER_SECOND / NPCM7XX_TIMER_REF_HZ);
|
||||
count /= npcm7xx_tcsr_prescaler(t->tcsr);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Raise the interrupt line if there's a pending interrupt and interrupts are
|
||||
* enabled for this timer. If not, lower it.
|
||||
*/
|
||||
static void npcm7xx_timer_check_interrupt(NPCM7xxTimer *t)
|
||||
{
|
||||
NPCM7xxTimerCtrlState *tc = t->ctrl;
|
||||
int index = npcm7xx_timer_index(tc, t);
|
||||
bool pending = (t->tcsr & NPCM7XX_TCSR_IE) && (tc->tisr & BIT(index));
|
||||
|
||||
qemu_set_irq(t->irq, pending);
|
||||
trace_npcm7xx_timer_irq(DEVICE(tc)->canonical_path, index, pending);
|
||||
}
|
||||
|
||||
/* Start or resume the timer. */
|
||||
static void npcm7xx_timer_start(NPCM7xxTimer *t)
|
||||
{
|
||||
int64_t now;
|
||||
|
||||
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
t->expires_ns = now + t->remaining_ns;
|
||||
timer_mod(&t->qtimer, t->expires_ns);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when the counter reaches zero. Sets the interrupt flag, and either
|
||||
* restarts or disables the timer.
|
||||
*/
|
||||
static void npcm7xx_timer_reached_zero(NPCM7xxTimer *t)
|
||||
{
|
||||
NPCM7xxTimerCtrlState *tc = t->ctrl;
|
||||
int index = npcm7xx_timer_index(tc, t);
|
||||
|
||||
tc->tisr |= BIT(index);
|
||||
|
||||
if (t->tcsr & NPCM7XX_TCSR_PERIODIC) {
|
||||
t->remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr);
|
||||
if (t->tcsr & NPCM7XX_TCSR_CEN) {
|
||||
npcm7xx_timer_start(t);
|
||||
}
|
||||
} else {
|
||||
t->tcsr &= ~(NPCM7XX_TCSR_CEN | NPCM7XX_TCSR_CACT);
|
||||
}
|
||||
|
||||
npcm7xx_timer_check_interrupt(t);
|
||||
}
|
||||
|
||||
/* Stop counting. Record the time remaining so we can continue later. */
|
||||
static void npcm7xx_timer_pause(NPCM7xxTimer *t)
|
||||
{
|
||||
int64_t now;
|
||||
|
||||
timer_del(&t->qtimer);
|
||||
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
t->remaining_ns = t->expires_ns - now;
|
||||
if (t->remaining_ns <= 0) {
|
||||
npcm7xx_timer_reached_zero(t);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Restart the timer from its initial value. If the timer was enabled and stays
|
||||
* enabled, adjust the QEMU timer according to the new count. If the timer is
|
||||
* transitioning from disabled to enabled, the caller is expected to start the
|
||||
* timer later.
|
||||
*/
|
||||
static void npcm7xx_timer_restart(NPCM7xxTimer *t, uint32_t old_tcsr)
|
||||
{
|
||||
t->remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr);
|
||||
|
||||
if (old_tcsr & t->tcsr & NPCM7XX_TCSR_CEN) {
|
||||
npcm7xx_timer_start(t);
|
||||
}
|
||||
}
|
||||
|
||||
/* Register read and write handlers */
|
||||
|
||||
static uint32_t npcm7xx_timer_read_tdr(NPCM7xxTimer *t)
|
||||
{
|
||||
if (t->tcsr & NPCM7XX_TCSR_CEN) {
|
||||
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
|
||||
return npcm7xx_timer_ns_to_count(t, t->expires_ns - now);
|
||||
}
|
||||
|
||||
return npcm7xx_timer_ns_to_count(t, t->remaining_ns);
|
||||
}
|
||||
|
||||
static void npcm7xx_timer_write_tcsr(NPCM7xxTimer *t, uint32_t new_tcsr)
|
||||
{
|
||||
uint32_t old_tcsr = t->tcsr;
|
||||
uint32_t tdr;
|
||||
|
||||
if (new_tcsr & NPCM7XX_TCSR_RSVD) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: reserved bits in 0x%08x ignored\n",
|
||||
__func__, new_tcsr);
|
||||
new_tcsr &= ~NPCM7XX_TCSR_RSVD;
|
||||
}
|
||||
if (new_tcsr & NPCM7XX_TCSR_CACT) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: read-only bits in 0x%08x ignored\n",
|
||||
__func__, new_tcsr);
|
||||
new_tcsr &= ~NPCM7XX_TCSR_CACT;
|
||||
}
|
||||
if ((new_tcsr & NPCM7XX_TCSR_CRST) && (new_tcsr & NPCM7XX_TCSR_CEN)) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: both CRST and CEN set; ignoring CEN.\n",
|
||||
__func__);
|
||||
new_tcsr &= ~NPCM7XX_TCSR_CEN;
|
||||
}
|
||||
|
||||
/* Calculate the value of TDR before potentially changing the prescaler. */
|
||||
tdr = npcm7xx_timer_read_tdr(t);
|
||||
|
||||
t->tcsr = (t->tcsr & NPCM7XX_TCSR_CACT) | new_tcsr;
|
||||
|
||||
if (npcm7xx_tcsr_prescaler(old_tcsr) != npcm7xx_tcsr_prescaler(new_tcsr)) {
|
||||
/* Recalculate time remaining based on the current TDR value. */
|
||||
t->remaining_ns = npcm7xx_timer_count_to_ns(t, tdr);
|
||||
if (old_tcsr & t->tcsr & NPCM7XX_TCSR_CEN) {
|
||||
npcm7xx_timer_start(t);
|
||||
}
|
||||
}
|
||||
|
||||
if ((old_tcsr ^ new_tcsr) & NPCM7XX_TCSR_IE) {
|
||||
npcm7xx_timer_check_interrupt(t);
|
||||
}
|
||||
if (new_tcsr & NPCM7XX_TCSR_CRST) {
|
||||
npcm7xx_timer_restart(t, old_tcsr);
|
||||
t->tcsr &= ~NPCM7XX_TCSR_CRST;
|
||||
}
|
||||
if ((old_tcsr ^ new_tcsr) & NPCM7XX_TCSR_CEN) {
|
||||
if (new_tcsr & NPCM7XX_TCSR_CEN) {
|
||||
t->tcsr |= NPCM7XX_TCSR_CACT;
|
||||
npcm7xx_timer_start(t);
|
||||
} else {
|
||||
t->tcsr &= ~NPCM7XX_TCSR_CACT;
|
||||
npcm7xx_timer_pause(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void npcm7xx_timer_write_ticr(NPCM7xxTimer *t, uint32_t new_ticr)
|
||||
{
|
||||
t->ticr = new_ticr;
|
||||
|
||||
npcm7xx_timer_restart(t, t->tcsr);
|
||||
}
|
||||
|
||||
static void npcm7xx_timer_write_tisr(NPCM7xxTimerCtrlState *s, uint32_t value)
|
||||
{
|
||||
int i;
|
||||
|
||||
s->tisr &= ~value;
|
||||
for (i = 0; i < ARRAY_SIZE(s->timer); i++) {
|
||||
if (value & (1U << i)) {
|
||||
npcm7xx_timer_check_interrupt(&s->timer[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static hwaddr npcm7xx_tcsr_index(hwaddr reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case NPCM7XX_TIMER_TCSR0:
|
||||
return 0;
|
||||
case NPCM7XX_TIMER_TCSR1:
|
||||
return 1;
|
||||
case NPCM7XX_TIMER_TCSR2:
|
||||
return 2;
|
||||
case NPCM7XX_TIMER_TCSR3:
|
||||
return 3;
|
||||
case NPCM7XX_TIMER_TCSR4:
|
||||
return 4;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
static hwaddr npcm7xx_ticr_index(hwaddr reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case NPCM7XX_TIMER_TICR0:
|
||||
return 0;
|
||||
case NPCM7XX_TIMER_TICR1:
|
||||
return 1;
|
||||
case NPCM7XX_TIMER_TICR2:
|
||||
return 2;
|
||||
case NPCM7XX_TIMER_TICR3:
|
||||
return 3;
|
||||
case NPCM7XX_TIMER_TICR4:
|
||||
return 4;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
static hwaddr npcm7xx_tdr_index(hwaddr reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case NPCM7XX_TIMER_TDR0:
|
||||
return 0;
|
||||
case NPCM7XX_TIMER_TDR1:
|
||||
return 1;
|
||||
case NPCM7XX_TIMER_TDR2:
|
||||
return 2;
|
||||
case NPCM7XX_TIMER_TDR3:
|
||||
return 3;
|
||||
case NPCM7XX_TIMER_TDR4:
|
||||
return 4;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t npcm7xx_timer_read(void *opaque, hwaddr offset, unsigned size)
|
||||
{
|
||||
NPCM7xxTimerCtrlState *s = opaque;
|
||||
uint64_t value = 0;
|
||||
hwaddr reg;
|
||||
|
||||
reg = offset / sizeof(uint32_t);
|
||||
switch (reg) {
|
||||
case NPCM7XX_TIMER_TCSR0:
|
||||
case NPCM7XX_TIMER_TCSR1:
|
||||
case NPCM7XX_TIMER_TCSR2:
|
||||
case NPCM7XX_TIMER_TCSR3:
|
||||
case NPCM7XX_TIMER_TCSR4:
|
||||
value = s->timer[npcm7xx_tcsr_index(reg)].tcsr;
|
||||
break;
|
||||
|
||||
case NPCM7XX_TIMER_TICR0:
|
||||
case NPCM7XX_TIMER_TICR1:
|
||||
case NPCM7XX_TIMER_TICR2:
|
||||
case NPCM7XX_TIMER_TICR3:
|
||||
case NPCM7XX_TIMER_TICR4:
|
||||
value = s->timer[npcm7xx_ticr_index(reg)].ticr;
|
||||
break;
|
||||
|
||||
case NPCM7XX_TIMER_TDR0:
|
||||
case NPCM7XX_TIMER_TDR1:
|
||||
case NPCM7XX_TIMER_TDR2:
|
||||
case NPCM7XX_TIMER_TDR3:
|
||||
case NPCM7XX_TIMER_TDR4:
|
||||
value = npcm7xx_timer_read_tdr(&s->timer[npcm7xx_tdr_index(reg)]);
|
||||
break;
|
||||
|
||||
case NPCM7XX_TIMER_TISR:
|
||||
value = s->tisr;
|
||||
break;
|
||||
|
||||
case NPCM7XX_TIMER_WTCR:
|
||||
value = s->wtcr;
|
||||
break;
|
||||
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: invalid offset 0x%04" HWADDR_PRIx "\n",
|
||||
__func__, offset);
|
||||
break;
|
||||
}
|
||||
|
||||
trace_npcm7xx_timer_read(DEVICE(s)->canonical_path, offset, value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static void npcm7xx_timer_write(void *opaque, hwaddr offset,
|
||||
uint64_t v, unsigned size)
|
||||
{
|
||||
uint32_t reg = offset / sizeof(uint32_t);
|
||||
NPCM7xxTimerCtrlState *s = opaque;
|
||||
uint32_t value = v;
|
||||
|
||||
trace_npcm7xx_timer_write(DEVICE(s)->canonical_path, offset, value);
|
||||
|
||||
switch (reg) {
|
||||
case NPCM7XX_TIMER_TCSR0:
|
||||
case NPCM7XX_TIMER_TCSR1:
|
||||
case NPCM7XX_TIMER_TCSR2:
|
||||
case NPCM7XX_TIMER_TCSR3:
|
||||
case NPCM7XX_TIMER_TCSR4:
|
||||
npcm7xx_timer_write_tcsr(&s->timer[npcm7xx_tcsr_index(reg)], value);
|
||||
return;
|
||||
|
||||
case NPCM7XX_TIMER_TICR0:
|
||||
case NPCM7XX_TIMER_TICR1:
|
||||
case NPCM7XX_TIMER_TICR2:
|
||||
case NPCM7XX_TIMER_TICR3:
|
||||
case NPCM7XX_TIMER_TICR4:
|
||||
npcm7xx_timer_write_ticr(&s->timer[npcm7xx_ticr_index(reg)], value);
|
||||
return;
|
||||
|
||||
case NPCM7XX_TIMER_TDR0:
|
||||
case NPCM7XX_TIMER_TDR1:
|
||||
case NPCM7XX_TIMER_TDR2:
|
||||
case NPCM7XX_TIMER_TDR3:
|
||||
case NPCM7XX_TIMER_TDR4:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: register @ 0x%04" HWADDR_PRIx " is read-only\n",
|
||||
__func__, offset);
|
||||
return;
|
||||
|
||||
case NPCM7XX_TIMER_TISR:
|
||||
npcm7xx_timer_write_tisr(s, value);
|
||||
return;
|
||||
|
||||
case NPCM7XX_TIMER_WTCR:
|
||||
qemu_log_mask(LOG_UNIMP, "%s: WTCR write not implemented: 0x%08x\n",
|
||||
__func__, value);
|
||||
return;
|
||||
}
|
||||
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: invalid offset 0x%04" HWADDR_PRIx "\n",
|
||||
__func__, offset);
|
||||
}
|
||||
|
||||
static const struct MemoryRegionOps npcm7xx_timer_ops = {
|
||||
.read = npcm7xx_timer_read,
|
||||
.write = npcm7xx_timer_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
.unaligned = false,
|
||||
},
|
||||
};
|
||||
|
||||
/* Called when the QEMU timer expires. */
|
||||
static void npcm7xx_timer_expired(void *opaque)
|
||||
{
|
||||
NPCM7xxTimer *t = opaque;
|
||||
|
||||
if (t->tcsr & NPCM7XX_TCSR_CEN) {
|
||||
npcm7xx_timer_reached_zero(t);
|
||||
}
|
||||
}
|
||||
|
||||
static void npcm7xx_timer_enter_reset(Object *obj, ResetType type)
|
||||
{
|
||||
NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(obj);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) {
|
||||
NPCM7xxTimer *t = &s->timer[i];
|
||||
|
||||
timer_del(&t->qtimer);
|
||||
t->expires_ns = 0;
|
||||
t->remaining_ns = 0;
|
||||
t->tcsr = 0x00000005;
|
||||
t->ticr = 0x00000000;
|
||||
}
|
||||
|
||||
s->tisr = 0x00000000;
|
||||
s->wtcr = 0x00000400;
|
||||
}
|
||||
|
||||
static void npcm7xx_timer_hold_reset(Object *obj)
|
||||
{
|
||||
NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(obj);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) {
|
||||
qemu_irq_lower(s->timer[i].irq);
|
||||
}
|
||||
}
|
||||
|
||||
static void npcm7xx_timer_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(dev);
|
||||
SysBusDevice *sbd = &s->parent;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) {
|
||||
NPCM7xxTimer *t = &s->timer[i];
|
||||
t->ctrl = s;
|
||||
timer_init_ns(&t->qtimer, QEMU_CLOCK_VIRTUAL, npcm7xx_timer_expired, t);
|
||||
sysbus_init_irq(sbd, &t->irq);
|
||||
}
|
||||
|
||||
memory_region_init_io(&s->iomem, OBJECT(s), &npcm7xx_timer_ops, s,
|
||||
TYPE_NPCM7XX_TIMER, 4 * KiB);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_npcm7xx_timer = {
|
||||
.name = "npcm7xx-timer",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_TIMER(qtimer, NPCM7xxTimer),
|
||||
VMSTATE_INT64(expires_ns, NPCM7xxTimer),
|
||||
VMSTATE_INT64(remaining_ns, NPCM7xxTimer),
|
||||
VMSTATE_UINT32(tcsr, NPCM7xxTimer),
|
||||
VMSTATE_UINT32(ticr, NPCM7xxTimer),
|
||||
VMSTATE_END_OF_LIST(),
|
||||
},
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_npcm7xx_timer_ctrl = {
|
||||
.name = "npcm7xx-timer-ctrl",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32(tisr, NPCM7xxTimerCtrlState),
|
||||
VMSTATE_UINT32(wtcr, NPCM7xxTimerCtrlState),
|
||||
VMSTATE_STRUCT_ARRAY(timer, NPCM7xxTimerCtrlState,
|
||||
NPCM7XX_TIMERS_PER_CTRL, 0, vmstate_npcm7xx_timer,
|
||||
NPCM7xxTimer),
|
||||
VMSTATE_END_OF_LIST(),
|
||||
},
|
||||
};
|
||||
|
||||
static void npcm7xx_timer_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
ResettableClass *rc = RESETTABLE_CLASS(klass);
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
QEMU_BUILD_BUG_ON(NPCM7XX_TIMER_REGS_END > NPCM7XX_TIMER_NR_REGS);
|
||||
|
||||
dc->desc = "NPCM7xx Timer Controller";
|
||||
dc->realize = npcm7xx_timer_realize;
|
||||
dc->vmsd = &vmstate_npcm7xx_timer_ctrl;
|
||||
rc->phases.enter = npcm7xx_timer_enter_reset;
|
||||
rc->phases.hold = npcm7xx_timer_hold_reset;
|
||||
}
|
||||
|
||||
static const TypeInfo npcm7xx_timer_info = {
|
||||
.name = TYPE_NPCM7XX_TIMER,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(NPCM7xxTimerCtrlState),
|
||||
.class_init = npcm7xx_timer_class_init,
|
||||
};
|
||||
|
||||
static void npcm7xx_timer_register_type(void)
|
||||
{
|
||||
type_register_static(&npcm7xx_timer_info);
|
||||
}
|
||||
type_init(npcm7xx_timer_register_type);
|
|
@ -66,6 +66,11 @@ cmsdk_apb_dualtimer_read(uint64_t offset, uint64_t data, unsigned size) "CMSDK A
|
|||
cmsdk_apb_dualtimer_write(uint64_t offset, uint64_t data, unsigned size) "CMSDK APB dualtimer write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
|
||||
cmsdk_apb_dualtimer_reset(void) "CMSDK APB dualtimer: reset"
|
||||
|
||||
# npcm7xx_timer.c
|
||||
npcm7xx_timer_read(const char *id, uint64_t offset, uint64_t value) " %s offset: 0x%04" PRIx64 " value 0x%08" PRIx64
|
||||
npcm7xx_timer_write(const char *id, uint64_t offset, uint64_t value) "%s offset: 0x%04" PRIx64 " value 0x%08" PRIx64
|
||||
npcm7xx_timer_irq(const char *id, int timer, int state) "%s timer %d state %d"
|
||||
|
||||
# nrf51_timer.c
|
||||
nrf51_timer_read(uint8_t timer_id, uint64_t addr, uint32_t value, unsigned size) "timer %u read addr 0x%" PRIx64 " data 0x%" PRIx32 " size %u"
|
||||
nrf51_timer_write(uint8_t timer_id, uint64_t addr, uint32_t value, unsigned size) "timer %u write addr 0x%" PRIx64 " data 0x%" PRIx32 " size %u"
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Nuvoton NPCM7xx SoC family.
|
||||
*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef NPCM7XX_H
|
||||
#define NPCM7XX_H
|
||||
|
||||
#include "hw/boards.h"
|
||||
#include "hw/cpu/a9mpcore.h"
|
||||
#include "hw/mem/npcm7xx_mc.h"
|
||||
#include "hw/misc/npcm7xx_clk.h"
|
||||
#include "hw/misc/npcm7xx_gcr.h"
|
||||
#include "hw/nvram/npcm7xx_otp.h"
|
||||
#include "hw/timer/npcm7xx_timer.h"
|
||||
#include "hw/ssi/npcm7xx_fiu.h"
|
||||
#include "target/arm/cpu.h"
|
||||
|
||||
#define NPCM7XX_MAX_NUM_CPUS (2)
|
||||
|
||||
/* The first half of the address space is reserved for DDR4 DRAM. */
|
||||
#define NPCM7XX_DRAM_BA (0x00000000)
|
||||
#define NPCM7XX_DRAM_SZ (2 * GiB)
|
||||
|
||||
/* Magic addresses for setting up direct kernel booting and SMP boot stubs. */
|
||||
#define NPCM7XX_LOADER_START (0x00000000) /* Start of SDRAM */
|
||||
#define NPCM7XX_SMP_LOADER_START (0xffff0000) /* Boot ROM */
|
||||
#define NPCM7XX_SMP_BOOTREG_ADDR (0xf080013c) /* GCR.SCRPAD */
|
||||
#define NPCM7XX_GIC_CPU_IF_ADDR (0xf03fe100) /* GIC within A9 */
|
||||
#define NPCM7XX_BOARD_SETUP_ADDR (0xffff1000) /* Boot ROM */
|
||||
|
||||
typedef struct NPCM7xxMachine {
|
||||
MachineState parent;
|
||||
} NPCM7xxMachine;
|
||||
|
||||
#define TYPE_NPCM7XX_MACHINE MACHINE_TYPE_NAME("npcm7xx")
|
||||
#define NPCM7XX_MACHINE(obj) \
|
||||
OBJECT_CHECK(NPCM7xxMachine, (obj), TYPE_NPCM7XX_MACHINE)
|
||||
|
||||
typedef struct NPCM7xxMachineClass {
|
||||
MachineClass parent;
|
||||
|
||||
const char *soc_type;
|
||||
} NPCM7xxMachineClass;
|
||||
|
||||
#define NPCM7XX_MACHINE_CLASS(klass) \
|
||||
OBJECT_CLASS_CHECK(NPCM7xxMachineClass, (klass), TYPE_NPCM7XX_MACHINE)
|
||||
#define NPCM7XX_MACHINE_GET_CLASS(obj) \
|
||||
OBJECT_GET_CLASS(NPCM7xxMachineClass, (obj), TYPE_NPCM7XX_MACHINE)
|
||||
|
||||
typedef struct NPCM7xxState {
|
||||
DeviceState parent;
|
||||
|
||||
ARMCPU cpu[NPCM7XX_MAX_NUM_CPUS];
|
||||
A9MPPrivState a9mpcore;
|
||||
|
||||
MemoryRegion sram;
|
||||
MemoryRegion irom;
|
||||
MemoryRegion ram3;
|
||||
MemoryRegion *dram;
|
||||
|
||||
NPCM7xxGCRState gcr;
|
||||
NPCM7xxCLKState clk;
|
||||
NPCM7xxTimerCtrlState tim[3];
|
||||
NPCM7xxOTPState key_storage;
|
||||
NPCM7xxOTPState fuse_array;
|
||||
NPCM7xxMCState mc;
|
||||
NPCM7xxFIUState fiu[2];
|
||||
} NPCM7xxState;
|
||||
|
||||
#define TYPE_NPCM7XX "npcm7xx"
|
||||
#define NPCM7XX(obj) OBJECT_CHECK(NPCM7xxState, (obj), TYPE_NPCM7XX)
|
||||
|
||||
#define TYPE_NPCM730 "npcm730"
|
||||
#define TYPE_NPCM750 "npcm750"
|
||||
|
||||
typedef struct NPCM7xxClass {
|
||||
DeviceClass parent;
|
||||
|
||||
/* Bitmask of modules that are permanently disabled on this chip. */
|
||||
uint32_t disabled_modules;
|
||||
/* Number of CPU cores enabled in this SoC class (may be 1 or 2). */
|
||||
uint32_t num_cpus;
|
||||
} NPCM7xxClass;
|
||||
|
||||
#define NPCM7XX_CLASS(klass) \
|
||||
OBJECT_CLASS_CHECK(NPCM7xxClass, (klass), TYPE_NPCM7XX)
|
||||
#define NPCM7XX_GET_CLASS(obj) \
|
||||
OBJECT_GET_CLASS(NPCM7xxClass, (obj), TYPE_NPCM7XX)
|
||||
|
||||
/**
|
||||
* npcm7xx_load_kernel - Loads memory with everything needed to boot
|
||||
* @machine - The machine containing the SoC to be booted.
|
||||
* @soc - The SoC containing the CPU to be booted.
|
||||
*
|
||||
* This will set up the ARM boot info structure for the specific NPCM7xx
|
||||
* derivative and call arm_load_kernel() to set up loading of the kernel, etc.
|
||||
* into memory, if requested by the user.
|
||||
*/
|
||||
void npcm7xx_load_kernel(MachineState *machine, NPCM7xxState *soc);
|
||||
|
||||
#endif /* NPCM7XX_H */
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Nuvoton NPCM7xx Memory Controller stub
|
||||
*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef NPCM7XX_MC_H
|
||||
#define NPCM7XX_MC_H
|
||||
|
||||
#include "exec/memory.h"
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
/**
|
||||
* struct NPCM7xxMCState - Device state for the memory controller.
|
||||
* @parent: System bus device.
|
||||
* @mmio: Memory region through which registers are accessed.
|
||||
*/
|
||||
typedef struct NPCM7xxMCState {
|
||||
SysBusDevice parent;
|
||||
|
||||
MemoryRegion mmio;
|
||||
} NPCM7xxMCState;
|
||||
|
||||
#define TYPE_NPCM7XX_MC "npcm7xx-mc"
|
||||
#define NPCM7XX_MC(obj) OBJECT_CHECK(NPCM7xxMCState, (obj), TYPE_NPCM7XX_MC)
|
||||
|
||||
#endif /* NPCM7XX_MC_H */
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Nuvoton NPCM7xx Clock Control Registers.
|
||||
*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef NPCM7XX_CLK_H
|
||||
#define NPCM7XX_CLK_H
|
||||
|
||||
#include "exec/memory.h"
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
/*
|
||||
* The reference clock frequency for the timer modules, and the SECCNT and
|
||||
* CNTR25M registers in this module, is always 25 MHz.
|
||||
*/
|
||||
#define NPCM7XX_TIMER_REF_HZ (25000000)
|
||||
|
||||
/*
|
||||
* Number of registers in our device state structure. Don't change this without
|
||||
* incrementing the version_id in the vmstate.
|
||||
*/
|
||||
#define NPCM7XX_CLK_NR_REGS (0x70 / sizeof(uint32_t))
|
||||
|
||||
typedef struct NPCM7xxCLKState {
|
||||
SysBusDevice parent;
|
||||
|
||||
MemoryRegion iomem;
|
||||
|
||||
uint32_t regs[NPCM7XX_CLK_NR_REGS];
|
||||
|
||||
/* Time reference for SECCNT and CNTR25M, initialized by power on reset */
|
||||
int64_t ref_ns;
|
||||
} NPCM7xxCLKState;
|
||||
|
||||
#define TYPE_NPCM7XX_CLK "npcm7xx-clk"
|
||||
#define NPCM7XX_CLK(obj) OBJECT_CHECK(NPCM7xxCLKState, (obj), TYPE_NPCM7XX_CLK)
|
||||
|
||||
#endif /* NPCM7XX_CLK_H */
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Nuvoton NPCM7xx System Global Control Registers.
|
||||
*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef NPCM7XX_GCR_H
|
||||
#define NPCM7XX_GCR_H
|
||||
|
||||
#include "exec/memory.h"
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
/*
|
||||
* Number of registers in our device state structure. Don't change this without
|
||||
* incrementing the version_id in the vmstate.
|
||||
*/
|
||||
#define NPCM7XX_GCR_NR_REGS (0x148 / sizeof(uint32_t))
|
||||
|
||||
typedef struct NPCM7xxGCRState {
|
||||
SysBusDevice parent;
|
||||
|
||||
MemoryRegion iomem;
|
||||
|
||||
uint32_t regs[NPCM7XX_GCR_NR_REGS];
|
||||
|
||||
uint32_t reset_pwron;
|
||||
uint32_t reset_mdlr;
|
||||
uint32_t reset_intcr3;
|
||||
} NPCM7xxGCRState;
|
||||
|
||||
#define TYPE_NPCM7XX_GCR "npcm7xx-gcr"
|
||||
#define NPCM7XX_GCR(obj) OBJECT_CHECK(NPCM7xxGCRState, (obj), TYPE_NPCM7XX_GCR)
|
||||
|
||||
#endif /* NPCM7XX_GCR_H */
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Nuvoton NPCM7xx OTP (Fuse Array) Interface
|
||||
*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef NPCM7XX_OTP_H
|
||||
#define NPCM7XX_OTP_H
|
||||
|
||||
#include "exec/memory.h"
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
/* Each OTP module holds 8192 bits of one-time programmable storage */
|
||||
#define NPCM7XX_OTP_ARRAY_BITS (8192)
|
||||
#define NPCM7XX_OTP_ARRAY_BYTES (NPCM7XX_OTP_ARRAY_BITS / BITS_PER_BYTE)
|
||||
|
||||
/* Fuse array offsets */
|
||||
#define NPCM7XX_FUSE_FUSTRAP (0)
|
||||
#define NPCM7XX_FUSE_CP_FUSTRAP (12)
|
||||
#define NPCM7XX_FUSE_DAC_CALIB (16)
|
||||
#define NPCM7XX_FUSE_ADC_CALIB (24)
|
||||
#define NPCM7XX_FUSE_DERIVATIVE (64)
|
||||
#define NPCM7XX_FUSE_TEST_SIG (72)
|
||||
#define NPCM7XX_FUSE_DIE_LOCATION (74)
|
||||
#define NPCM7XX_FUSE_GP1 (80)
|
||||
#define NPCM7XX_FUSE_GP2 (128)
|
||||
|
||||
/*
|
||||
* Number of registers in our device state structure. Don't change this without
|
||||
* incrementing the version_id in the vmstate.
|
||||
*/
|
||||
#define NPCM7XX_OTP_NR_REGS (0x18 / sizeof(uint32_t))
|
||||
|
||||
/**
|
||||
* struct NPCM7xxOTPState - Device state for one OTP module.
|
||||
* @parent: System bus device.
|
||||
* @mmio: Memory region through which registers are accessed.
|
||||
* @regs: Register contents.
|
||||
* @array: OTP storage array.
|
||||
*/
|
||||
typedef struct NPCM7xxOTPState {
|
||||
SysBusDevice parent;
|
||||
|
||||
MemoryRegion mmio;
|
||||
uint32_t regs[NPCM7XX_OTP_NR_REGS];
|
||||
uint8_t array[NPCM7XX_OTP_ARRAY_BYTES];
|
||||
} NPCM7xxOTPState;
|
||||
|
||||
#define TYPE_NPCM7XX_OTP "npcm7xx-otp"
|
||||
#define NPCM7XX_OTP(obj) OBJECT_CHECK(NPCM7xxOTPState, (obj), TYPE_NPCM7XX_OTP)
|
||||
|
||||
#define TYPE_NPCM7XX_KEY_STORAGE "npcm7xx-key-storage"
|
||||
#define TYPE_NPCM7XX_FUSE_ARRAY "npcm7xx-fuse-array"
|
||||
|
||||
typedef struct NPCM7xxOTPClass NPCM7xxOTPClass;
|
||||
|
||||
/**
|
||||
* npcm7xx_otp_array_write - ECC encode and write data to OTP array.
|
||||
* @s: OTP module.
|
||||
* @data: Data to be encoded and written.
|
||||
* @offset: Offset of first byte to be written in the OTP array.
|
||||
* @len: Number of bytes before ECC encoding.
|
||||
*
|
||||
* Each nibble of data is encoded into a byte, so the number of bytes written
|
||||
* to the array will be @len * 2.
|
||||
*/
|
||||
extern void npcm7xx_otp_array_write(NPCM7xxOTPState *s, const void *data,
|
||||
unsigned int offset, unsigned int len);
|
||||
|
||||
#endif /* NPCM7XX_OTP_H */
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Nuvoton NPCM7xx Flash Interface Unit (FIU)
|
||||
*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef NPCM7XX_FIU_H
|
||||
#define NPCM7XX_FIU_H
|
||||
|
||||
#include "hw/ssi/ssi.h"
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
/*
|
||||
* Number of registers in our device state structure. Don't change this without
|
||||
* incrementing the version_id in the vmstate.
|
||||
*/
|
||||
#define NPCM7XX_FIU_NR_REGS (0x7c / sizeof(uint32_t))
|
||||
|
||||
typedef struct NPCM7xxFIUState NPCM7xxFIUState;
|
||||
|
||||
/**
|
||||
* struct NPCM7xxFIUFlash - Per-chipselect flash controller state.
|
||||
* @direct_access: Memory region for direct flash access.
|
||||
* @fiu: Pointer to flash controller shared state.
|
||||
*/
|
||||
typedef struct NPCM7xxFIUFlash {
|
||||
MemoryRegion direct_access;
|
||||
NPCM7xxFIUState *fiu;
|
||||
} NPCM7xxFIUFlash;
|
||||
|
||||
/**
|
||||
* NPCM7xxFIUState - Device state for one Flash Interface Unit.
|
||||
* @parent: System bus device.
|
||||
* @mmio: Memory region for register access.
|
||||
* @cs_count: Number of flash chips that may be connected to this module.
|
||||
* @active_cs: Currently active chip select, or -1 if no chip is selected.
|
||||
* @cs_lines: GPIO lines that may be wired to flash chips.
|
||||
* @flash: Array of @cs_count per-flash-chip state objects.
|
||||
* @spi: The SPI bus mastered by this controller.
|
||||
* @regs: Register contents.
|
||||
*
|
||||
* Each FIU has a shared bank of registers, and controls up to four chip
|
||||
* selects. Each chip select has a dedicated memory region which may be used to
|
||||
* read and write the flash connected to that chip select as if it were memory.
|
||||
*/
|
||||
struct NPCM7xxFIUState {
|
||||
SysBusDevice parent;
|
||||
|
||||
MemoryRegion mmio;
|
||||
|
||||
int32_t cs_count;
|
||||
int32_t active_cs;
|
||||
qemu_irq *cs_lines;
|
||||
NPCM7xxFIUFlash *flash;
|
||||
|
||||
SSIBus *spi;
|
||||
|
||||
uint32_t regs[NPCM7XX_FIU_NR_REGS];
|
||||
};
|
||||
|
||||
#define TYPE_NPCM7XX_FIU "npcm7xx-fiu"
|
||||
#define NPCM7XX_FIU(obj) OBJECT_CHECK(NPCM7xxFIUState, (obj), TYPE_NPCM7XX_FIU)
|
||||
|
||||
#endif /* NPCM7XX_FIU_H */
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Nuvoton NPCM7xx Timer Controller
|
||||
*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef NPCM7XX_TIMER_H
|
||||
#define NPCM7XX_TIMER_H
|
||||
|
||||
#include "exec/memory.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "qemu/timer.h"
|
||||
|
||||
/* Each Timer Module (TIM) instance holds five 25 MHz timers. */
|
||||
#define NPCM7XX_TIMERS_PER_CTRL (5)
|
||||
|
||||
/*
|
||||
* Number of registers in our device state structure. Don't change this without
|
||||
* incrementing the version_id in the vmstate.
|
||||
*/
|
||||
#define NPCM7XX_TIMER_NR_REGS (0x54 / sizeof(uint32_t))
|
||||
|
||||
typedef struct NPCM7xxTimerCtrlState NPCM7xxTimerCtrlState;
|
||||
|
||||
/**
|
||||
* struct NPCM7xxTimer - Individual timer state.
|
||||
* @irq: GIC interrupt line to fire on expiration (if enabled).
|
||||
* @qtimer: QEMU timer that notifies us on expiration.
|
||||
* @expires_ns: Absolute virtual expiration time.
|
||||
* @remaining_ns: Remaining time until expiration if timer is paused.
|
||||
* @tcsr: The Timer Control and Status Register.
|
||||
* @ticr: The Timer Initial Count Register.
|
||||
*/
|
||||
typedef struct NPCM7xxTimer {
|
||||
NPCM7xxTimerCtrlState *ctrl;
|
||||
|
||||
qemu_irq irq;
|
||||
QEMUTimer qtimer;
|
||||
int64_t expires_ns;
|
||||
int64_t remaining_ns;
|
||||
|
||||
uint32_t tcsr;
|
||||
uint32_t ticr;
|
||||
} NPCM7xxTimer;
|
||||
|
||||
/**
|
||||
* struct NPCM7xxTimerCtrlState - Timer Module device state.
|
||||
* @parent: System bus device.
|
||||
* @iomem: Memory region through which registers are accessed.
|
||||
* @tisr: The Timer Interrupt Status Register.
|
||||
* @wtcr: The Watchdog Timer Control Register.
|
||||
* @timer: The five individual timers managed by this module.
|
||||
*/
|
||||
struct NPCM7xxTimerCtrlState {
|
||||
SysBusDevice parent;
|
||||
|
||||
MemoryRegion iomem;
|
||||
|
||||
uint32_t tisr;
|
||||
uint32_t wtcr;
|
||||
|
||||
NPCM7xxTimer timer[NPCM7XX_TIMERS_PER_CTRL];
|
||||
};
|
||||
|
||||
#define TYPE_NPCM7XX_TIMER "npcm7xx-timer"
|
||||
#define NPCM7XX_TIMER(obj) \
|
||||
OBJECT_CHECK(NPCM7xxTimerCtrlState, (obj), TYPE_NPCM7XX_TIMER)
|
||||
|
||||
#endif /* NPCM7XX_TIMER_H */
|
|
@ -71,3 +71,9 @@
|
|||
("Simplified BSD License" or "FreeBSD License", SPDX: BSD-2-Clause). OpenSBI
|
||||
source code also contains code reused from other projects desribed here:
|
||||
https://github.com/riscv/opensbi/blob/master/ThirdPartyNotices.md.
|
||||
|
||||
- npcm7xx_bootrom.bin is a simplified, free (Apache 2.0) boot ROM for Nuvoton
|
||||
NPCM7xx BMC devices. It currently implements the bare minimum to load, parse,
|
||||
initialize and run boot images stored in SPI flash, but may grow more
|
||||
features over time as needed. The source code is available at:
|
||||
https://github.com/google/vbootrom
|
||||
|
|
|
@ -81,6 +81,7 @@ blobs = files(
|
|||
'opensbi-riscv64-generic-fw_dynamic.bin',
|
||||
'opensbi-riscv32-generic-fw_dynamic.elf',
|
||||
'opensbi-riscv64-generic-fw_dynamic.elf',
|
||||
'npcm7xx_bootrom.bin',
|
||||
)
|
||||
|
||||
if install_blobs
|
||||
|
|
Binary file not shown.
|
@ -34,6 +34,7 @@ find-cross-gcc = $(firstword $(wildcard $(patsubst %ld,%gcc,$(call find-cross-ld
|
|||
# finally strip off path + toolname so we get the prefix
|
||||
find-cross-prefix = $(subst gcc,,$(notdir $(call find-cross-gcc,$(1))))
|
||||
|
||||
arm_cross_prefix := $(call find-cross-prefix,arm)
|
||||
powerpc64_cross_prefix := $(call find-cross-prefix,powerpc64)
|
||||
powerpc_cross_prefix := $(call find-cross-prefix,powerpc)
|
||||
x86_64_cross_prefix := $(call find-cross-prefix,x86_64)
|
||||
|
@ -63,6 +64,7 @@ default help:
|
|||
@echo " skiboot -- update skiboot.lid"
|
||||
@echo " u-boot.e500 -- update u-boot.e500"
|
||||
@echo " u-boot.sam460 -- update u-boot.sam460"
|
||||
@echo " npcm7xx_bootrom -- update vbootrom for npcm7xx"
|
||||
@echo " efi -- update UEFI (edk2) platform firmware"
|
||||
@echo " opensbi32-generic -- update OpenSBI for 32-bit generic machine"
|
||||
@echo " opensbi64-generic -- update OpenSBI for 64-bit generic machine"
|
||||
|
@ -185,6 +187,10 @@ bios-microvm:
|
|||
$(MAKE) -C qboot
|
||||
cp qboot/bios.bin ../pc-bios/bios-microvm.bin
|
||||
|
||||
npcm7xx_bootrom:
|
||||
$(MAKE) -C vbootrom CROSS_COMPILE=$(arm_cross_prefix)
|
||||
cp vbootrom/npcm7xx_bootrom.bin ../pc-bios/npcm7xx_bootrom.bin
|
||||
|
||||
clean:
|
||||
rm -rf seabios/.config seabios/out seabios/builds
|
||||
$(MAKE) -C sgabios clean
|
||||
|
@ -198,3 +204,4 @@ clean:
|
|||
$(MAKE) -f Makefile.edk2 clean
|
||||
$(MAKE) -C opensbi clean
|
||||
$(MAKE) -C qboot clean
|
||||
$(MAKE) -C vbootrom clean
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 0c37a43527f0ee2b9584e7fb2fdc805e902635ac
|
|
@ -42,8 +42,14 @@ output_fd = None
|
|||
insntype = 'uint32_t'
|
||||
decode_function = 'decode'
|
||||
|
||||
re_ident = '[a-zA-Z][a-zA-Z0-9_]*'
|
||||
# An identifier for C.
|
||||
re_C_ident = '[a-zA-Z][a-zA-Z0-9_]*'
|
||||
|
||||
# Identifiers for Arguments, Fields, Formats and Patterns.
|
||||
re_arg_ident = '&[a-zA-Z0-9_]*'
|
||||
re_fld_ident = '%[a-zA-Z0-9_]*'
|
||||
re_fmt_ident = '@[a-zA-Z0-9_]*'
|
||||
re_pat_ident = '[a-zA-Z0-9_]*'
|
||||
|
||||
def error_with_file(file, lineno, *args):
|
||||
"""Print an error message from file:line and args and exit."""
|
||||
|
@ -632,7 +638,6 @@ class ExcMultiPattern(MultiPattern):
|
|||
def parse_field(lineno, name, toks):
|
||||
"""Parse one instruction field from TOKS at LINENO"""
|
||||
global fields
|
||||
global re_ident
|
||||
global insnwidth
|
||||
|
||||
# A "simple" field will have only one entry;
|
||||
|
@ -641,7 +646,7 @@ def parse_field(lineno, name, toks):
|
|||
width = 0
|
||||
func = None
|
||||
for t in toks:
|
||||
if re.fullmatch('!function=' + re_ident, t):
|
||||
if re.match('^!function=', t):
|
||||
if func:
|
||||
error(lineno, 'duplicate function')
|
||||
func = t.split('=')
|
||||
|
@ -695,7 +700,7 @@ def parse_field(lineno, name, toks):
|
|||
def parse_arguments(lineno, name, toks):
|
||||
"""Parse one argument set from TOKS at LINENO"""
|
||||
global arguments
|
||||
global re_ident
|
||||
global re_C_ident
|
||||
global anyextern
|
||||
|
||||
flds = []
|
||||
|
@ -705,7 +710,7 @@ def parse_arguments(lineno, name, toks):
|
|||
extern = True
|
||||
anyextern = True
|
||||
continue
|
||||
if not re.fullmatch(re_ident, t):
|
||||
if not re.fullmatch(re_C_ident, t):
|
||||
error(lineno, 'invalid argument set token "{0}"'.format(t))
|
||||
if t in flds:
|
||||
error(lineno, 'duplicate argument "{0}"'.format(t))
|
||||
|
@ -791,7 +796,10 @@ def parse_generic(lineno, parent_pat, name, toks):
|
|||
global arguments
|
||||
global formats
|
||||
global allpatterns
|
||||
global re_ident
|
||||
global re_arg_ident
|
||||
global re_fld_ident
|
||||
global re_fmt_ident
|
||||
global re_C_ident
|
||||
global insnwidth
|
||||
global insnmask
|
||||
global variablewidth
|
||||
|
@ -807,7 +815,7 @@ def parse_generic(lineno, parent_pat, name, toks):
|
|||
fmt = None
|
||||
for t in toks:
|
||||
# '&Foo' gives a format an explcit argument set.
|
||||
if t[0] == '&':
|
||||
if re.fullmatch(re_arg_ident, t):
|
||||
tt = t[1:]
|
||||
if arg:
|
||||
error(lineno, 'multiple argument sets')
|
||||
|
@ -818,7 +826,7 @@ def parse_generic(lineno, parent_pat, name, toks):
|
|||
continue
|
||||
|
||||
# '@Foo' gives a pattern an explicit format.
|
||||
if t[0] == '@':
|
||||
if re.fullmatch(re_fmt_ident, t):
|
||||
tt = t[1:]
|
||||
if fmt:
|
||||
error(lineno, 'multiple formats')
|
||||
|
@ -829,19 +837,19 @@ def parse_generic(lineno, parent_pat, name, toks):
|
|||
continue
|
||||
|
||||
# '%Foo' imports a field.
|
||||
if t[0] == '%':
|
||||
if re.fullmatch(re_fld_ident, t):
|
||||
tt = t[1:]
|
||||
flds = add_field_byname(lineno, flds, tt, tt)
|
||||
continue
|
||||
|
||||
# 'Foo=%Bar' imports a field with a different name.
|
||||
if re.fullmatch(re_ident + '=%' + re_ident, t):
|
||||
if re.fullmatch(re_C_ident + '=' + re_fld_ident, t):
|
||||
(fname, iname) = t.split('=%')
|
||||
flds = add_field_byname(lineno, flds, fname, iname)
|
||||
continue
|
||||
|
||||
# 'Foo=number' sets an argument field to a constant value
|
||||
if re.fullmatch(re_ident + '=[+-]?[0-9]+', t):
|
||||
if re.fullmatch(re_C_ident + '=[+-]?[0-9]+', t):
|
||||
(fname, value) = t.split('=')
|
||||
value = int(value)
|
||||
flds = add_field(lineno, flds, fname, ConstField(value))
|
||||
|
@ -866,7 +874,7 @@ def parse_generic(lineno, parent_pat, name, toks):
|
|||
fixedmask = (fixedmask << shift) | fms
|
||||
undefmask = (undefmask << shift) | ubm
|
||||
# Otherwise, fieldname:fieldwidth
|
||||
elif re.fullmatch(re_ident + ':s?[0-9]+', t):
|
||||
elif re.fullmatch(re_C_ident + ':s?[0-9]+', t):
|
||||
(fname, flen) = t.split(':')
|
||||
sign = False
|
||||
if flen[0] == 's':
|
||||
|
@ -971,6 +979,10 @@ def parse_generic(lineno, parent_pat, name, toks):
|
|||
|
||||
def parse_file(f, parent_pat):
|
||||
"""Parse all of the patterns within a file"""
|
||||
global re_arg_ident
|
||||
global re_fld_ident
|
||||
global re_fmt_ident
|
||||
global re_pat_ident
|
||||
|
||||
# Read all of the lines of the file. Concatenate lines
|
||||
# ending in backslash; discard empty lines and comments.
|
||||
|
@ -1063,14 +1075,16 @@ def parse_file(f, parent_pat):
|
|||
continue
|
||||
|
||||
# Determine the type of object needing to be parsed.
|
||||
if name[0] == '%':
|
||||
if re.fullmatch(re_fld_ident, name):
|
||||
parse_field(start_lineno, name[1:], toks)
|
||||
elif name[0] == '&':
|
||||
elif re.fullmatch(re_arg_ident, name):
|
||||
parse_arguments(start_lineno, name[1:], toks)
|
||||
elif name[0] == '@':
|
||||
elif re.fullmatch(re_fmt_ident, name):
|
||||
parse_generic(start_lineno, None, name[1:], toks)
|
||||
else:
|
||||
elif re.fullmatch(re_pat_ident, name):
|
||||
parse_generic(start_lineno, parent_pat, name, toks)
|
||||
else:
|
||||
error(lineno, 'invalid token "{0}"'.format(name))
|
||||
toks = []
|
||||
|
||||
if nesting != 0:
|
||||
|
|
101
target/arm/cpu.c
101
target/arm/cpu.c
|
@ -2098,72 +2098,69 @@ static void cortex_a15_initfn(Object *obj)
|
|||
}
|
||||
|
||||
#ifndef TARGET_AARCH64
|
||||
/* -cpu max: if KVM is enabled, like -cpu host (best possible with this host);
|
||||
* otherwise, a CPU with as many features enabled as our emulation supports.
|
||||
/*
|
||||
* -cpu max: a CPU with as many features enabled as our emulation supports.
|
||||
* The version of '-cpu max' for qemu-system-aarch64 is defined in cpu64.c;
|
||||
* this only needs to handle 32 bits.
|
||||
* this only needs to handle 32 bits, and need not care about KVM.
|
||||
*/
|
||||
static void arm_max_initfn(Object *obj)
|
||||
{
|
||||
ARMCPU *cpu = ARM_CPU(obj);
|
||||
|
||||
if (kvm_enabled()) {
|
||||
kvm_arm_set_cpu_features_from_host(cpu);
|
||||
} else {
|
||||
cortex_a15_initfn(obj);
|
||||
cortex_a15_initfn(obj);
|
||||
|
||||
/* old-style VFP short-vector support */
|
||||
cpu->isar.mvfr0 = FIELD_DP32(cpu->isar.mvfr0, MVFR0, FPSHVEC, 1);
|
||||
/* old-style VFP short-vector support */
|
||||
cpu->isar.mvfr0 = FIELD_DP32(cpu->isar.mvfr0, MVFR0, FPSHVEC, 1);
|
||||
|
||||
#ifdef CONFIG_USER_ONLY
|
||||
/* We don't set these in system emulation mode for the moment,
|
||||
* since we don't correctly set (all of) the ID registers to
|
||||
* advertise them.
|
||||
*/
|
||||
set_feature(&cpu->env, ARM_FEATURE_V8);
|
||||
{
|
||||
uint32_t t;
|
||||
/*
|
||||
* We don't set these in system emulation mode for the moment,
|
||||
* since we don't correctly set (all of) the ID registers to
|
||||
* advertise them.
|
||||
*/
|
||||
set_feature(&cpu->env, ARM_FEATURE_V8);
|
||||
{
|
||||
uint32_t t;
|
||||
|
||||
t = cpu->isar.id_isar5;
|
||||
t = FIELD_DP32(t, ID_ISAR5, AES, 2);
|
||||
t = FIELD_DP32(t, ID_ISAR5, SHA1, 1);
|
||||
t = FIELD_DP32(t, ID_ISAR5, SHA2, 1);
|
||||
t = FIELD_DP32(t, ID_ISAR5, CRC32, 1);
|
||||
t = FIELD_DP32(t, ID_ISAR5, RDM, 1);
|
||||
t = FIELD_DP32(t, ID_ISAR5, VCMA, 1);
|
||||
cpu->isar.id_isar5 = t;
|
||||
t = cpu->isar.id_isar5;
|
||||
t = FIELD_DP32(t, ID_ISAR5, AES, 2);
|
||||
t = FIELD_DP32(t, ID_ISAR5, SHA1, 1);
|
||||
t = FIELD_DP32(t, ID_ISAR5, SHA2, 1);
|
||||
t = FIELD_DP32(t, ID_ISAR5, CRC32, 1);
|
||||
t = FIELD_DP32(t, ID_ISAR5, RDM, 1);
|
||||
t = FIELD_DP32(t, ID_ISAR5, VCMA, 1);
|
||||
cpu->isar.id_isar5 = t;
|
||||
|
||||
t = cpu->isar.id_isar6;
|
||||
t = FIELD_DP32(t, ID_ISAR6, JSCVT, 1);
|
||||
t = FIELD_DP32(t, ID_ISAR6, DP, 1);
|
||||
t = FIELD_DP32(t, ID_ISAR6, FHM, 1);
|
||||
t = FIELD_DP32(t, ID_ISAR6, SB, 1);
|
||||
t = FIELD_DP32(t, ID_ISAR6, SPECRES, 1);
|
||||
cpu->isar.id_isar6 = t;
|
||||
t = cpu->isar.id_isar6;
|
||||
t = FIELD_DP32(t, ID_ISAR6, JSCVT, 1);
|
||||
t = FIELD_DP32(t, ID_ISAR6, DP, 1);
|
||||
t = FIELD_DP32(t, ID_ISAR6, FHM, 1);
|
||||
t = FIELD_DP32(t, ID_ISAR6, SB, 1);
|
||||
t = FIELD_DP32(t, ID_ISAR6, SPECRES, 1);
|
||||
cpu->isar.id_isar6 = t;
|
||||
|
||||
t = cpu->isar.mvfr1;
|
||||
t = FIELD_DP32(t, MVFR1, FPHP, 3); /* v8.2-FP16 */
|
||||
t = FIELD_DP32(t, MVFR1, SIMDHP, 2); /* v8.2-FP16 */
|
||||
cpu->isar.mvfr1 = t;
|
||||
t = cpu->isar.mvfr1;
|
||||
t = FIELD_DP32(t, MVFR1, FPHP, 3); /* v8.2-FP16 */
|
||||
t = FIELD_DP32(t, MVFR1, SIMDHP, 2); /* v8.2-FP16 */
|
||||
cpu->isar.mvfr1 = t;
|
||||
|
||||
t = cpu->isar.mvfr2;
|
||||
t = FIELD_DP32(t, MVFR2, SIMDMISC, 3); /* SIMD MaxNum */
|
||||
t = FIELD_DP32(t, MVFR2, FPMISC, 4); /* FP MaxNum */
|
||||
cpu->isar.mvfr2 = t;
|
||||
t = cpu->isar.mvfr2;
|
||||
t = FIELD_DP32(t, MVFR2, SIMDMISC, 3); /* SIMD MaxNum */
|
||||
t = FIELD_DP32(t, MVFR2, FPMISC, 4); /* FP MaxNum */
|
||||
cpu->isar.mvfr2 = t;
|
||||
|
||||
t = cpu->isar.id_mmfr3;
|
||||
t = FIELD_DP32(t, ID_MMFR3, PAN, 2); /* ATS1E1 */
|
||||
cpu->isar.id_mmfr3 = t;
|
||||
t = cpu->isar.id_mmfr3;
|
||||
t = FIELD_DP32(t, ID_MMFR3, PAN, 2); /* ATS1E1 */
|
||||
cpu->isar.id_mmfr3 = t;
|
||||
|
||||
t = cpu->isar.id_mmfr4;
|
||||
t = FIELD_DP32(t, ID_MMFR4, HPDS, 1); /* AA32HPD */
|
||||
t = FIELD_DP32(t, ID_MMFR4, AC2, 1); /* ACTLR2, HACTLR2 */
|
||||
t = FIELD_DP32(t, ID_MMFR4, CNP, 1); /* TTCNP */
|
||||
t = FIELD_DP32(t, ID_MMFR4, XNX, 1); /* TTS2UXN */
|
||||
cpu->isar.id_mmfr4 = t;
|
||||
}
|
||||
#endif
|
||||
t = cpu->isar.id_mmfr4;
|
||||
t = FIELD_DP32(t, ID_MMFR4, HPDS, 1); /* AA32HPD */
|
||||
t = FIELD_DP32(t, ID_MMFR4, AC2, 1); /* ACTLR2, HACTLR2 */
|
||||
t = FIELD_DP32(t, ID_MMFR4, CNP, 1); /* TTCNP */
|
||||
t = FIELD_DP32(t, ID_MMFR4, XNX, 1); /* TTS2UXN */
|
||||
cpu->isar.id_mmfr4 = t;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -2267,11 +2264,7 @@ static void arm_host_initfn(Object *obj)
|
|||
|
||||
static const TypeInfo host_arm_cpu_type_info = {
|
||||
.name = TYPE_ARM_HOST_CPU,
|
||||
#ifdef TARGET_AARCH64
|
||||
.parent = TYPE_AARCH64_CPU,
|
||||
#else
|
||||
.parent = TYPE_ARM_CPU,
|
||||
#endif
|
||||
.instance_init = arm_host_initfn,
|
||||
};
|
||||
|
||||
|
|
|
@ -1452,7 +1452,7 @@ static bool pmu_counter_enabled(CPUARMState *env, uint8_t counter)
|
|||
}
|
||||
} else {
|
||||
prohibited = arm_feature(env, ARM_FEATURE_EL3) &&
|
||||
(env->cp15.mdcr_el3 & MDCR_SPME);
|
||||
!(env->cp15.mdcr_el3 & MDCR_SPME);
|
||||
}
|
||||
|
||||
if (prohibited && counter == 31) {
|
||||
|
|
|
@ -136,16 +136,11 @@ MISMATCH_CHECK(QEMU_PSCI_RET_DISABLED, PSCI_RET_DISABLED);
|
|||
*/
|
||||
#define QEMU_KVM_ARM_TARGET_NONE UINT_MAX
|
||||
|
||||
#ifdef TARGET_AARCH64
|
||||
MISMATCH_CHECK(QEMU_KVM_ARM_TARGET_AEM_V8, KVM_ARM_TARGET_AEM_V8);
|
||||
MISMATCH_CHECK(QEMU_KVM_ARM_TARGET_FOUNDATION_V8, KVM_ARM_TARGET_FOUNDATION_V8);
|
||||
MISMATCH_CHECK(QEMU_KVM_ARM_TARGET_CORTEX_A57, KVM_ARM_TARGET_CORTEX_A57);
|
||||
MISMATCH_CHECK(QEMU_KVM_ARM_TARGET_XGENE_POTENZA, KVM_ARM_TARGET_XGENE_POTENZA);
|
||||
MISMATCH_CHECK(QEMU_KVM_ARM_TARGET_CORTEX_A53, KVM_ARM_TARGET_CORTEX_A53);
|
||||
#else
|
||||
MISMATCH_CHECK(QEMU_KVM_ARM_TARGET_CORTEX_A15, KVM_ARM_TARGET_CORTEX_A15);
|
||||
MISMATCH_CHECK(QEMU_KVM_ARM_TARGET_CORTEX_A7, KVM_ARM_TARGET_CORTEX_A7);
|
||||
#endif
|
||||
|
||||
#define CP_REG_ARM64 0x6000000000000000ULL
|
||||
#define CP_REG_ARM_COPROC_MASK 0x000000000FFF0000
|
||||
|
@ -165,7 +160,6 @@ MISMATCH_CHECK(QEMU_KVM_ARM_TARGET_CORTEX_A7, KVM_ARM_TARGET_CORTEX_A7);
|
|||
/* No kernel define but it's useful to QEMU */
|
||||
#define CP_REG_ARM64_SYSREG_CP (CP_REG_ARM64_SYSREG >> CP_REG_ARM_COPROC_SHIFT)
|
||||
|
||||
#ifdef TARGET_AARCH64
|
||||
MISMATCH_CHECK(CP_REG_ARM64, KVM_REG_ARM64);
|
||||
MISMATCH_CHECK(CP_REG_ARM_COPROC_MASK, KVM_REG_ARM_COPROC_MASK);
|
||||
MISMATCH_CHECK(CP_REG_ARM_COPROC_SHIFT, KVM_REG_ARM_COPROC_SHIFT);
|
||||
|
@ -180,7 +174,6 @@ MISMATCH_CHECK(CP_REG_ARM64_SYSREG_CRM_MASK, KVM_REG_ARM64_SYSREG_CRM_MASK);
|
|||
MISMATCH_CHECK(CP_REG_ARM64_SYSREG_CRM_SHIFT, KVM_REG_ARM64_SYSREG_CRM_SHIFT);
|
||||
MISMATCH_CHECK(CP_REG_ARM64_SYSREG_OP2_MASK, KVM_REG_ARM64_SYSREG_OP2_MASK);
|
||||
MISMATCH_CHECK(CP_REG_ARM64_SYSREG_OP2_SHIFT, KVM_REG_ARM64_SYSREG_OP2_SHIFT);
|
||||
#endif
|
||||
|
||||
#undef MISMATCH_CHECK
|
||||
|
||||
|
|
|
@ -918,22 +918,15 @@ int kvm_arch_process_async_events(CPUState *cs)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* The #ifdef protections are until 32bit headers are imported and can
|
||||
* be removed once both 32 and 64 bit reach feature parity.
|
||||
*/
|
||||
void kvm_arch_update_guest_debug(CPUState *cs, struct kvm_guest_debug *dbg)
|
||||
{
|
||||
#ifdef KVM_GUESTDBG_USE_SW_BP
|
||||
if (kvm_sw_breakpoints_active(cs)) {
|
||||
dbg->control |= KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_SW_BP;
|
||||
}
|
||||
#endif
|
||||
#ifdef KVM_GUESTDBG_USE_HW
|
||||
if (kvm_arm_hw_debug_active(cs)) {
|
||||
dbg->control |= KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW;
|
||||
kvm_arm_copy_hw_debug_data(&dbg->arch);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void kvm_arch_init_irq_routing(KVMState *s)
|
||||
|
|
|
@ -1,595 +0,0 @@
|
|||
/*
|
||||
* ARM implementation of KVM hooks, 32 bit specific code.
|
||||
*
|
||||
* Copyright Christoffer Dall 2009-2010
|
||||
*
|
||||
* 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 <sys/ioctl.h>
|
||||
|
||||
#include <linux/kvm.h>
|
||||
|
||||
#include "qemu-common.h"
|
||||
#include "cpu.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "sysemu/runstate.h"
|
||||
#include "sysemu/kvm.h"
|
||||
#include "kvm_arm.h"
|
||||
#include "internals.h"
|
||||
#include "qemu/log.h"
|
||||
|
||||
static int read_sys_reg32(int fd, uint32_t *pret, uint64_t id)
|
||||
{
|
||||
struct kvm_one_reg idreg = { .id = id, .addr = (uintptr_t)pret };
|
||||
|
||||
assert((id & KVM_REG_SIZE_MASK) == KVM_REG_SIZE_U32);
|
||||
return ioctl(fd, KVM_GET_ONE_REG, &idreg);
|
||||
}
|
||||
|
||||
bool kvm_arm_get_host_cpu_features(ARMHostCPUFeatures *ahcf)
|
||||
{
|
||||
/* Identify the feature bits corresponding to the host CPU, and
|
||||
* fill out the ARMHostCPUClass fields accordingly. To do this
|
||||
* we have to create a scratch VM, create a single CPU inside it,
|
||||
* and then query that CPU for the relevant ID registers.
|
||||
*/
|
||||
int err = 0, fdarray[3];
|
||||
uint32_t midr, id_pfr0;
|
||||
uint64_t features = 0;
|
||||
|
||||
/* Old kernels may not know about the PREFERRED_TARGET ioctl: however
|
||||
* we know these will only support creating one kind of guest CPU,
|
||||
* which is its preferred CPU type.
|
||||
*/
|
||||
static const uint32_t cpus_to_try[] = {
|
||||
QEMU_KVM_ARM_TARGET_CORTEX_A15,
|
||||
QEMU_KVM_ARM_TARGET_NONE
|
||||
};
|
||||
/*
|
||||
* target = -1 informs kvm_arm_create_scratch_host_vcpu()
|
||||
* to use the preferred target
|
||||
*/
|
||||
struct kvm_vcpu_init init = { .target = -1, };
|
||||
|
||||
if (!kvm_arm_create_scratch_host_vcpu(cpus_to_try, fdarray, &init)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ahcf->target = init.target;
|
||||
|
||||
/* This is not strictly blessed by the device tree binding docs yet,
|
||||
* but in practice the kernel does not care about this string so
|
||||
* there is no point maintaining an KVM_ARM_TARGET_* -> string table.
|
||||
*/
|
||||
ahcf->dtb_compatible = "arm,arm-v7";
|
||||
|
||||
err |= read_sys_reg32(fdarray[2], &midr, ARM_CP15_REG32(0, 0, 0, 0));
|
||||
err |= read_sys_reg32(fdarray[2], &id_pfr0, ARM_CP15_REG32(0, 0, 1, 0));
|
||||
|
||||
err |= read_sys_reg32(fdarray[2], &ahcf->isar.id_isar0,
|
||||
ARM_CP15_REG32(0, 0, 2, 0));
|
||||
err |= read_sys_reg32(fdarray[2], &ahcf->isar.id_isar1,
|
||||
ARM_CP15_REG32(0, 0, 2, 1));
|
||||
err |= read_sys_reg32(fdarray[2], &ahcf->isar.id_isar2,
|
||||
ARM_CP15_REG32(0, 0, 2, 2));
|
||||
err |= read_sys_reg32(fdarray[2], &ahcf->isar.id_isar3,
|
||||
ARM_CP15_REG32(0, 0, 2, 3));
|
||||
err |= read_sys_reg32(fdarray[2], &ahcf->isar.id_isar4,
|
||||
ARM_CP15_REG32(0, 0, 2, 4));
|
||||
err |= read_sys_reg32(fdarray[2], &ahcf->isar.id_isar5,
|
||||
ARM_CP15_REG32(0, 0, 2, 5));
|
||||
if (read_sys_reg32(fdarray[2], &ahcf->isar.id_isar6,
|
||||
ARM_CP15_REG32(0, 0, 2, 7))) {
|
||||
/*
|
||||
* Older kernels don't support reading ID_ISAR6. This register was
|
||||
* only introduced in ARMv8, so we can assume that it is zero on a
|
||||
* CPU that a kernel this old is running on.
|
||||
*/
|
||||
ahcf->isar.id_isar6 = 0;
|
||||
}
|
||||
|
||||
err |= read_sys_reg32(fdarray[2], &ahcf->isar.id_dfr0,
|
||||
ARM_CP15_REG32(0, 0, 1, 2));
|
||||
|
||||
err |= read_sys_reg32(fdarray[2], &ahcf->isar.mvfr0,
|
||||
KVM_REG_ARM | KVM_REG_SIZE_U32 |
|
||||
KVM_REG_ARM_VFP | KVM_REG_ARM_VFP_MVFR0);
|
||||
err |= read_sys_reg32(fdarray[2], &ahcf->isar.mvfr1,
|
||||
KVM_REG_ARM | KVM_REG_SIZE_U32 |
|
||||
KVM_REG_ARM_VFP | KVM_REG_ARM_VFP_MVFR1);
|
||||
/*
|
||||
* FIXME: There is not yet a way to read MVFR2.
|
||||
* Fortunately there is not yet anything in there that affects migration.
|
||||
*/
|
||||
|
||||
err |= read_sys_reg32(fdarray[2], &ahcf->isar.id_mmfr0,
|
||||
ARM_CP15_REG32(0, 0, 1, 4));
|
||||
err |= read_sys_reg32(fdarray[2], &ahcf->isar.id_mmfr1,
|
||||
ARM_CP15_REG32(0, 0, 1, 5));
|
||||
err |= read_sys_reg32(fdarray[2], &ahcf->isar.id_mmfr2,
|
||||
ARM_CP15_REG32(0, 0, 1, 6));
|
||||
err |= read_sys_reg32(fdarray[2], &ahcf->isar.id_mmfr3,
|
||||
ARM_CP15_REG32(0, 0, 1, 7));
|
||||
if (read_sys_reg32(fdarray[2], &ahcf->isar.id_mmfr4,
|
||||
ARM_CP15_REG32(0, 0, 2, 6))) {
|
||||
/*
|
||||
* Older kernels don't support reading ID_MMFR4 (a new in v8
|
||||
* register); assume it's zero.
|
||||
*/
|
||||
ahcf->isar.id_mmfr4 = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* There is no way to read DBGDIDR, because currently 32-bit KVM
|
||||
* doesn't implement debug at all. Leave it at zero.
|
||||
*/
|
||||
|
||||
kvm_arm_destroy_scratch_host_vcpu(fdarray);
|
||||
|
||||
if (err < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Now we've retrieved all the register information we can
|
||||
* set the feature bits based on the ID register fields.
|
||||
* We can assume any KVM supporting CPU is at least a v7
|
||||
* with VFPv3, virtualization extensions, and the generic
|
||||
* timers; this in turn implies most of the other feature
|
||||
* bits, but a few must be tested.
|
||||
*/
|
||||
features |= 1ULL << ARM_FEATURE_V7VE;
|
||||
features |= 1ULL << ARM_FEATURE_GENERIC_TIMER;
|
||||
|
||||
if (extract32(id_pfr0, 12, 4) == 1) {
|
||||
features |= 1ULL << ARM_FEATURE_THUMB2EE;
|
||||
}
|
||||
if (extract32(ahcf->isar.mvfr1, 12, 4) == 1) {
|
||||
features |= 1ULL << ARM_FEATURE_NEON;
|
||||
}
|
||||
|
||||
ahcf->features = features;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool kvm_arm_reg_syncs_via_cpreg_list(uint64_t regidx)
|
||||
{
|
||||
/* Return true if the regidx is a register we should synchronize
|
||||
* via the cpreg_tuples array (ie is not a core reg we sync by
|
||||
* hand in kvm_arch_get/put_registers())
|
||||
*/
|
||||
switch (regidx & KVM_REG_ARM_COPROC_MASK) {
|
||||
case KVM_REG_ARM_CORE:
|
||||
case KVM_REG_ARM_VFP:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct CPRegStateLevel {
|
||||
uint64_t regidx;
|
||||
int level;
|
||||
} CPRegStateLevel;
|
||||
|
||||
/* All coprocessor registers not listed in the following table are assumed to
|
||||
* be of the level KVM_PUT_RUNTIME_STATE. If a register should be written less
|
||||
* often, you must add it to this table with a state of either
|
||||
* KVM_PUT_RESET_STATE or KVM_PUT_FULL_STATE.
|
||||
*/
|
||||
static const CPRegStateLevel non_runtime_cpregs[] = {
|
||||
{ KVM_REG_ARM_TIMER_CNT, KVM_PUT_FULL_STATE },
|
||||
};
|
||||
|
||||
int kvm_arm_cpreg_level(uint64_t regidx)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(non_runtime_cpregs); i++) {
|
||||
const CPRegStateLevel *l = &non_runtime_cpregs[i];
|
||||
if (l->regidx == regidx) {
|
||||
return l->level;
|
||||
}
|
||||
}
|
||||
|
||||
return KVM_PUT_RUNTIME_STATE;
|
||||
}
|
||||
|
||||
#define ARM_CPU_ID_MPIDR 0, 0, 0, 5
|
||||
|
||||
int kvm_arch_init_vcpu(CPUState *cs)
|
||||
{
|
||||
int ret;
|
||||
uint64_t v;
|
||||
uint32_t mpidr;
|
||||
struct kvm_one_reg r;
|
||||
ARMCPU *cpu = ARM_CPU(cs);
|
||||
|
||||
if (cpu->kvm_target == QEMU_KVM_ARM_TARGET_NONE) {
|
||||
fprintf(stderr, "KVM is not supported for this guest CPU type\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
qemu_add_vm_change_state_handler(kvm_arm_vm_state_change, cs);
|
||||
|
||||
/* Determine init features for this CPU */
|
||||
memset(cpu->kvm_init_features, 0, sizeof(cpu->kvm_init_features));
|
||||
if (cs->start_powered_off) {
|
||||
cpu->kvm_init_features[0] |= 1 << KVM_ARM_VCPU_POWER_OFF;
|
||||
}
|
||||
if (kvm_check_extension(cs->kvm_state, KVM_CAP_ARM_PSCI_0_2)) {
|
||||
cpu->psci_version = 2;
|
||||
cpu->kvm_init_features[0] |= 1 << KVM_ARM_VCPU_PSCI_0_2;
|
||||
}
|
||||
|
||||
/* Do KVM_ARM_VCPU_INIT ioctl */
|
||||
ret = kvm_arm_vcpu_init(cs);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Query the kernel to make sure it supports 32 VFP
|
||||
* registers: QEMU's "cortex-a15" CPU is always a
|
||||
* VFP-D32 core. The simplest way to do this is just
|
||||
* to attempt to read register d31.
|
||||
*/
|
||||
r.id = KVM_REG_ARM | KVM_REG_SIZE_U64 | KVM_REG_ARM_VFP | 31;
|
||||
r.addr = (uintptr_t)(&v);
|
||||
ret = kvm_vcpu_ioctl(cs, KVM_GET_ONE_REG, &r);
|
||||
if (ret == -ENOENT) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* When KVM is in use, PSCI is emulated in-kernel and not by qemu.
|
||||
* Currently KVM has its own idea about MPIDR assignment, so we
|
||||
* override our defaults with what we get from KVM.
|
||||
*/
|
||||
ret = kvm_get_one_reg(cs, ARM_CP15_REG32(ARM_CPU_ID_MPIDR), &mpidr);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
cpu->mp_affinity = mpidr & ARM32_AFFINITY_MASK;
|
||||
|
||||
/* Check whether userspace can specify guest syndrome value */
|
||||
kvm_arm_init_serror_injection(cs);
|
||||
|
||||
return kvm_arm_init_cpreg_list(cpu);
|
||||
}
|
||||
|
||||
int kvm_arch_destroy_vcpu(CPUState *cs)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct Reg {
|
||||
uint64_t id;
|
||||
int offset;
|
||||
} Reg;
|
||||
|
||||
#define COREREG(KERNELNAME, QEMUFIELD) \
|
||||
{ \
|
||||
KVM_REG_ARM | KVM_REG_SIZE_U32 | \
|
||||
KVM_REG_ARM_CORE | KVM_REG_ARM_CORE_REG(KERNELNAME), \
|
||||
offsetof(CPUARMState, QEMUFIELD) \
|
||||
}
|
||||
|
||||
#define VFPSYSREG(R) \
|
||||
{ \
|
||||
KVM_REG_ARM | KVM_REG_SIZE_U32 | KVM_REG_ARM_VFP | \
|
||||
KVM_REG_ARM_VFP_##R, \
|
||||
offsetof(CPUARMState, vfp.xregs[ARM_VFP_##R]) \
|
||||
}
|
||||
|
||||
/* Like COREREG, but handle fields which are in a uint64_t in CPUARMState. */
|
||||
#define COREREG64(KERNELNAME, QEMUFIELD) \
|
||||
{ \
|
||||
KVM_REG_ARM | KVM_REG_SIZE_U32 | \
|
||||
KVM_REG_ARM_CORE | KVM_REG_ARM_CORE_REG(KERNELNAME), \
|
||||
offsetoflow32(CPUARMState, QEMUFIELD) \
|
||||
}
|
||||
|
||||
static const Reg regs[] = {
|
||||
/* R0_usr .. R14_usr */
|
||||
COREREG(usr_regs.uregs[0], regs[0]),
|
||||
COREREG(usr_regs.uregs[1], regs[1]),
|
||||
COREREG(usr_regs.uregs[2], regs[2]),
|
||||
COREREG(usr_regs.uregs[3], regs[3]),
|
||||
COREREG(usr_regs.uregs[4], regs[4]),
|
||||
COREREG(usr_regs.uregs[5], regs[5]),
|
||||
COREREG(usr_regs.uregs[6], regs[6]),
|
||||
COREREG(usr_regs.uregs[7], regs[7]),
|
||||
COREREG(usr_regs.uregs[8], usr_regs[0]),
|
||||
COREREG(usr_regs.uregs[9], usr_regs[1]),
|
||||
COREREG(usr_regs.uregs[10], usr_regs[2]),
|
||||
COREREG(usr_regs.uregs[11], usr_regs[3]),
|
||||
COREREG(usr_regs.uregs[12], usr_regs[4]),
|
||||
COREREG(usr_regs.uregs[13], banked_r13[BANK_USRSYS]),
|
||||
COREREG(usr_regs.uregs[14], banked_r14[BANK_USRSYS]),
|
||||
/* R13, R14, SPSR for SVC, ABT, UND, IRQ banks */
|
||||
COREREG(svc_regs[0], banked_r13[BANK_SVC]),
|
||||
COREREG(svc_regs[1], banked_r14[BANK_SVC]),
|
||||
COREREG64(svc_regs[2], banked_spsr[BANK_SVC]),
|
||||
COREREG(abt_regs[0], banked_r13[BANK_ABT]),
|
||||
COREREG(abt_regs[1], banked_r14[BANK_ABT]),
|
||||
COREREG64(abt_regs[2], banked_spsr[BANK_ABT]),
|
||||
COREREG(und_regs[0], banked_r13[BANK_UND]),
|
||||
COREREG(und_regs[1], banked_r14[BANK_UND]),
|
||||
COREREG64(und_regs[2], banked_spsr[BANK_UND]),
|
||||
COREREG(irq_regs[0], banked_r13[BANK_IRQ]),
|
||||
COREREG(irq_regs[1], banked_r14[BANK_IRQ]),
|
||||
COREREG64(irq_regs[2], banked_spsr[BANK_IRQ]),
|
||||
/* R8_fiq .. R14_fiq and SPSR_fiq */
|
||||
COREREG(fiq_regs[0], fiq_regs[0]),
|
||||
COREREG(fiq_regs[1], fiq_regs[1]),
|
||||
COREREG(fiq_regs[2], fiq_regs[2]),
|
||||
COREREG(fiq_regs[3], fiq_regs[3]),
|
||||
COREREG(fiq_regs[4], fiq_regs[4]),
|
||||
COREREG(fiq_regs[5], banked_r13[BANK_FIQ]),
|
||||
COREREG(fiq_regs[6], banked_r14[BANK_FIQ]),
|
||||
COREREG64(fiq_regs[7], banked_spsr[BANK_FIQ]),
|
||||
/* R15 */
|
||||
COREREG(usr_regs.uregs[15], regs[15]),
|
||||
/* VFP system registers */
|
||||
VFPSYSREG(FPSID),
|
||||
VFPSYSREG(MVFR1),
|
||||
VFPSYSREG(MVFR0),
|
||||
VFPSYSREG(FPEXC),
|
||||
VFPSYSREG(FPINST),
|
||||
VFPSYSREG(FPINST2),
|
||||
};
|
||||
|
||||
int kvm_arch_put_registers(CPUState *cs, int level)
|
||||
{
|
||||
ARMCPU *cpu = ARM_CPU(cs);
|
||||
CPUARMState *env = &cpu->env;
|
||||
struct kvm_one_reg r;
|
||||
int mode, bn;
|
||||
int ret, i;
|
||||
uint32_t cpsr, fpscr;
|
||||
|
||||
/* Make sure the banked regs are properly set */
|
||||
mode = env->uncached_cpsr & CPSR_M;
|
||||
bn = bank_number(mode);
|
||||
if (mode == ARM_CPU_MODE_FIQ) {
|
||||
memcpy(env->fiq_regs, env->regs + 8, 5 * sizeof(uint32_t));
|
||||
} else {
|
||||
memcpy(env->usr_regs, env->regs + 8, 5 * sizeof(uint32_t));
|
||||
}
|
||||
env->banked_r13[bn] = env->regs[13];
|
||||
env->banked_spsr[bn] = env->spsr;
|
||||
env->banked_r14[r14_bank_number(mode)] = env->regs[14];
|
||||
|
||||
/* Now we can safely copy stuff down to the kernel */
|
||||
for (i = 0; i < ARRAY_SIZE(regs); i++) {
|
||||
r.id = regs[i].id;
|
||||
r.addr = (uintptr_t)(env) + regs[i].offset;
|
||||
ret = kvm_vcpu_ioctl(cs, KVM_SET_ONE_REG, &r);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* Special cases which aren't a single CPUARMState field */
|
||||
cpsr = cpsr_read(env);
|
||||
r.id = KVM_REG_ARM | KVM_REG_SIZE_U32 |
|
||||
KVM_REG_ARM_CORE | KVM_REG_ARM_CORE_REG(usr_regs.ARM_cpsr);
|
||||
r.addr = (uintptr_t)(&cpsr);
|
||||
ret = kvm_vcpu_ioctl(cs, KVM_SET_ONE_REG, &r);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* VFP registers */
|
||||
r.id = KVM_REG_ARM | KVM_REG_SIZE_U64 | KVM_REG_ARM_VFP;
|
||||
for (i = 0; i < 32; i++) {
|
||||
r.addr = (uintptr_t)aa32_vfp_dreg(env, i);
|
||||
ret = kvm_vcpu_ioctl(cs, KVM_SET_ONE_REG, &r);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
r.id++;
|
||||
}
|
||||
|
||||
r.id = KVM_REG_ARM | KVM_REG_SIZE_U32 | KVM_REG_ARM_VFP |
|
||||
KVM_REG_ARM_VFP_FPSCR;
|
||||
fpscr = vfp_get_fpscr(env);
|
||||
r.addr = (uintptr_t)&fpscr;
|
||||
ret = kvm_vcpu_ioctl(cs, KVM_SET_ONE_REG, &r);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
write_cpustate_to_list(cpu, true);
|
||||
|
||||
if (!write_list_to_kvmstate(cpu, level)) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Setting VCPU events should be triggered after syncing the registers
|
||||
* to avoid overwriting potential changes made by KVM upon calling
|
||||
* KVM_SET_VCPU_EVENTS ioctl
|
||||
*/
|
||||
ret = kvm_put_vcpu_events(cpu);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
kvm_arm_sync_mpstate_to_kvm(cpu);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int kvm_arch_get_registers(CPUState *cs)
|
||||
{
|
||||
ARMCPU *cpu = ARM_CPU(cs);
|
||||
CPUARMState *env = &cpu->env;
|
||||
struct kvm_one_reg r;
|
||||
int mode, bn;
|
||||
int ret, i;
|
||||
uint32_t cpsr, fpscr;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(regs); i++) {
|
||||
r.id = regs[i].id;
|
||||
r.addr = (uintptr_t)(env) + regs[i].offset;
|
||||
ret = kvm_vcpu_ioctl(cs, KVM_GET_ONE_REG, &r);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* Special cases which aren't a single CPUARMState field */
|
||||
r.id = KVM_REG_ARM | KVM_REG_SIZE_U32 |
|
||||
KVM_REG_ARM_CORE | KVM_REG_ARM_CORE_REG(usr_regs.ARM_cpsr);
|
||||
r.addr = (uintptr_t)(&cpsr);
|
||||
ret = kvm_vcpu_ioctl(cs, KVM_GET_ONE_REG, &r);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
cpsr_write(env, cpsr, 0xffffffff, CPSRWriteRaw);
|
||||
|
||||
/* Make sure the current mode regs are properly set */
|
||||
mode = env->uncached_cpsr & CPSR_M;
|
||||
bn = bank_number(mode);
|
||||
if (mode == ARM_CPU_MODE_FIQ) {
|
||||
memcpy(env->regs + 8, env->fiq_regs, 5 * sizeof(uint32_t));
|
||||
} else {
|
||||
memcpy(env->regs + 8, env->usr_regs, 5 * sizeof(uint32_t));
|
||||
}
|
||||
env->regs[13] = env->banked_r13[bn];
|
||||
env->spsr = env->banked_spsr[bn];
|
||||
env->regs[14] = env->banked_r14[r14_bank_number(mode)];
|
||||
|
||||
/* VFP registers */
|
||||
r.id = KVM_REG_ARM | KVM_REG_SIZE_U64 | KVM_REG_ARM_VFP;
|
||||
for (i = 0; i < 32; i++) {
|
||||
r.addr = (uintptr_t)aa32_vfp_dreg(env, i);
|
||||
ret = kvm_vcpu_ioctl(cs, KVM_GET_ONE_REG, &r);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
r.id++;
|
||||
}
|
||||
|
||||
r.id = KVM_REG_ARM | KVM_REG_SIZE_U32 | KVM_REG_ARM_VFP |
|
||||
KVM_REG_ARM_VFP_FPSCR;
|
||||
r.addr = (uintptr_t)&fpscr;
|
||||
ret = kvm_vcpu_ioctl(cs, KVM_GET_ONE_REG, &r);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
vfp_set_fpscr(env, fpscr);
|
||||
|
||||
ret = kvm_get_vcpu_events(cpu);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!write_kvmstate_to_list(cpu)) {
|
||||
return EINVAL;
|
||||
}
|
||||
/* Note that it's OK to have registers which aren't in CPUState,
|
||||
* so we can ignore a failure return here.
|
||||
*/
|
||||
write_list_to_cpustate(cpu);
|
||||
|
||||
kvm_arm_sync_mpstate_to_qemu(cpu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int kvm_arch_insert_sw_breakpoint(CPUState *cs, struct kvm_sw_breakpoint *bp)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "%s: guest debug not yet implemented\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
int kvm_arch_remove_sw_breakpoint(CPUState *cs, struct kvm_sw_breakpoint *bp)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "%s: guest debug not yet implemented\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bool kvm_arm_handle_debug(CPUState *cs, struct kvm_debug_exit_arch *debug_exit)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "%s: guest debug not yet implemented\n", __func__);
|
||||
return false;
|
||||
}
|
||||
|
||||
int kvm_arch_insert_hw_breakpoint(target_ulong addr,
|
||||
target_ulong len, int type)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "%s: not implemented\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
int kvm_arch_remove_hw_breakpoint(target_ulong addr,
|
||||
target_ulong len, int type)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "%s: not implemented\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
void kvm_arch_remove_all_hw_breakpoints(void)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "%s: not implemented\n", __func__);
|
||||
}
|
||||
|
||||
void kvm_arm_copy_hw_debug_data(struct kvm_guest_debug_arch *ptr)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "%s: not implemented\n", __func__);
|
||||
}
|
||||
|
||||
bool kvm_arm_hw_debug_active(CPUState *cs)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void kvm_arm_pmu_set_irq(CPUState *cs, int irq)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "%s: not implemented\n", __func__);
|
||||
}
|
||||
|
||||
void kvm_arm_pmu_init(CPUState *cs)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "%s: not implemented\n", __func__);
|
||||
}
|
||||
|
||||
#define ARM_REG_DFSR ARM_CP15_REG32(0, 5, 0, 0)
|
||||
#define ARM_REG_TTBCR ARM_CP15_REG32(0, 2, 0, 2)
|
||||
/*
|
||||
*DFSR:
|
||||
* TTBCR.EAE == 0
|
||||
* FS[4] - DFSR[10]
|
||||
* FS[3:0] - DFSR[3:0]
|
||||
* TTBCR.EAE == 1
|
||||
* FS, bits [5:0]
|
||||
*/
|
||||
#define DFSR_FSC(lpae, v) \
|
||||
((lpae) ? ((v) & 0x3F) : (((v) >> 6) | ((v) & 0x1F)))
|
||||
|
||||
#define DFSC_EXTABT(lpae) ((lpae) ? 0x10 : 0x08)
|
||||
|
||||
bool kvm_arm_verify_ext_dabt_pending(CPUState *cs)
|
||||
{
|
||||
uint32_t dfsr_val;
|
||||
|
||||
if (!kvm_get_one_reg(cs, ARM_REG_DFSR, &dfsr_val)) {
|
||||
ARMCPU *cpu = ARM_CPU(cs);
|
||||
CPUARMState *env = &cpu->env;
|
||||
uint32_t ttbcr;
|
||||
int lpae = 0;
|
||||
|
||||
if (!kvm_get_one_reg(cs, ARM_REG_TTBCR, &ttbcr)) {
|
||||
lpae = arm_feature(env, ARM_FEATURE_LPAE) && (ttbcr & TTBCR_EAE);
|
||||
}
|
||||
/* The verification is based on FS filed of the DFSR reg only*/
|
||||
return (DFSR_FSC(lpae, dfsr_val) == DFSC_EXTABT(lpae));
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -406,13 +406,7 @@ static inline const char *gic_class_name(void)
|
|||
static inline const char *gicv3_class_name(void)
|
||||
{
|
||||
if (kvm_irqchip_in_kernel()) {
|
||||
#ifdef TARGET_AARCH64
|
||||
return "kvm-arm-gicv3";
|
||||
#else
|
||||
error_report("KVM GICv3 acceleration is not supported on this "
|
||||
"platform");
|
||||
exit(1);
|
||||
#endif
|
||||
} else {
|
||||
if (kvm_enabled()) {
|
||||
error_report("Userspace GICv3 is not supported with KVM");
|
||||
|
|
|
@ -34,10 +34,7 @@ arm_ss.add(zlib)
|
|||
|
||||
arm_ss.add(when: 'CONFIG_TCG', if_true: files('arm-semi.c'))
|
||||
|
||||
kvm_ss = ss.source_set()
|
||||
kvm_ss.add(when: 'TARGET_AARCH64', if_true: files('kvm64.c'), if_false: files('kvm32.c'))
|
||||
arm_ss.add_all(when: 'CONFIG_KVM', if_true: kvm_ss)
|
||||
arm_ss.add(when: 'CONFIG_KVM', if_true: files('kvm.c'), if_false: files('kvm-stub.c'))
|
||||
arm_ss.add(when: 'CONFIG_KVM', if_true: files('kvm.c', 'kvm64.c'), if_false: files('kvm-stub.c'))
|
||||
|
||||
arm_ss.add(when: 'TARGET_AARCH64', if_true: files(
|
||||
'cpu64.c',
|
||||
|
|
|
@ -45,11 +45,16 @@
|
|||
@3same_q0 .... ... . . . size:2 .... .... .... . 0 . . .... \
|
||||
&3same vm=%vm_dp vn=%vn_dp vd=%vd_dp q=0
|
||||
|
||||
# For FP insns the high bit of 'size' is used as part of opcode decode
|
||||
@3same_fp .... ... . . . . size:1 .... .... .... . q:1 . . .... \
|
||||
&3same vm=%vm_dp vn=%vn_dp vd=%vd_dp
|
||||
@3same_fp_q0 .... ... . . . . size:1 .... .... .... . 0 . . .... \
|
||||
&3same vm=%vm_dp vn=%vn_dp vd=%vd_dp q=0
|
||||
# For FP insns the high bit of 'size' is used as part of opcode decode,
|
||||
# and the 'size' bit is 0 for 32-bit float and 1 for 16-bit float.
|
||||
# This converts this encoding to the same MO_8/16/32/64 values that the
|
||||
# integer neon insns use.
|
||||
%3same_fp_size 20:1 !function=neon_3same_fp_size
|
||||
|
||||
@3same_fp .... ... . . . . . .... .... .... . q:1 . . .... \
|
||||
&3same vm=%vm_dp vn=%vn_dp vd=%vd_dp size=%3same_fp_size
|
||||
@3same_fp_q0 .... ... . . . . . .... .... .... . 0 . . .... \
|
||||
&3same vm=%vm_dp vn=%vn_dp vd=%vd_dp q=0 size=%3same_fp_size
|
||||
|
||||
VHADD_S_3s 1111 001 0 0 . .. .... .... 0000 . . . 0 .... @3same
|
||||
VHADD_U_3s 1111 001 1 0 . .. .... .... 0000 . . . 0 .... @3same
|
||||
|
@ -251,9 +256,8 @@ VMINNM_fp_3s 1111 001 1 0 . 1 . .... .... 1111 ... 1 .... @3same_fp
|
|||
@2reg_shll_b .... ... . . . 001 shift:3 .... .... 0 . . . .... \
|
||||
&2reg_shift vm=%vm_dp vd=%vd_dp size=0 q=0
|
||||
|
||||
# We use size=0 for fp32 and size=1 for fp16 to match the 3-same encodings.
|
||||
@2reg_vcvt .... ... . . . 1 ..... .... .... . q:1 . . .... \
|
||||
&2reg_shift vm=%vm_dp vd=%vd_dp size=0 shift=%neon_rshift_i5
|
||||
&2reg_shift vm=%vm_dp vd=%vd_dp size=2 shift=%neon_rshift_i5
|
||||
@2reg_vcvt_f16 .... ... . . . 11 .... .... .... . q:1 . . .... \
|
||||
&2reg_shift vm=%vm_dp vd=%vd_dp size=1 shift=%neon_rshift_i4
|
||||
|
||||
|
|
|
@ -34,11 +34,17 @@
|
|||
%vd_dp 22:1 12:4
|
||||
%vd_sp 12:4 22:1
|
||||
|
||||
VCMLA 1111 110 rot:2 . 1 size:1 .... .... 1000 . q:1 . 0 .... \
|
||||
vm=%vm_dp vn=%vn_dp vd=%vd_dp
|
||||
# For VCMLA/VCADD insns, convert the single-bit size field
|
||||
# which is 0 for fp16 and 1 for fp32 into a MO_* constant.
|
||||
# (Note that this is the reverse of the sense of the 1-bit size
|
||||
# field in the 3same_fp Neon insns.)
|
||||
%vcadd_size 20:1 !function=plus1
|
||||
|
||||
VCADD 1111 110 rot:1 1 . 0 size:1 .... .... 1000 . q:1 . 0 .... \
|
||||
vm=%vm_dp vn=%vn_dp vd=%vd_dp
|
||||
VCMLA 1111 110 rot:2 . 1 . .... .... 1000 . q:1 . 0 .... \
|
||||
vm=%vm_dp vn=%vn_dp vd=%vd_dp size=%vcadd_size
|
||||
|
||||
VCADD 1111 110 rot:1 1 . 0 . .... .... 1000 . q:1 . 0 .... \
|
||||
vm=%vm_dp vn=%vn_dp vd=%vd_dp size=%vcadd_size
|
||||
|
||||
# VUDOT and VSDOT
|
||||
VDOT 1111 110 00 . 10 .... .... 1101 . q:1 . u:1 .... \
|
||||
|
@ -51,9 +57,9 @@ VFML 1111 110 0 s:1 . 10 .... .... 1000 . 1 . 1 .... \
|
|||
vm=%vm_dp vn=%vn_dp vd=%vd_dp q=1
|
||||
|
||||
VCMLA_scalar 1111 1110 0 . rot:2 .... .... 1000 . q:1 index:1 0 vm:4 \
|
||||
vn=%vn_dp vd=%vd_dp size=0
|
||||
vn=%vn_dp vd=%vd_dp size=1
|
||||
VCMLA_scalar 1111 1110 1 . rot:2 .... .... 1000 . q:1 . 0 .... \
|
||||
vm=%vm_dp vn=%vn_dp vd=%vd_dp size=1 index=0
|
||||
vm=%vm_dp vn=%vn_dp vd=%vd_dp size=2 index=0
|
||||
|
||||
VDOT_scalar 1111 1110 0 . 10 .... .... 1101 . q:1 index:1 u:1 rm:4 \
|
||||
vm=%vm_dp vn=%vn_dp vd=%vd_dp
|
||||
|
|
|
@ -49,6 +49,12 @@ static inline int rsub_8(DisasContext *s, int x)
|
|||
return 8 - x;
|
||||
}
|
||||
|
||||
static inline int neon_3same_fp_size(DisasContext *s, int x)
|
||||
{
|
||||
/* Convert 0==fp32, 1==fp16 into a MO_* value */
|
||||
return MO_32 - x;
|
||||
}
|
||||
|
||||
/* Include the generated Neon decoder */
|
||||
#include "decode-neon-dp.c.inc"
|
||||
#include "decode-neon-ls.c.inc"
|
||||
|
@ -162,7 +168,7 @@ static bool trans_VCMLA(DisasContext *s, arg_VCMLA *a)
|
|||
gen_helper_gvec_3_ptr *fn_gvec_ptr;
|
||||
|
||||
if (!dc_isar_feature(aa32_vcma, s)
|
||||
|| (!a->size && !dc_isar_feature(aa32_fp16_arith, s))) {
|
||||
|| (a->size == MO_16 && !dc_isar_feature(aa32_fp16_arith, s))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -181,8 +187,9 @@ static bool trans_VCMLA(DisasContext *s, arg_VCMLA *a)
|
|||
}
|
||||
|
||||
opr_sz = (1 + a->q) * 8;
|
||||
fpst = fpstatus_ptr(a->size == 0 ? FPST_STD_F16 : FPST_STD);
|
||||
fn_gvec_ptr = a->size ? gen_helper_gvec_fcmlas : gen_helper_gvec_fcmlah;
|
||||
fpst = fpstatus_ptr(a->size == MO_16 ? FPST_STD_F16 : FPST_STD);
|
||||
fn_gvec_ptr = (a->size == MO_16) ?
|
||||
gen_helper_gvec_fcmlah : gen_helper_gvec_fcmlas;
|
||||
tcg_gen_gvec_3_ptr(vfp_reg_offset(1, a->vd),
|
||||
vfp_reg_offset(1, a->vn),
|
||||
vfp_reg_offset(1, a->vm),
|
||||
|
@ -199,7 +206,7 @@ static bool trans_VCADD(DisasContext *s, arg_VCADD *a)
|
|||
gen_helper_gvec_3_ptr *fn_gvec_ptr;
|
||||
|
||||
if (!dc_isar_feature(aa32_vcma, s)
|
||||
|| (!a->size && !dc_isar_feature(aa32_fp16_arith, s))) {
|
||||
|| (a->size == MO_16 && !dc_isar_feature(aa32_fp16_arith, s))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -218,8 +225,9 @@ static bool trans_VCADD(DisasContext *s, arg_VCADD *a)
|
|||
}
|
||||
|
||||
opr_sz = (1 + a->q) * 8;
|
||||
fpst = fpstatus_ptr(a->size == 0 ? FPST_STD_F16 : FPST_STD);
|
||||
fn_gvec_ptr = a->size ? gen_helper_gvec_fcadds : gen_helper_gvec_fcaddh;
|
||||
fpst = fpstatus_ptr(a->size == MO_16 ? FPST_STD_F16 : FPST_STD);
|
||||
fn_gvec_ptr = (a->size == MO_16) ?
|
||||
gen_helper_gvec_fcaddh : gen_helper_gvec_fcadds;
|
||||
tcg_gen_gvec_3_ptr(vfp_reg_offset(1, a->vd),
|
||||
vfp_reg_offset(1, a->vn),
|
||||
vfp_reg_offset(1, a->vm),
|
||||
|
@ -301,7 +309,7 @@ static bool trans_VCMLA_scalar(DisasContext *s, arg_VCMLA_scalar *a)
|
|||
if (!dc_isar_feature(aa32_vcma, s)) {
|
||||
return false;
|
||||
}
|
||||
if (a->size == 0 && !dc_isar_feature(aa32_fp16_arith, s)) {
|
||||
if (a->size == MO_16 && !dc_isar_feature(aa32_fp16_arith, s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -319,10 +327,10 @@ static bool trans_VCMLA_scalar(DisasContext *s, arg_VCMLA_scalar *a)
|
|||
return true;
|
||||
}
|
||||
|
||||
fn_gvec_ptr = (a->size ? gen_helper_gvec_fcmlas_idx
|
||||
: gen_helper_gvec_fcmlah_idx);
|
||||
fn_gvec_ptr = (a->size == MO_16) ?
|
||||
gen_helper_gvec_fcmlah_idx : gen_helper_gvec_fcmlas_idx;
|
||||
opr_sz = (1 + a->q) * 8;
|
||||
fpst = fpstatus_ptr(a->size == 0 ? FPST_STD_F16 : FPST_STD);
|
||||
fpst = fpstatus_ptr(a->size == MO_16 ? FPST_STD_F16 : FPST_STD);
|
||||
tcg_gen_gvec_3_ptr(vfp_reg_offset(1, a->vd),
|
||||
vfp_reg_offset(1, a->vn),
|
||||
vfp_reg_offset(1, a->vm),
|
||||
|
@ -1049,7 +1057,7 @@ DO_3SAME_VQDMULH(VQRDMULH, qrdmulh)
|
|||
WRAP_FP_GVEC(gen_##INSN##_fp16_3s, FPST_STD_F16, HFUNC) \
|
||||
static bool trans_##INSN##_fp_3s(DisasContext *s, arg_3same *a) \
|
||||
{ \
|
||||
if (a->size != 0) { \
|
||||
if (a->size == MO_16) { \
|
||||
if (!dc_isar_feature(aa32_fp16_arith, s)) { \
|
||||
return false; \
|
||||
} \
|
||||
|
@ -1088,7 +1096,7 @@ static bool trans_VMAXNM_fp_3s(DisasContext *s, arg_3same *a)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (a->size != 0) {
|
||||
if (a->size == MO_16) {
|
||||
if (!dc_isar_feature(aa32_fp16_arith, s)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1103,7 +1111,7 @@ static bool trans_VMINNM_fp_3s(DisasContext *s, arg_3same *a)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (a->size != 0) {
|
||||
if (a->size == MO_16) {
|
||||
if (!dc_isar_feature(aa32_fp16_arith, s)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1135,7 +1143,7 @@ static bool do_3same_fp_pair(DisasContext *s, arg_3same *a,
|
|||
assert(a->q == 0); /* enforced by decode patterns */
|
||||
|
||||
|
||||
fpstatus = fpstatus_ptr(a->size != 0 ? FPST_STD_F16 : FPST_STD);
|
||||
fpstatus = fpstatus_ptr(a->size == MO_16 ? FPST_STD_F16 : FPST_STD);
|
||||
tcg_gen_gvec_3_ptr(vfp_reg_offset(1, a->vd),
|
||||
vfp_reg_offset(1, a->vn),
|
||||
vfp_reg_offset(1, a->vm),
|
||||
|
@ -1152,7 +1160,7 @@ static bool do_3same_fp_pair(DisasContext *s, arg_3same *a,
|
|||
#define DO_3S_FP_PAIR(INSN,FUNC) \
|
||||
static bool trans_##INSN##_fp_3s(DisasContext *s, arg_3same *a) \
|
||||
{ \
|
||||
if (a->size != 0) { \
|
||||
if (a->size == MO_16) { \
|
||||
if (!dc_isar_feature(aa32_fp16_arith, s)) { \
|
||||
return false; \
|
||||
} \
|
||||
|
@ -1620,7 +1628,7 @@ static bool do_fp_2sh(DisasContext *s, arg_2reg_shift *a,
|
|||
return false;
|
||||
}
|
||||
|
||||
if (a->size != 0) {
|
||||
if (a->size == MO_16) {
|
||||
if (!dc_isar_feature(aa32_fp16_arith, s)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1640,7 +1648,7 @@ static bool do_fp_2sh(DisasContext *s, arg_2reg_shift *a,
|
|||
return true;
|
||||
}
|
||||
|
||||
fpst = fpstatus_ptr(a->size ? FPST_STD_F16 : FPST_STD);
|
||||
fpst = fpstatus_ptr(a->size == MO_16 ? FPST_STD_F16 : FPST_STD);
|
||||
tcg_gen_gvec_2_ptr(rd_ofs, rm_ofs, fpst, vec_size, vec_size, a->shift, fn);
|
||||
tcg_temp_free_ptr(fpst);
|
||||
return true;
|
||||
|
|
|
@ -568,6 +568,89 @@ class BootLinuxConsole(LinuxKernelTest):
|
|||
'sda')
|
||||
# cubieboard's reboot is not functioning; omit reboot test.
|
||||
|
||||
def test_arm_quanta_gsj(self):
|
||||
"""
|
||||
:avocado: tags=arch:arm
|
||||
:avocado: tags=machine:quanta-gsj
|
||||
"""
|
||||
# 25 MiB compressed, 32 MiB uncompressed.
|
||||
image_url = (
|
||||
'https://github.com/hskinnemoen/openbmc/releases/download/'
|
||||
'20200711-gsj-qemu-0/obmc-phosphor-image-gsj.static.mtd.gz')
|
||||
image_hash = '14895e634923345cb5c8776037ff7876df96f6b1'
|
||||
image_path_gz = self.fetch_asset(image_url, asset_hash=image_hash)
|
||||
image_name = 'obmc.mtd'
|
||||
image_path = os.path.join(self.workdir, image_name)
|
||||
archive.gzip_uncompress(image_path_gz, image_path)
|
||||
|
||||
self.vm.set_console()
|
||||
drive_args = 'file=' + image_path + ',if=mtd,bus=0,unit=0'
|
||||
self.vm.add_args('-drive', drive_args)
|
||||
self.vm.launch()
|
||||
|
||||
# Disable drivers and services that stall for a long time during boot,
|
||||
# to avoid running past the 90-second timeout. These may be removed
|
||||
# as the corresponding device support is added.
|
||||
kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + (
|
||||
'console=${console} '
|
||||
'mem=${mem} '
|
||||
'initcall_blacklist=npcm_i2c_bus_driver_init '
|
||||
'systemd.mask=systemd-random-seed.service '
|
||||
'systemd.mask=dropbearkey.service '
|
||||
)
|
||||
|
||||
self.wait_for_console_pattern('> BootBlock by Nuvoton')
|
||||
self.wait_for_console_pattern('>Device: Poleg BMC NPCM730')
|
||||
self.wait_for_console_pattern('>Skip DDR init.')
|
||||
self.wait_for_console_pattern('U-Boot ')
|
||||
interrupt_interactive_console_until_pattern(
|
||||
self, 'Hit any key to stop autoboot:', 'U-Boot>')
|
||||
exec_command_and_wait_for_pattern(
|
||||
self, "setenv bootargs ${bootargs} " + kernel_command_line,
|
||||
'U-Boot>')
|
||||
exec_command_and_wait_for_pattern(
|
||||
self, 'run romboot', 'Booting Kernel from flash')
|
||||
self.wait_for_console_pattern('Booting Linux on physical CPU 0x0')
|
||||
self.wait_for_console_pattern('CPU1: thread -1, cpu 1, socket 0')
|
||||
self.wait_for_console_pattern('OpenBMC Project Reference Distro')
|
||||
self.wait_for_console_pattern('gsj login:')
|
||||
|
||||
def test_arm_quanta_gsj_initrd(self):
|
||||
"""
|
||||
:avocado: tags=arch:arm
|
||||
:avocado: tags=machine:quanta-gsj
|
||||
"""
|
||||
initrd_url = (
|
||||
'https://github.com/hskinnemoen/openbmc/releases/download/'
|
||||
'20200711-gsj-qemu-0/obmc-phosphor-initramfs-gsj.cpio.xz')
|
||||
initrd_hash = '98fefe5d7e56727b1eb17d5c00311b1b5c945300'
|
||||
initrd_path = self.fetch_asset(initrd_url, asset_hash=initrd_hash)
|
||||
kernel_url = (
|
||||
'https://github.com/hskinnemoen/openbmc/releases/download/'
|
||||
'20200711-gsj-qemu-0/uImage-gsj.bin')
|
||||
kernel_hash = 'fa67b2f141d56d39b3c54305c0e8a899c99eb2c7'
|
||||
kernel_path = self.fetch_asset(kernel_url, asset_hash=kernel_hash)
|
||||
dtb_url = (
|
||||
'https://github.com/hskinnemoen/openbmc/releases/download/'
|
||||
'20200711-gsj-qemu-0/nuvoton-npcm730-gsj.dtb')
|
||||
dtb_hash = '18315f7006d7b688d8312d5c727eecd819aa36a4'
|
||||
dtb_path = self.fetch_asset(dtb_url, asset_hash=dtb_hash)
|
||||
|
||||
self.vm.set_console()
|
||||
kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE +
|
||||
'console=ttyS0,115200n8 '
|
||||
'earlycon=uart8250,mmio32,0xf0001000')
|
||||
self.vm.add_args('-kernel', kernel_path,
|
||||
'-initrd', initrd_path,
|
||||
'-dtb', dtb_path,
|
||||
'-append', kernel_command_line)
|
||||
self.vm.launch()
|
||||
|
||||
self.wait_for_console_pattern('Booting Linux on physical CPU 0x0')
|
||||
self.wait_for_console_pattern('CPU1: thread -1, cpu 1, socket 0')
|
||||
self.wait_for_console_pattern(
|
||||
'Give root password for system maintenance')
|
||||
|
||||
def test_arm_orangepi(self):
|
||||
"""
|
||||
:avocado: tags=arch:arm
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
%1f 0:8
|
||||
%2f 8:8
|
||||
%3f 16:8
|
||||
|
||||
&3arg a b c
|
||||
@3arg ........ ........ ........ ........ &3arg a=%1f b=%2f c=%3f
|
||||
3insn 00000000 ........ ........ ........ @3arg
|
Loading…
Reference in New Issue