mirror of
https://gitlab.com/qemu-project/qemu
synced 2024-09-06 18:05:37 +00:00
target-arm queue:
* Add new mps3-an547 board * target/arm: Restrict v7A TCG cpus to TCG accel * Implement a Xilinx CSU DMA model * hw/timer/renesas_tmr: Fix use of uninitialized data in read_tcnt() -----BEGIN PGP SIGNATURE----- iQJMBAABCAA3FiEE4aXFk81BneKOgxXPPCUl7RQ2DN4FAmBI0AQZHHBldGVyLm1h eWRlbGxAbGluYXJvLm9yZwAKCRA8JSXtFDYM3n1WD/iXrX/YMZDrBOzP5h6sE5W/ W5tiTJBCKskRW1HduJjObFFl29yuiTzYld7+zfOTQUFgecpCN8q7AHuN0y9Sg+9B cyHNHwseOfkHE4CGe2ImjmpYSZUUEkQBtCN2OmJjsaiEoK/eZCIErPw4JMUcusLL VPccPjXS92WtqQkGshNYribMOhZnuBcvX/LsT7IL8THDVPv8OECIeq8ewTZtLMe/ l/x3D3PJ56q69EFMnYt6TS1cd9OtD7pw3Jnbfv0iStE/TiJQB92ft8H07kpE6KVI jhRkhyVBnrVI2deTFcFC+rZDQggzWGRVAbUzMikZOZycUML/zjJKEIGM2V6iCHmL bQMUOTR4GLFbVyabJ/IH6YoCFS+8hUboyQXQL0gOKtcJiryoDI3AjyKNjxRbIY3b qEV3xQYTtrS7mdrwQZqwH6Rs/54jDyX7eBNWfZwq4dOQKvnTQPQEj6iWmj4rOvma McWbhu6bAfU6ZINOxkr8HcG/AxF3IYw9Gtb8KRg7/87JRvNvxj++kqqjSRRLU4Co QUYuQnpd+ux7eSXKcRfnzd27iaZ++dKJMct4Gq8i0VASb+uk+xmkG2MiGEBVSJv8 kIK5NLzDGjWjrZd366uAUtHBnWktP/5J4DKq1XKx0mtrkt7tbU6Oh90a7rSiOgur LtpGH4IPQDwp/YGXZD86 =Qu2a -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/pmaydell/tags/pull-target-arm-20210310' into staging target-arm queue: * Add new mps3-an547 board * target/arm: Restrict v7A TCG cpus to TCG accel * Implement a Xilinx CSU DMA model * hw/timer/renesas_tmr: Fix use of uninitialized data in read_tcnt() # gpg: Signature made Wed 10 Mar 2021 13:56:20 GMT # 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-20210310: (54 commits) hw/timer/renesas_tmr: Fix use of uninitialized data in read_tcnt() hw/timer/renesas_tmr: Prefix constants for CSS values with CSS_ hw/ssi: xilinx_spips: Remove DMA related dead codes from zynqmp_spips hw/ssi: xilinx_spips: Clean up coding convention issues hw/arm: xlnx-zynqmp: Connect a Xilinx CSU DMA module for QSPI hw/arm: xlnx-zynqmp: Clean up coding convention issues hw/dma: Implement a Xilinx CSU DMA model target/arm: Restrict v7A TCG cpus to TCG accel tests/qtest/sse-timer-test: Test counter scaling changes tests/qtest/sse-timer-test: Test the system timer tests/qtest/sse-timer-test: Add simple test of the SSE counter docs/system/arm/mps2.rst: Document the new mps3-an547 board hw/arm/mps2-tz: Add new mps3-an547 board hw/arm/mps2-tz: Make initsvtor0 setting board-specific hw/arm/mps2-tz: Support running APB peripherals on different clock hw/misc/mps2-scc: Implement changes for AN547 hw/misc/mps2-fpgaio: Support AN547 DBGCTRL register hw/misc/mps2-fpgaio: Fold counters subsection into main vmstate hw/arm/mps2-tz: Make UART overflow IRQ board-specific hw/arm/armsse: Add SSE-300 support ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
5c6295a45b
|
@ -747,10 +747,17 @@ F: hw/misc/iotkit-sysctl.c
|
|||
F: include/hw/misc/iotkit-sysctl.h
|
||||
F: hw/misc/iotkit-sysinfo.c
|
||||
F: include/hw/misc/iotkit-sysinfo.h
|
||||
F: hw/misc/armsse-cpu-pwrctrl.c
|
||||
F: include/hw/misc/armsse-cpu-pwrctrl.h
|
||||
F: hw/misc/armsse-cpuid.c
|
||||
F: include/hw/misc/armsse-cpuid.h
|
||||
F: hw/misc/armsse-mhu.c
|
||||
F: include/hw/misc/armsse-mhu.h
|
||||
F: hw/timer/sse-counter.c
|
||||
F: include/hw/timer/sse-counter.h
|
||||
F: hw/timer/sse-timer.c
|
||||
F: include/hw/timer/sse-timer.h
|
||||
F: tests/qtest/sse-timer-test.c
|
||||
F: docs/system/arm/mps2.rst
|
||||
|
||||
Musca
|
||||
|
|
|
@ -80,11 +80,12 @@ Adding clocks to a device must be done during the init method of the Device
|
|||
instance.
|
||||
|
||||
To add an input clock to a device, the function ``qdev_init_clock_in()``
|
||||
must be used. It takes the name, a callback and an opaque parameter
|
||||
for the callback (this will be explained in a following section).
|
||||
must be used. It takes the name, a callback, an opaque parameter
|
||||
for the callback and a mask of events when the callback should be
|
||||
called (this will be explained in a following section).
|
||||
Output is simpler; only the name is required. Typically::
|
||||
|
||||
qdev_init_clock_in(DEVICE(dev), "clk_in", clk_in_callback, dev);
|
||||
qdev_init_clock_in(DEVICE(dev), "clk_in", clk_in_callback, dev, ClockUpdate);
|
||||
qdev_init_clock_out(DEVICE(dev), "clk_out");
|
||||
|
||||
Both functions return the created Clock pointer, which should be saved in the
|
||||
|
@ -113,7 +114,7 @@ output.
|
|||
* callback for the input clock (see "Callback on input clock
|
||||
* change" section below for more information).
|
||||
*/
|
||||
static void clk_in_callback(void *opaque);
|
||||
static void clk_in_callback(void *opaque, ClockEvent event);
|
||||
|
||||
/*
|
||||
* static array describing clocks:
|
||||
|
@ -124,7 +125,7 @@ output.
|
|||
* the clk_out field of a MyDeviceState structure.
|
||||
*/
|
||||
static const ClockPortInitArray mydev_clocks = {
|
||||
QDEV_CLOCK_IN(MyDeviceState, clk_in, clk_in_callback),
|
||||
QDEV_CLOCK_IN(MyDeviceState, clk_in, clk_in_callback, ClockUpdate),
|
||||
QDEV_CLOCK_OUT(MyDeviceState, clk_out),
|
||||
QDEV_CLOCK_END
|
||||
};
|
||||
|
@ -153,6 +154,47 @@ nothing else to do. This value will be propagated to other clocks when
|
|||
connecting the clocks together and devices will fetch the right value during
|
||||
the first reset.
|
||||
|
||||
Clock callbacks
|
||||
---------------
|
||||
|
||||
You can give a clock a callback function in several ways:
|
||||
|
||||
* by passing it as an argument to ``qdev_init_clock_in()``
|
||||
* as an argument to the ``QDEV_CLOCK_IN()`` macro initializing an
|
||||
array to be passed to ``qdev_init_clocks()``
|
||||
* by directly calling the ``clock_set_callback()`` function
|
||||
|
||||
The callback function must be of this type:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
typedef void ClockCallback(void *opaque, ClockEvent event);
|
||||
|
||||
The ``opaque`` argument is the pointer passed to ``qdev_init_clock_in()``
|
||||
or ``clock_set_callback()``; for ``qdev_init_clocks()`` it is the
|
||||
``dev`` device pointer.
|
||||
|
||||
The ``event`` argument specifies why the callback has been called.
|
||||
When you register the callback you specify a mask of ClockEvent values
|
||||
that you are interested in. The callback will only be called for those
|
||||
events.
|
||||
|
||||
The events currently supported are:
|
||||
|
||||
* ``ClockPreUpdate`` : called when the input clock's period is about to
|
||||
update. This is useful if the device needs to do some action for
|
||||
which it needs to know the old value of the clock period. During
|
||||
this callback, Clock API functions like ``clock_get()`` or
|
||||
``clock_ticks_to_ns()`` will use the old period.
|
||||
* ``ClockUpdate`` : called after the input clock's period has changed.
|
||||
During this callback, Clock API functions like ``clock_ticks_to_ns()``
|
||||
will use the new period.
|
||||
|
||||
Note that a clock only has one callback: it is not possible to register
|
||||
different functions for different events. You must register a single
|
||||
callback which listens for all of the events you are interested in,
|
||||
and use the ``event`` argument to identify which event has happened.
|
||||
|
||||
Retrieving clocks from a device
|
||||
-------------------------------
|
||||
|
||||
|
@ -231,7 +273,7 @@ object during device instance init. For example:
|
|||
.. code-block:: c
|
||||
|
||||
clk = qdev_init_clock_in(DEVICE(dev), "clk-in", clk_in_callback,
|
||||
dev);
|
||||
dev, ClockUpdate);
|
||||
/* set initial value to 10ns / 100MHz */
|
||||
clock_set_ns(clk, 10);
|
||||
|
||||
|
@ -267,11 +309,12 @@ next lowest integer. This implies some inaccuracy due to the rounding,
|
|||
so be cautious about using it in calculations.
|
||||
|
||||
It is also possible to register a callback on clock frequency changes.
|
||||
Here is an example:
|
||||
Here is an example, which assumes that ``clock_callback`` has been
|
||||
specified as the callback for the ``ClockUpdate`` event:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void clock_callback(void *opaque) {
|
||||
void clock_callback(void *opaque, ClockEvent event) {
|
||||
MyDeviceState *s = (MyDeviceState *) opaque;
|
||||
/*
|
||||
* 'opaque' is the argument passed to qdev_init_clock_in();
|
||||
|
@ -317,6 +360,18 @@ rather than simply passing it to a QEMUTimer function like
|
|||
``timer_mod_ns()`` then you should be careful to avoid overflow
|
||||
in those calculations, of course.)
|
||||
|
||||
Obtaining tick counts
|
||||
---------------------
|
||||
|
||||
For calculations where you need to know the number of ticks in
|
||||
a given duration, use ``clock_ns_to_ticks()``. This function handles
|
||||
possible non-whole-number-of-nanoseconds periods and avoids
|
||||
potential rounding errors. It will return '0' if the clock is stopped
|
||||
(i.e. it has period zero). If the inputs imply a tick count that
|
||||
overflows a 64-bit value (a very long duration for a clock with a
|
||||
very short period) the output value is truncated, so effectively
|
||||
the 64-bit output wraps around.
|
||||
|
||||
Changing a clock period
|
||||
-----------------------
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Arm MPS2 and MPS3 boards (``mps2-an385``, ``mps2-an386``, ``mps2-an500``, ``mps2-an505``, ``mps2-an511``, ``mps2-an521``, ``mps3-an524``)
|
||||
=========================================================================================================================================
|
||||
Arm MPS2 and MPS3 boards (``mps2-an385``, ``mps2-an386``, ``mps2-an500``, ``mps2-an505``, ``mps2-an511``, ``mps2-an521``, ``mps3-an524``, ``mps3-an547``)
|
||||
=========================================================================================================================================================
|
||||
|
||||
These board models all use Arm M-profile CPUs.
|
||||
|
||||
|
@ -27,6 +27,8 @@ QEMU models the following FPGA images:
|
|||
Dual Cortex-M33 as documented in Arm Application Note AN521
|
||||
``mps3-an524``
|
||||
Dual Cortex-M33 on an MPS3, as documented in Arm Application Note AN524
|
||||
``mps3-an547``
|
||||
Cortex-M55 on an MPS3, as documented in Arm Application Note AN547
|
||||
|
||||
Differences between QEMU and real hardware:
|
||||
|
||||
|
|
|
@ -238,7 +238,7 @@ static void npcm7xx_adc_init(Object *obj)
|
|||
memory_region_init_io(&s->iomem, obj, &npcm7xx_adc_ops, s,
|
||||
TYPE_NPCM7XX_ADC, 4 * KiB);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
s->clock = qdev_init_clock_in(DEVICE(s), "clock", NULL, NULL);
|
||||
s->clock = qdev_init_clock_in(DEVICE(s), "clock", NULL, NULL, 0);
|
||||
|
||||
for (i = 0; i < NPCM7XX_ADC_NUM_INPUTS; ++i) {
|
||||
object_property_add_uint32_ptr(obj, "adci[*]",
|
||||
|
|
|
@ -353,6 +353,7 @@ config XLNX_ZYNQMP_ARM
|
|||
select SSI_M25P80
|
||||
select XILINX_AXI
|
||||
select XILINX_SPIPS
|
||||
select XLNX_CSU_DMA
|
||||
select XLNX_ZYNQMP
|
||||
select XLNX_ZDMA
|
||||
|
||||
|
@ -505,6 +506,7 @@ config ARM11MPCORE
|
|||
config ARMSSE
|
||||
bool
|
||||
select ARM_V7M
|
||||
select ARMSSE_CPU_PWRCTRL
|
||||
select ARMSSE_CPUID
|
||||
select ARMSSE_MHU
|
||||
select CMSDK_APB_TIMER
|
||||
|
@ -520,9 +522,5 @@ config ARMSSE
|
|||
select TZ_MSC
|
||||
select TZ_PPC
|
||||
select UNIMP
|
||||
|
||||
config ARMSSE_CPUID
|
||||
bool
|
||||
|
||||
config ARMSSE_MHU
|
||||
bool
|
||||
select SSE_COUNTER
|
||||
select SSE_TIMER
|
||||
|
|
1008
hw/arm/armsse.c
1008
hw/arm/armsse.c
File diff suppressed because it is too large
Load diff
168
hw/arm/mps2-tz.c
168
hw/arm/mps2-tz.c
|
@ -17,6 +17,7 @@
|
|||
* "mps2-an505" -- Cortex-M33 as documented in ARM Application Note AN505
|
||||
* "mps2-an521" -- Dual Cortex-M33 as documented in Application Note AN521
|
||||
* "mps2-an524" -- Dual Cortex-M33 as documented in Application Note AN524
|
||||
* "mps2-an547" -- Single Cortex-M55 as documented in Application Note AN547
|
||||
*
|
||||
* Links to the TRM for the board itself and to the various Application
|
||||
* Notes which document the FPGA images can be found here:
|
||||
|
@ -30,6 +31,8 @@
|
|||
* https://developer.arm.com/documentation/dai0521/latest/
|
||||
* Application Note AN524:
|
||||
* https://developer.arm.com/documentation/dai0524/latest/
|
||||
* Application Note AN547:
|
||||
* https://developer.arm.com/-/media/Arm%20Developer%20Community/PDF/DAI0547B_SSE300_PLUS_U55_FPGA_for_mps3.pdf
|
||||
*
|
||||
* The AN505 defers to the Cortex-M33 processor ARMv8M IoT Kit FVP User Guide
|
||||
* (ARM ECM0601256) for the details of some of the device layout:
|
||||
|
@ -37,6 +40,8 @@
|
|||
* Similarly, the AN521 and AN524 use the SSE-200, and the SSE-200 TRM defines
|
||||
* most of the device layout:
|
||||
* https://developer.arm.com/documentation/101104/latest/
|
||||
* and the AN547 uses the SSE-300, whose layout is in the SSE-300 TRM:
|
||||
* https://developer.arm.com/documentation/101773/latest/
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
@ -68,13 +73,14 @@
|
|||
#include "hw/qdev-clock.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define MPS2TZ_NUMIRQ_MAX 95
|
||||
#define MPS2TZ_RAM_MAX 4
|
||||
#define MPS2TZ_NUMIRQ_MAX 96
|
||||
#define MPS2TZ_RAM_MAX 5
|
||||
|
||||
typedef enum MPS2TZFPGAType {
|
||||
FPGA_AN505,
|
||||
FPGA_AN521,
|
||||
FPGA_AN524,
|
||||
FPGA_AN547,
|
||||
} MPS2TZFPGAType;
|
||||
|
||||
/*
|
||||
|
@ -106,11 +112,15 @@ struct MPS2TZMachineClass {
|
|||
MPS2TZFPGAType fpga_type;
|
||||
uint32_t scc_id;
|
||||
uint32_t sysclk_frq; /* Main SYSCLK frequency in Hz */
|
||||
uint32_t apb_periph_frq; /* APB peripheral frequency in Hz */
|
||||
uint32_t len_oscclk;
|
||||
const uint32_t *oscclk;
|
||||
uint32_t fpgaio_num_leds; /* Number of LEDs in FPGAIO LED0 register */
|
||||
bool fpgaio_has_switches; /* Does FPGAIO have SWITCH register? */
|
||||
bool fpgaio_has_dbgctrl; /* Does FPGAIO have DBGCTRL register? */
|
||||
int numirq; /* Number of external interrupts */
|
||||
int uart_overflow_irq; /* number of the combined UART overflow IRQ */
|
||||
uint32_t init_svtor; /* init-svtor setting for SSE */
|
||||
const RAMInfo *raminfo;
|
||||
const char *armsse_type;
|
||||
};
|
||||
|
@ -149,6 +159,7 @@ struct MPS2TZMachineState {
|
|||
#define TYPE_MPS2TZ_AN505_MACHINE MACHINE_TYPE_NAME("mps2-an505")
|
||||
#define TYPE_MPS2TZ_AN521_MACHINE MACHINE_TYPE_NAME("mps2-an521")
|
||||
#define TYPE_MPS3TZ_AN524_MACHINE MACHINE_TYPE_NAME("mps3-an524")
|
||||
#define TYPE_MPS3TZ_AN547_MACHINE MACHINE_TYPE_NAME("mps3-an547")
|
||||
|
||||
OBJECT_DECLARE_TYPE(MPS2TZMachineState, MPS2TZMachineClass, MPS2TZ_MACHINE)
|
||||
|
||||
|
@ -248,6 +259,49 @@ static const RAMInfo an524_raminfo[] = { {
|
|||
},
|
||||
};
|
||||
|
||||
static const RAMInfo an547_raminfo[] = { {
|
||||
.name = "itcm",
|
||||
.base = 0x00000000,
|
||||
.size = 512 * KiB,
|
||||
.mpc = -1,
|
||||
.mrindex = 0,
|
||||
}, {
|
||||
.name = "sram",
|
||||
.base = 0x01000000,
|
||||
.size = 2 * MiB,
|
||||
.mpc = 0,
|
||||
.mrindex = 1,
|
||||
}, {
|
||||
.name = "dtcm",
|
||||
.base = 0x20000000,
|
||||
.size = 4 * 128 * KiB,
|
||||
.mpc = -1,
|
||||
.mrindex = 2,
|
||||
}, {
|
||||
.name = "sram 2",
|
||||
.base = 0x21000000,
|
||||
.size = 4 * MiB,
|
||||
.mpc = -1,
|
||||
.mrindex = 3,
|
||||
}, {
|
||||
/* We don't model QSPI flash yet; for now expose it as simple ROM */
|
||||
.name = "QSPI",
|
||||
.base = 0x28000000,
|
||||
.size = 8 * MiB,
|
||||
.mpc = 1,
|
||||
.mrindex = 4,
|
||||
.flags = IS_ROM,
|
||||
}, {
|
||||
.name = "DDR",
|
||||
.base = 0x60000000,
|
||||
.size = MPS3_DDR_SIZE,
|
||||
.mpc = 2,
|
||||
.mrindex = -1,
|
||||
}, {
|
||||
.name = NULL,
|
||||
},
|
||||
};
|
||||
|
||||
static const RAMInfo *find_raminfo_for_mpc(MPS2TZMachineState *mms, int mpc)
|
||||
{
|
||||
MPS2TZMachineClass *mmc = MPS2TZ_MACHINE_GET_CLASS(mms);
|
||||
|
@ -377,7 +431,7 @@ static MemoryRegion *make_uart(MPS2TZMachineState *mms, void *opaque,
|
|||
|
||||
object_initialize_child(OBJECT(mms), name, uart, TYPE_CMSDK_APB_UART);
|
||||
qdev_prop_set_chr(DEVICE(uart), "chardev", serial_hd(i));
|
||||
qdev_prop_set_uint32(DEVICE(uart), "pclk-frq", mmc->sysclk_frq);
|
||||
qdev_prop_set_uint32(DEVICE(uart), "pclk-frq", mmc->apb_periph_frq);
|
||||
sysbus_realize(SYS_BUS_DEVICE(uart), &error_fatal);
|
||||
s = SYS_BUS_DEVICE(uart);
|
||||
sysbus_connect_irq(s, 0, get_sse_irq_in(mms, irqs[0]));
|
||||
|
@ -421,6 +475,7 @@ static MemoryRegion *make_fpgaio(MPS2TZMachineState *mms, void *opaque,
|
|||
object_initialize_child(OBJECT(mms), "fpgaio", fpgaio, TYPE_MPS2_FPGAIO);
|
||||
qdev_prop_set_uint32(DEVICE(fpgaio), "num-leds", mmc->fpgaio_num_leds);
|
||||
qdev_prop_set_bit(DEVICE(fpgaio), "has-switches", mmc->fpgaio_has_switches);
|
||||
qdev_prop_set_bit(DEVICE(fpgaio), "has-dbgctrl", mmc->fpgaio_has_dbgctrl);
|
||||
sysbus_realize(SYS_BUS_DEVICE(fpgaio), &error_fatal);
|
||||
return sysbus_mmio_get_region(SYS_BUS_DEVICE(fpgaio), 0);
|
||||
}
|
||||
|
@ -696,6 +751,7 @@ static void mps2tz_common_init(MachineState *machine)
|
|||
object_property_set_link(OBJECT(&mms->iotkit), "memory",
|
||||
OBJECT(system_memory), &error_abort);
|
||||
qdev_prop_set_uint32(iotkitdev, "EXP_NUMIRQ", mmc->numirq);
|
||||
qdev_prop_set_uint32(iotkitdev, "init-svtor", mmc->init_svtor);
|
||||
qdev_connect_clock_in(iotkitdev, "MAINCLK", mms->sysclk);
|
||||
qdev_connect_clock_in(iotkitdev, "S32KCLK", mms->s32kclk);
|
||||
sysbus_realize(SYS_BUS_DEVICE(&mms->iotkit), &error_fatal);
|
||||
|
@ -770,7 +826,7 @@ static void mps2tz_common_init(MachineState *machine)
|
|||
&error_fatal);
|
||||
qdev_realize(DEVICE(&mms->uart_irq_orgate), NULL, &error_fatal);
|
||||
qdev_connect_gpio_out(DEVICE(&mms->uart_irq_orgate), 0,
|
||||
get_sse_irq_in(mms, 47));
|
||||
get_sse_irq_in(mms, mmc->uart_overflow_irq));
|
||||
|
||||
/* Most of the devices in the FPGA are behind Peripheral Protection
|
||||
* Controllers. The required order for initializing things is:
|
||||
|
@ -887,6 +943,55 @@ static void mps2tz_common_init(MachineState *machine)
|
|||
},
|
||||
};
|
||||
|
||||
const PPCInfo an547_ppcs[] = { {
|
||||
.name = "apb_ppcexp0",
|
||||
.ports = {
|
||||
{ "ssram-mpc", make_mpc, &mms->mpc[0], 0x57000000, 0x1000 },
|
||||
{ "qspi-mpc", make_mpc, &mms->mpc[1], 0x57001000, 0x1000 },
|
||||
{ "ddr-mpc", make_mpc, &mms->mpc[2], 0x57002000, 0x1000 },
|
||||
},
|
||||
}, {
|
||||
.name = "apb_ppcexp1",
|
||||
.ports = {
|
||||
{ "i2c0", make_i2c, &mms->i2c[0], 0x49200000, 0x1000 },
|
||||
{ "i2c1", make_i2c, &mms->i2c[1], 0x49201000, 0x1000 },
|
||||
{ "spi0", make_spi, &mms->spi[0], 0x49202000, 0x1000, { 53 } },
|
||||
{ "spi1", make_spi, &mms->spi[1], 0x49203000, 0x1000, { 54 } },
|
||||
{ "spi2", make_spi, &mms->spi[2], 0x49204000, 0x1000, { 55 } },
|
||||
{ "i2c2", make_i2c, &mms->i2c[2], 0x49205000, 0x1000 },
|
||||
{ "i2c3", make_i2c, &mms->i2c[3], 0x49206000, 0x1000 },
|
||||
{ /* port 7 reserved */ },
|
||||
{ "i2c4", make_i2c, &mms->i2c[4], 0x49208000, 0x1000 },
|
||||
},
|
||||
}, {
|
||||
.name = "apb_ppcexp2",
|
||||
.ports = {
|
||||
{ "scc", make_scc, &mms->scc, 0x49300000, 0x1000 },
|
||||
{ "i2s-audio", make_unimp_dev, &mms->i2s_audio, 0x49301000, 0x1000 },
|
||||
{ "fpgaio", make_fpgaio, &mms->fpgaio, 0x49302000, 0x1000 },
|
||||
{ "uart0", make_uart, &mms->uart[0], 0x49303000, 0x1000, { 33, 34, 43 } },
|
||||
{ "uart1", make_uart, &mms->uart[1], 0x49304000, 0x1000, { 35, 36, 44 } },
|
||||
{ "uart2", make_uart, &mms->uart[2], 0x49305000, 0x1000, { 37, 38, 45 } },
|
||||
{ "uart3", make_uart, &mms->uart[3], 0x49306000, 0x1000, { 39, 40, 46 } },
|
||||
{ "uart4", make_uart, &mms->uart[4], 0x49307000, 0x1000, { 41, 42, 47 } },
|
||||
{ "uart5", make_uart, &mms->uart[5], 0x49308000, 0x1000, { 125, 126, 127 } },
|
||||
|
||||
{ /* port 9 reserved */ },
|
||||
{ "clcd", make_unimp_dev, &mms->cldc, 0x4930a000, 0x1000 },
|
||||
{ "rtc", make_rtc, &mms->rtc, 0x4930b000, 0x1000 },
|
||||
},
|
||||
}, {
|
||||
.name = "ahb_ppcexp0",
|
||||
.ports = {
|
||||
{ "gpio0", make_unimp_dev, &mms->gpio[0], 0x41100000, 0x1000 },
|
||||
{ "gpio1", make_unimp_dev, &mms->gpio[1], 0x41101000, 0x1000 },
|
||||
{ "gpio2", make_unimp_dev, &mms->gpio[2], 0x41102000, 0x1000 },
|
||||
{ "gpio3", make_unimp_dev, &mms->gpio[3], 0x41103000, 0x1000 },
|
||||
{ "eth-usb", make_eth_usb, NULL, 0x41400000, 0x200000, { 49 } },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
switch (mmc->fpga_type) {
|
||||
case FPGA_AN505:
|
||||
case FPGA_AN521:
|
||||
|
@ -897,6 +1002,10 @@ static void mps2tz_common_init(MachineState *machine)
|
|||
ppcs = an524_ppcs;
|
||||
num_ppcs = ARRAY_SIZE(an524_ppcs);
|
||||
break;
|
||||
case FPGA_AN547:
|
||||
ppcs = an547_ppcs;
|
||||
num_ppcs = ARRAY_SIZE(an547_ppcs);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
|
@ -975,6 +1084,11 @@ static void mps2tz_common_init(MachineState *machine)
|
|||
|
||||
create_unimplemented_device("FPGA NS PC", 0x48007000, 0x1000);
|
||||
|
||||
if (mmc->fpga_type == FPGA_AN547) {
|
||||
create_unimplemented_device("U55 timing adapter 0", 0x48102000, 0x1000);
|
||||
create_unimplemented_device("U55 timing adapter 1", 0x48103000, 0x1000);
|
||||
}
|
||||
|
||||
create_non_mpc_ram(mms);
|
||||
|
||||
armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename,
|
||||
|
@ -1041,11 +1155,15 @@ static void mps2tz_an505_class_init(ObjectClass *oc, void *data)
|
|||
mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m33");
|
||||
mmc->scc_id = 0x41045050;
|
||||
mmc->sysclk_frq = 20 * 1000 * 1000; /* 20MHz */
|
||||
mmc->apb_periph_frq = mmc->sysclk_frq;
|
||||
mmc->oscclk = an505_oscclk;
|
||||
mmc->len_oscclk = ARRAY_SIZE(an505_oscclk);
|
||||
mmc->fpgaio_num_leds = 2;
|
||||
mmc->fpgaio_has_switches = false;
|
||||
mmc->fpgaio_has_dbgctrl = false;
|
||||
mmc->numirq = 92;
|
||||
mmc->uart_overflow_irq = 47;
|
||||
mmc->init_svtor = 0x10000000;
|
||||
mmc->raminfo = an505_raminfo;
|
||||
mmc->armsse_type = TYPE_IOTKIT;
|
||||
mps2tz_set_default_ram_info(mmc);
|
||||
|
@ -1064,11 +1182,15 @@ static void mps2tz_an521_class_init(ObjectClass *oc, void *data)
|
|||
mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m33");
|
||||
mmc->scc_id = 0x41045210;
|
||||
mmc->sysclk_frq = 20 * 1000 * 1000; /* 20MHz */
|
||||
mmc->apb_periph_frq = mmc->sysclk_frq;
|
||||
mmc->oscclk = an505_oscclk; /* AN521 is the same as AN505 here */
|
||||
mmc->len_oscclk = ARRAY_SIZE(an505_oscclk);
|
||||
mmc->fpgaio_num_leds = 2;
|
||||
mmc->fpgaio_has_switches = false;
|
||||
mmc->fpgaio_has_dbgctrl = false;
|
||||
mmc->numirq = 92;
|
||||
mmc->uart_overflow_irq = 47;
|
||||
mmc->init_svtor = 0x10000000;
|
||||
mmc->raminfo = an505_raminfo; /* AN521 is the same as AN505 here */
|
||||
mmc->armsse_type = TYPE_SSE200;
|
||||
mps2tz_set_default_ram_info(mmc);
|
||||
|
@ -1087,16 +1209,47 @@ static void mps3tz_an524_class_init(ObjectClass *oc, void *data)
|
|||
mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m33");
|
||||
mmc->scc_id = 0x41045240;
|
||||
mmc->sysclk_frq = 32 * 1000 * 1000; /* 32MHz */
|
||||
mmc->apb_periph_frq = mmc->sysclk_frq;
|
||||
mmc->oscclk = an524_oscclk;
|
||||
mmc->len_oscclk = ARRAY_SIZE(an524_oscclk);
|
||||
mmc->fpgaio_num_leds = 10;
|
||||
mmc->fpgaio_has_switches = true;
|
||||
mmc->fpgaio_has_dbgctrl = false;
|
||||
mmc->numirq = 95;
|
||||
mmc->uart_overflow_irq = 47;
|
||||
mmc->init_svtor = 0x10000000;
|
||||
mmc->raminfo = an524_raminfo;
|
||||
mmc->armsse_type = TYPE_SSE200;
|
||||
mps2tz_set_default_ram_info(mmc);
|
||||
}
|
||||
|
||||
static void mps3tz_an547_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
MachineClass *mc = MACHINE_CLASS(oc);
|
||||
MPS2TZMachineClass *mmc = MPS2TZ_MACHINE_CLASS(oc);
|
||||
|
||||
mc->desc = "ARM MPS3 with AN547 FPGA image for Cortex-M55";
|
||||
mc->default_cpus = 1;
|
||||
mc->min_cpus = mc->default_cpus;
|
||||
mc->max_cpus = mc->default_cpus;
|
||||
mmc->fpga_type = FPGA_AN547;
|
||||
mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m55");
|
||||
mmc->scc_id = 0x41055470;
|
||||
mmc->sysclk_frq = 32 * 1000 * 1000; /* 32MHz */
|
||||
mmc->apb_periph_frq = 25 * 1000 * 1000; /* 25MHz */
|
||||
mmc->oscclk = an524_oscclk; /* same as AN524 */
|
||||
mmc->len_oscclk = ARRAY_SIZE(an524_oscclk);
|
||||
mmc->fpgaio_num_leds = 10;
|
||||
mmc->fpgaio_has_switches = true;
|
||||
mmc->fpgaio_has_dbgctrl = true;
|
||||
mmc->numirq = 96;
|
||||
mmc->uart_overflow_irq = 48;
|
||||
mmc->init_svtor = 0x00000000;
|
||||
mmc->raminfo = an547_raminfo;
|
||||
mmc->armsse_type = TYPE_SSE300;
|
||||
mps2tz_set_default_ram_info(mmc);
|
||||
}
|
||||
|
||||
static const TypeInfo mps2tz_info = {
|
||||
.name = TYPE_MPS2TZ_MACHINE,
|
||||
.parent = TYPE_MACHINE,
|
||||
|
@ -1128,12 +1281,19 @@ static const TypeInfo mps3tz_an524_info = {
|
|||
.class_init = mps3tz_an524_class_init,
|
||||
};
|
||||
|
||||
static const TypeInfo mps3tz_an547_info = {
|
||||
.name = TYPE_MPS3TZ_AN547_MACHINE,
|
||||
.parent = TYPE_MPS2TZ_MACHINE,
|
||||
.class_init = mps3tz_an547_class_init,
|
||||
};
|
||||
|
||||
static void mps2tz_machine_init(void)
|
||||
{
|
||||
type_register_static(&mps2tz_info);
|
||||
type_register_static(&mps2tz_an505_info);
|
||||
type_register_static(&mps2tz_an521_info);
|
||||
type_register_static(&mps3tz_an524_info);
|
||||
type_register_static(&mps3tz_an547_info);
|
||||
}
|
||||
|
||||
type_init(mps2tz_machine_init);
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
#define QSPI_ADDR 0xff0f0000
|
||||
#define LQSPI_ADDR 0xc0000000
|
||||
#define QSPI_IRQ 15
|
||||
#define QSPI_DMA_ADDR 0xff0f0800
|
||||
|
||||
#define DP_ADDR 0xfd4a0000
|
||||
#define DP_IRQ 113
|
||||
|
@ -284,6 +285,8 @@ static void xlnx_zynqmp_init(Object *obj)
|
|||
for (i = 0; i < XLNX_ZYNQMP_NUM_ADMA_CH; i++) {
|
||||
object_initialize_child(obj, "adma[*]", &s->adma[i], TYPE_XLNX_ZDMA);
|
||||
}
|
||||
|
||||
object_initialize_child(obj, "qspi-dma", &s->qspi_dma, TYPE_XLNX_CSU_DMA);
|
||||
}
|
||||
|
||||
static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp)
|
||||
|
@ -301,11 +304,13 @@ static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp)
|
|||
|
||||
ram_size = memory_region_size(s->ddr_ram);
|
||||
|
||||
/* Create the DDR Memory Regions. User friendly checks should happen at
|
||||
/*
|
||||
* Create the DDR Memory Regions. User friendly checks should happen at
|
||||
* the board level
|
||||
*/
|
||||
if (ram_size > XLNX_ZYNQMP_MAX_LOW_RAM_SIZE) {
|
||||
/* The RAM size is above the maximum available for the low DDR.
|
||||
/*
|
||||
* The RAM size is above the maximum available for the low DDR.
|
||||
* Create the high DDR memory region as well.
|
||||
*/
|
||||
assert(ram_size <= XLNX_ZYNQMP_MAX_RAM_SIZE);
|
||||
|
@ -521,7 +526,8 @@ static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp)
|
|||
SysBusDevice *sbd = SYS_BUS_DEVICE(&s->sdhci[i]);
|
||||
Object *sdhci = OBJECT(&s->sdhci[i]);
|
||||
|
||||
/* Compatible with:
|
||||
/*
|
||||
* Compatible with:
|
||||
* - SD Host Controller Specification Version 3.00
|
||||
* - SDIO Specification Version 3.0
|
||||
* - eMMC Specification Version 4.51
|
||||
|
@ -635,6 +641,15 @@ static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp)
|
|||
sysbus_connect_irq(SYS_BUS_DEVICE(&s->adma[i]), 0,
|
||||
gic_spi[adma_ch_intr[i]]);
|
||||
}
|
||||
|
||||
if (!sysbus_realize(SYS_BUS_DEVICE(&s->qspi_dma), errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->qspi_dma), 0, QSPI_DMA_ADDR);
|
||||
sysbus_connect_irq(SYS_BUS_DEVICE(&s->qspi_dma), 0, gic_spi[QSPI_IRQ]);
|
||||
object_property_set_link(OBJECT(&s->qspi), "stream-connected-dma",
|
||||
OBJECT(&s->qspi_dma), errp);
|
||||
}
|
||||
|
||||
static Property xlnx_zynqmp_props[] = {
|
||||
|
|
|
@ -519,7 +519,7 @@ static void cadence_uart_realize(DeviceState *dev, Error **errp)
|
|||
uart_event, NULL, s, NULL, true);
|
||||
}
|
||||
|
||||
static void cadence_uart_refclk_update(void *opaque)
|
||||
static void cadence_uart_refclk_update(void *opaque, ClockEvent event)
|
||||
{
|
||||
CadenceUARTState *s = opaque;
|
||||
|
||||
|
@ -537,7 +537,7 @@ static void cadence_uart_init(Object *obj)
|
|||
sysbus_init_irq(sbd, &s->irq);
|
||||
|
||||
s->refclk = qdev_init_clock_in(DEVICE(obj), "refclk",
|
||||
cadence_uart_refclk_update, s);
|
||||
cadence_uart_refclk_update, s, ClockUpdate);
|
||||
/* initialize the frequency in case the clock remains unconnected */
|
||||
clock_set_hz(s->refclk, UART_DEFAULT_REF_CLK);
|
||||
|
||||
|
|
|
@ -396,7 +396,7 @@ static void ibex_uart_write(void *opaque, hwaddr addr,
|
|||
}
|
||||
}
|
||||
|
||||
static void ibex_uart_clk_update(void *opaque)
|
||||
static void ibex_uart_clk_update(void *opaque, ClockEvent event)
|
||||
{
|
||||
IbexUartState *s = opaque;
|
||||
|
||||
|
@ -466,7 +466,7 @@ static void ibex_uart_init(Object *obj)
|
|||
IbexUartState *s = IBEX_UART(obj);
|
||||
|
||||
s->f_clk = qdev_init_clock_in(DEVICE(obj), "f_clock",
|
||||
ibex_uart_clk_update, s);
|
||||
ibex_uart_clk_update, s, ClockUpdate);
|
||||
clock_set_hz(s->f_clk, IBEX_UART_CLOCK);
|
||||
|
||||
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->tx_watermark);
|
||||
|
|
|
@ -309,7 +309,7 @@ static void pl011_event(void *opaque, QEMUChrEvent event)
|
|||
pl011_put_fifo(opaque, 0x400);
|
||||
}
|
||||
|
||||
static void pl011_clock_update(void *opaque)
|
||||
static void pl011_clock_update(void *opaque, ClockEvent event)
|
||||
{
|
||||
PL011State *s = PL011(opaque);
|
||||
|
||||
|
@ -378,7 +378,8 @@ static void pl011_init(Object *obj)
|
|||
sysbus_init_irq(sbd, &s->irq[i]);
|
||||
}
|
||||
|
||||
s->clk = qdev_init_clock_in(DEVICE(obj), "clk", pl011_clock_update, s);
|
||||
s->clk = qdev_init_clock_in(DEVICE(obj), "clk", pl011_clock_update, s,
|
||||
ClockUpdate);
|
||||
|
||||
s->read_trigger = 1;
|
||||
s->ifl = 0x12;
|
||||
|
|
|
@ -39,15 +39,17 @@ Clock *clock_new(Object *parent, const char *name)
|
|||
return clk;
|
||||
}
|
||||
|
||||
void clock_set_callback(Clock *clk, ClockCallback *cb, void *opaque)
|
||||
void clock_set_callback(Clock *clk, ClockCallback *cb, void *opaque,
|
||||
unsigned int events)
|
||||
{
|
||||
clk->callback = cb;
|
||||
clk->callback_opaque = opaque;
|
||||
clk->callback_events = events;
|
||||
}
|
||||
|
||||
void clock_clear_callback(Clock *clk)
|
||||
{
|
||||
clock_set_callback(clk, NULL, NULL);
|
||||
clock_set_callback(clk, NULL, NULL, 0);
|
||||
}
|
||||
|
||||
bool clock_set(Clock *clk, uint64_t period)
|
||||
|
@ -62,18 +64,32 @@ bool clock_set(Clock *clk, uint64_t period)
|
|||
return true;
|
||||
}
|
||||
|
||||
static void clock_call_callback(Clock *clk, ClockEvent event)
|
||||
{
|
||||
/*
|
||||
* Call the Clock's callback for this event, if it has one and
|
||||
* is interested in this event.
|
||||
*/
|
||||
if (clk->callback && (clk->callback_events & event)) {
|
||||
clk->callback(clk->callback_opaque, event);
|
||||
}
|
||||
}
|
||||
|
||||
static void clock_propagate_period(Clock *clk, bool call_callbacks)
|
||||
{
|
||||
Clock *child;
|
||||
|
||||
QLIST_FOREACH(child, &clk->children, sibling) {
|
||||
if (child->period != clk->period) {
|
||||
if (call_callbacks) {
|
||||
clock_call_callback(child, ClockPreUpdate);
|
||||
}
|
||||
child->period = clk->period;
|
||||
trace_clock_update(CLOCK_PATH(child), CLOCK_PATH(clk),
|
||||
CLOCK_PERIOD_TO_HZ(clk->period),
|
||||
call_callbacks);
|
||||
if (call_callbacks && child->callback) {
|
||||
child->callback(child->callback_opaque);
|
||||
if (call_callbacks) {
|
||||
clock_call_callback(child, ClockUpdate);
|
||||
}
|
||||
clock_propagate_period(child, call_callbacks);
|
||||
}
|
||||
|
|
|
@ -111,7 +111,8 @@ Clock *qdev_init_clock_out(DeviceState *dev, const char *name)
|
|||
}
|
||||
|
||||
Clock *qdev_init_clock_in(DeviceState *dev, const char *name,
|
||||
ClockCallback *callback, void *opaque)
|
||||
ClockCallback *callback, void *opaque,
|
||||
unsigned int events)
|
||||
{
|
||||
NamedClockList *ncl;
|
||||
|
||||
|
@ -120,7 +121,7 @@ Clock *qdev_init_clock_in(DeviceState *dev, const char *name,
|
|||
ncl = qdev_init_clocklist(dev, name, false, NULL);
|
||||
|
||||
if (callback) {
|
||||
clock_set_callback(ncl->clock, callback, opaque);
|
||||
clock_set_callback(ncl->clock, callback, opaque, events);
|
||||
}
|
||||
return ncl->clock;
|
||||
}
|
||||
|
@ -137,7 +138,8 @@ void qdev_init_clocks(DeviceState *dev, const ClockPortInitArray clocks)
|
|||
if (elem->is_output) {
|
||||
*clkp = qdev_init_clock_out(dev, elem->name);
|
||||
} else {
|
||||
*clkp = qdev_init_clock_in(dev, elem->name, elem->callback, dev);
|
||||
*clkp = qdev_init_clock_in(dev, elem->name, elem->callback, dev,
|
||||
elem->callback_events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,3 +26,7 @@ config STP2000
|
|||
|
||||
config SIFIVE_PDMA
|
||||
bool
|
||||
|
||||
config XLNX_CSU_DMA
|
||||
bool
|
||||
select REGISTER
|
||||
|
|
|
@ -14,3 +14,4 @@ softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_dma.c', 'soc_dma.c'))
|
|||
softmmu_ss.add(when: 'CONFIG_PXA2XX', if_true: files('pxa2xx_dma.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_dma.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_SIFIVE_PDMA', if_true: files('sifive_pdma.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_XLNX_CSU_DMA', if_true: files('xlnx_csu_dma.c'))
|
||||
|
|
745
hw/dma/xlnx_csu_dma.c
Normal file
745
hw/dma/xlnx_csu_dma.c
Normal file
|
@ -0,0 +1,745 @@
|
|||
/*
|
||||
* Xilinx Platform CSU Stream DMA emulation
|
||||
*
|
||||
* This implementation is based on
|
||||
* https://github.com/Xilinx/qemu/blob/master/hw/dma/csu_stream_dma.c
|
||||
*
|
||||
* 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 or
|
||||
* (at your option) version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qapi/error.h"
|
||||
#include "hw/hw.h"
|
||||
#include "hw/irq.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "sysemu/dma.h"
|
||||
#include "hw/ptimer.h"
|
||||
#include "hw/stream.h"
|
||||
#include "hw/register.h"
|
||||
#include "hw/dma/xlnx_csu_dma.h"
|
||||
|
||||
/*
|
||||
* Ref: UG1087 (v1.7) February 8, 2019
|
||||
* https://www.xilinx.com/html_docs/registers/ug1087/ug1087-zynq-ultrascale-registers.html
|
||||
* CSUDMA Module section
|
||||
*/
|
||||
REG32(ADDR, 0x0)
|
||||
FIELD(ADDR, ADDR, 2, 30) /* wo */
|
||||
REG32(SIZE, 0x4)
|
||||
FIELD(SIZE, SIZE, 2, 27) /* wo */
|
||||
FIELD(SIZE, LAST_WORD, 0, 1) /* rw, only exists in SRC */
|
||||
REG32(STATUS, 0x8)
|
||||
FIELD(STATUS, DONE_CNT, 13, 3) /* wtc */
|
||||
FIELD(STATUS, FIFO_LEVEL, 5, 8) /* ro */
|
||||
FIELD(STATUS, OUTSTANDING, 1, 4) /* ro */
|
||||
FIELD(STATUS, BUSY, 0, 1) /* ro */
|
||||
REG32(CTRL, 0xc)
|
||||
FIELD(CTRL, FIFOTHRESH, 25, 7) /* rw, only exists in DST, reset 0x40 */
|
||||
FIELD(CTRL, APB_ERR_RESP, 24, 1) /* rw */
|
||||
FIELD(CTRL, ENDIANNESS, 23, 1) /* rw */
|
||||
FIELD(CTRL, AXI_BRST_TYPE, 22, 1) /* rw */
|
||||
FIELD(CTRL, TIMEOUT_VAL, 10, 12) /* rw, reset: 0xFFE */
|
||||
FIELD(CTRL, FIFO_THRESH, 2, 8) /* rw, reset: 0x80 */
|
||||
FIELD(CTRL, PAUSE_STRM, 1, 1) /* rw */
|
||||
FIELD(CTRL, PAUSE_MEM, 0, 1) /* rw */
|
||||
REG32(CRC, 0x10)
|
||||
REG32(INT_STATUS, 0x14)
|
||||
FIELD(INT_STATUS, FIFO_OVERFLOW, 7, 1) /* wtc */
|
||||
FIELD(INT_STATUS, INVALID_APB, 6, 1) /* wtc */
|
||||
FIELD(INT_STATUS, THRESH_HIT, 5, 1) /* wtc */
|
||||
FIELD(INT_STATUS, TIMEOUT_MEM, 4, 1) /* wtc */
|
||||
FIELD(INT_STATUS, TIMEOUT_STRM, 3, 1) /* wtc */
|
||||
FIELD(INT_STATUS, AXI_BRESP_ERR, 2, 1) /* wtc, SRC: AXI_RDERR */
|
||||
FIELD(INT_STATUS, DONE, 1, 1) /* wtc */
|
||||
FIELD(INT_STATUS, MEM_DONE, 0, 1) /* wtc */
|
||||
REG32(INT_ENABLE, 0x18)
|
||||
FIELD(INT_ENABLE, FIFO_OVERFLOW, 7, 1) /* wtc */
|
||||
FIELD(INT_ENABLE, INVALID_APB, 6, 1) /* wtc */
|
||||
FIELD(INT_ENABLE, THRESH_HIT, 5, 1) /* wtc */
|
||||
FIELD(INT_ENABLE, TIMEOUT_MEM, 4, 1) /* wtc */
|
||||
FIELD(INT_ENABLE, TIMEOUT_STRM, 3, 1) /* wtc */
|
||||
FIELD(INT_ENABLE, AXI_BRESP_ERR, 2, 1) /* wtc, SRC: AXI_RDERR */
|
||||
FIELD(INT_ENABLE, DONE, 1, 1) /* wtc */
|
||||
FIELD(INT_ENABLE, MEM_DONE, 0, 1) /* wtc */
|
||||
REG32(INT_DISABLE, 0x1c)
|
||||
FIELD(INT_DISABLE, FIFO_OVERFLOW, 7, 1) /* wtc */
|
||||
FIELD(INT_DISABLE, INVALID_APB, 6, 1) /* wtc */
|
||||
FIELD(INT_DISABLE, THRESH_HIT, 5, 1) /* wtc */
|
||||
FIELD(INT_DISABLE, TIMEOUT_MEM, 4, 1) /* wtc */
|
||||
FIELD(INT_DISABLE, TIMEOUT_STRM, 3, 1) /* wtc */
|
||||
FIELD(INT_DISABLE, AXI_BRESP_ERR, 2, 1) /* wtc, SRC: AXI_RDERR */
|
||||
FIELD(INT_DISABLE, DONE, 1, 1) /* wtc */
|
||||
FIELD(INT_DISABLE, MEM_DONE, 0, 1) /* wtc */
|
||||
REG32(INT_MASK, 0x20)
|
||||
FIELD(INT_MASK, FIFO_OVERFLOW, 7, 1) /* ro, reset: 0x1 */
|
||||
FIELD(INT_MASK, INVALID_APB, 6, 1) /* ro, reset: 0x1 */
|
||||
FIELD(INT_MASK, THRESH_HIT, 5, 1) /* ro, reset: 0x1 */
|
||||
FIELD(INT_MASK, TIMEOUT_MEM, 4, 1) /* ro, reset: 0x1 */
|
||||
FIELD(INT_MASK, TIMEOUT_STRM, 3, 1) /* ro, reset: 0x1 */
|
||||
FIELD(INT_MASK, AXI_BRESP_ERR, 2, 1) /* ro, reset: 0x1, SRC: AXI_RDERR */
|
||||
FIELD(INT_MASK, DONE, 1, 1) /* ro, reset: 0x1 */
|
||||
FIELD(INT_MASK, MEM_DONE, 0, 1) /* ro, reset: 0x1 */
|
||||
REG32(CTRL2, 0x24)
|
||||
FIELD(CTRL2, ARCACHE, 24, 3) /* rw */
|
||||
FIELD(CTRL2, ROUTE_BIT, 23, 1) /* rw */
|
||||
FIELD(CTRL2, TIMEOUT_EN, 22, 1) /* rw */
|
||||
FIELD(CTRL2, TIMEOUT_PRE, 4, 12) /* rw, reset: 0xFFF */
|
||||
FIELD(CTRL2, MAX_OUTS_CMDS, 0, 4) /* rw, reset: 0x8 */
|
||||
REG32(ADDR_MSB, 0x28)
|
||||
FIELD(ADDR_MSB, ADDR_MSB, 0, 17) /* wo */
|
||||
|
||||
#define R_CTRL_TIMEOUT_VAL_RESET (0xFFE)
|
||||
#define R_CTRL_FIFO_THRESH_RESET (0x80)
|
||||
#define R_CTRL_FIFOTHRESH_RESET (0x40)
|
||||
|
||||
#define R_CTRL2_TIMEOUT_PRE_RESET (0xFFF)
|
||||
#define R_CTRL2_MAX_OUTS_CMDS_RESET (0x8)
|
||||
|
||||
#define XLNX_CSU_DMA_ERR_DEBUG (0)
|
||||
#define XLNX_CSU_DMA_INT_R_MASK (0xff)
|
||||
|
||||
/* UG1807: Set the prescaler value for the timeout in clk (~2.5ns) cycles */
|
||||
#define XLNX_CSU_DMA_TIMER_FREQ (400 * 1000 * 1000)
|
||||
|
||||
static bool xlnx_csu_dma_is_paused(XlnxCSUDMA *s)
|
||||
{
|
||||
bool paused;
|
||||
|
||||
paused = !!(s->regs[R_CTRL] & R_CTRL_PAUSE_STRM_MASK);
|
||||
paused |= !!(s->regs[R_CTRL] & R_CTRL_PAUSE_MEM_MASK);
|
||||
|
||||
return paused;
|
||||
}
|
||||
|
||||
static bool xlnx_csu_dma_get_eop(XlnxCSUDMA *s)
|
||||
{
|
||||
return s->r_size_last_word;
|
||||
}
|
||||
|
||||
static bool xlnx_csu_dma_burst_is_fixed(XlnxCSUDMA *s)
|
||||
{
|
||||
return !!(s->regs[R_CTRL] & R_CTRL_AXI_BRST_TYPE_MASK);
|
||||
}
|
||||
|
||||
static bool xlnx_csu_dma_timeout_enabled(XlnxCSUDMA *s)
|
||||
{
|
||||
return !!(s->regs[R_CTRL2] & R_CTRL2_TIMEOUT_EN_MASK);
|
||||
}
|
||||
|
||||
static void xlnx_csu_dma_update_done_cnt(XlnxCSUDMA *s, int a)
|
||||
{
|
||||
int cnt;
|
||||
|
||||
/* Increase DONE_CNT */
|
||||
cnt = ARRAY_FIELD_EX32(s->regs, STATUS, DONE_CNT) + a;
|
||||
ARRAY_FIELD_DP32(s->regs, STATUS, DONE_CNT, cnt);
|
||||
}
|
||||
|
||||
static void xlnx_csu_dma_data_process(XlnxCSUDMA *s, uint8_t *buf, uint32_t len)
|
||||
{
|
||||
uint32_t bswap;
|
||||
uint32_t i;
|
||||
|
||||
bswap = s->regs[R_CTRL] & R_CTRL_ENDIANNESS_MASK;
|
||||
if (s->is_dst && !bswap) {
|
||||
/* Fast when ENDIANNESS cleared */
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < len; i += 4) {
|
||||
uint8_t *b = &buf[i];
|
||||
union {
|
||||
uint8_t u8[4];
|
||||
uint32_t u32;
|
||||
} v = {
|
||||
.u8 = { b[0], b[1], b[2], b[3] }
|
||||
};
|
||||
|
||||
if (!s->is_dst) {
|
||||
s->regs[R_CRC] += v.u32;
|
||||
}
|
||||
if (bswap) {
|
||||
/*
|
||||
* No point using bswap, we need to writeback
|
||||
* into a potentially unaligned pointer.
|
||||
*/
|
||||
b[0] = v.u8[3];
|
||||
b[1] = v.u8[2];
|
||||
b[2] = v.u8[1];
|
||||
b[3] = v.u8[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void xlnx_csu_dma_update_irq(XlnxCSUDMA *s)
|
||||
{
|
||||
qemu_set_irq(s->irq, !!(s->regs[R_INT_STATUS] & ~s->regs[R_INT_MASK]));
|
||||
}
|
||||
|
||||
/* len is in bytes */
|
||||
static uint32_t xlnx_csu_dma_read(XlnxCSUDMA *s, uint8_t *buf, uint32_t len)
|
||||
{
|
||||
hwaddr addr = (hwaddr)s->regs[R_ADDR_MSB] << 32 | s->regs[R_ADDR];
|
||||
MemTxResult result = MEMTX_OK;
|
||||
|
||||
if (xlnx_csu_dma_burst_is_fixed(s)) {
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < len && (result == MEMTX_OK); i += s->width) {
|
||||
uint32_t mlen = MIN(len - i, s->width);
|
||||
|
||||
result = address_space_rw(s->dma_as, addr, s->attr,
|
||||
buf + i, mlen, false);
|
||||
}
|
||||
} else {
|
||||
result = address_space_rw(s->dma_as, addr, s->attr, buf, len, false);
|
||||
}
|
||||
|
||||
if (result == MEMTX_OK) {
|
||||
xlnx_csu_dma_data_process(s, buf, len);
|
||||
} else {
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad address " TARGET_FMT_plx
|
||||
" for mem read", __func__, addr);
|
||||
s->regs[R_INT_STATUS] |= R_INT_STATUS_AXI_BRESP_ERR_MASK;
|
||||
xlnx_csu_dma_update_irq(s);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/* len is in bytes */
|
||||
static uint32_t xlnx_csu_dma_write(XlnxCSUDMA *s, uint8_t *buf, uint32_t len)
|
||||
{
|
||||
hwaddr addr = (hwaddr)s->regs[R_ADDR_MSB] << 32 | s->regs[R_ADDR];
|
||||
MemTxResult result = MEMTX_OK;
|
||||
|
||||
xlnx_csu_dma_data_process(s, buf, len);
|
||||
if (xlnx_csu_dma_burst_is_fixed(s)) {
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < len && (result == MEMTX_OK); i += s->width) {
|
||||
uint32_t mlen = MIN(len - i, s->width);
|
||||
|
||||
result = address_space_rw(s->dma_as, addr, s->attr,
|
||||
buf, mlen, true);
|
||||
buf += mlen;
|
||||
}
|
||||
} else {
|
||||
result = address_space_rw(s->dma_as, addr, s->attr, buf, len, true);
|
||||
}
|
||||
|
||||
if (result != MEMTX_OK) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad address " TARGET_FMT_plx
|
||||
" for mem write", __func__, addr);
|
||||
s->regs[R_INT_STATUS] |= R_INT_STATUS_AXI_BRESP_ERR_MASK;
|
||||
xlnx_csu_dma_update_irq(s);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static void xlnx_csu_dma_done(XlnxCSUDMA *s)
|
||||
{
|
||||
s->regs[R_STATUS] &= ~R_STATUS_BUSY_MASK;
|
||||
s->regs[R_INT_STATUS] |= R_INT_STATUS_DONE_MASK;
|
||||
|
||||
if (!s->is_dst) {
|
||||
s->regs[R_INT_STATUS] |= R_INT_STATUS_MEM_DONE_MASK;
|
||||
}
|
||||
|
||||
xlnx_csu_dma_update_done_cnt(s, 1);
|
||||
}
|
||||
|
||||
static uint32_t xlnx_csu_dma_advance(XlnxCSUDMA *s, uint32_t len)
|
||||
{
|
||||
uint32_t size = s->regs[R_SIZE];
|
||||
hwaddr dst = (hwaddr)s->regs[R_ADDR_MSB] << 32 | s->regs[R_ADDR];
|
||||
|
||||
assert(len <= size);
|
||||
|
||||
size -= len;
|
||||
s->regs[R_SIZE] = size;
|
||||
|
||||
if (!xlnx_csu_dma_burst_is_fixed(s)) {
|
||||
dst += len;
|
||||
s->regs[R_ADDR] = (uint32_t) dst;
|
||||
s->regs[R_ADDR_MSB] = dst >> 32;
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
xlnx_csu_dma_done(s);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static void xlnx_csu_dma_src_notify(void *opaque)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(opaque);
|
||||
unsigned char buf[4 * 1024];
|
||||
size_t rlen = 0;
|
||||
|
||||
ptimer_transaction_begin(s->src_timer);
|
||||
/* Stop the backpreassure timer */
|
||||
ptimer_stop(s->src_timer);
|
||||
|
||||
while (s->regs[R_SIZE] && !xlnx_csu_dma_is_paused(s) &&
|
||||
stream_can_push(s->tx_dev, xlnx_csu_dma_src_notify, s)) {
|
||||
uint32_t plen = MIN(s->regs[R_SIZE], sizeof buf);
|
||||
bool eop = false;
|
||||
|
||||
/* Did we fit it all? */
|
||||
if (s->regs[R_SIZE] == plen && xlnx_csu_dma_get_eop(s)) {
|
||||
eop = true;
|
||||
}
|
||||
|
||||
/* DMA transfer */
|
||||
xlnx_csu_dma_read(s, buf, plen);
|
||||
rlen = stream_push(s->tx_dev, buf, plen, eop);
|
||||
xlnx_csu_dma_advance(s, rlen);
|
||||
}
|
||||
|
||||
if (xlnx_csu_dma_timeout_enabled(s) && s->regs[R_SIZE] &&
|
||||
!stream_can_push(s->tx_dev, xlnx_csu_dma_src_notify, s)) {
|
||||
uint32_t timeout = ARRAY_FIELD_EX32(s->regs, CTRL, TIMEOUT_VAL);
|
||||
uint32_t div = ARRAY_FIELD_EX32(s->regs, CTRL2, TIMEOUT_PRE) + 1;
|
||||
uint32_t freq = XLNX_CSU_DMA_TIMER_FREQ;
|
||||
|
||||
freq /= div;
|
||||
ptimer_set_freq(s->src_timer, freq);
|
||||
ptimer_set_count(s->src_timer, timeout);
|
||||
ptimer_run(s->src_timer, 1);
|
||||
}
|
||||
|
||||
ptimer_transaction_commit(s->src_timer);
|
||||
xlnx_csu_dma_update_irq(s);
|
||||
}
|
||||
|
||||
static uint64_t addr_pre_write(RegisterInfo *reg, uint64_t val)
|
||||
{
|
||||
/* Address is word aligned */
|
||||
return val & R_ADDR_ADDR_MASK;
|
||||
}
|
||||
|
||||
static uint64_t size_pre_write(RegisterInfo *reg, uint64_t val)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
|
||||
|
||||
if (s->regs[R_SIZE] != 0) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: Starting DMA while already running.\n", __func__);
|
||||
}
|
||||
|
||||
if (!s->is_dst) {
|
||||
s->r_size_last_word = !!(val & R_SIZE_LAST_WORD_MASK);
|
||||
}
|
||||
|
||||
/* Size is word aligned */
|
||||
return val & R_SIZE_SIZE_MASK;
|
||||
}
|
||||
|
||||
static uint64_t size_post_read(RegisterInfo *reg, uint64_t val)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
|
||||
|
||||
return val | s->r_size_last_word;
|
||||
}
|
||||
|
||||
static void size_post_write(RegisterInfo *reg, uint64_t val)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
|
||||
|
||||
s->regs[R_STATUS] |= R_STATUS_BUSY_MASK;
|
||||
|
||||
/*
|
||||
* Note that if SIZE is programmed to 0, and the DMA is started,
|
||||
* the interrupts DONE and MEM_DONE will be asserted.
|
||||
*/
|
||||
if (s->regs[R_SIZE] == 0) {
|
||||
xlnx_csu_dma_done(s);
|
||||
xlnx_csu_dma_update_irq(s);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set SIZE is considered the last step in transfer configuration */
|
||||
if (!s->is_dst) {
|
||||
xlnx_csu_dma_src_notify(s);
|
||||
} else {
|
||||
if (s->notify) {
|
||||
s->notify(s->notify_opaque);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t status_pre_write(RegisterInfo *reg, uint64_t val)
|
||||
{
|
||||
return val & (R_STATUS_DONE_CNT_MASK | R_STATUS_BUSY_MASK);
|
||||
}
|
||||
|
||||
static void ctrl_post_write(RegisterInfo *reg, uint64_t val)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
|
||||
|
||||
if (!s->is_dst) {
|
||||
if (!xlnx_csu_dma_is_paused(s)) {
|
||||
xlnx_csu_dma_src_notify(s);
|
||||
}
|
||||
} else {
|
||||
if (!xlnx_csu_dma_is_paused(s) && s->notify) {
|
||||
s->notify(s->notify_opaque);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t int_status_pre_write(RegisterInfo *reg, uint64_t val)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
|
||||
|
||||
/* DMA counter decrements when flag 'DONE' is cleared */
|
||||
if ((val & s->regs[R_INT_STATUS] & R_INT_STATUS_DONE_MASK)) {
|
||||
xlnx_csu_dma_update_done_cnt(s, -1);
|
||||
}
|
||||
|
||||
return s->regs[R_INT_STATUS] & ~val;
|
||||
}
|
||||
|
||||
static void int_status_post_write(RegisterInfo *reg, uint64_t val)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
|
||||
|
||||
xlnx_csu_dma_update_irq(s);
|
||||
}
|
||||
|
||||
static uint64_t int_enable_pre_write(RegisterInfo *reg, uint64_t val)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
|
||||
uint32_t v32 = val;
|
||||
|
||||
/*
|
||||
* R_INT_ENABLE doesn't have its own state.
|
||||
* It is used to indirectly modify R_INT_MASK.
|
||||
*
|
||||
* 1: Enable this interrupt field (the mask bit will be cleared to 0)
|
||||
* 0: No effect
|
||||
*/
|
||||
s->regs[R_INT_MASK] &= ~v32;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void int_enable_post_write(RegisterInfo *reg, uint64_t val)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
|
||||
|
||||
xlnx_csu_dma_update_irq(s);
|
||||
}
|
||||
|
||||
static uint64_t int_disable_pre_write(RegisterInfo *reg, uint64_t val)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
|
||||
uint32_t v32 = val;
|
||||
|
||||
/*
|
||||
* R_INT_DISABLE doesn't have its own state.
|
||||
* It is used to indirectly modify R_INT_MASK.
|
||||
*
|
||||
* 1: Disable this interrupt field (the mask bit will be set to 1)
|
||||
* 0: No effect
|
||||
*/
|
||||
s->regs[R_INT_MASK] |= v32;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void int_disable_post_write(RegisterInfo *reg, uint64_t val)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
|
||||
|
||||
xlnx_csu_dma_update_irq(s);
|
||||
}
|
||||
|
||||
static uint64_t addr_msb_pre_write(RegisterInfo *reg, uint64_t val)
|
||||
{
|
||||
return val & R_ADDR_MSB_ADDR_MSB_MASK;
|
||||
}
|
||||
|
||||
static const RegisterAccessInfo *xlnx_csu_dma_regs_info[] = {
|
||||
#define DMACH_REGINFO(NAME, snd) \
|
||||
(const RegisterAccessInfo []) { \
|
||||
{ \
|
||||
.name = #NAME "_ADDR", \
|
||||
.addr = A_ADDR, \
|
||||
.pre_write = addr_pre_write \
|
||||
}, { \
|
||||
.name = #NAME "_SIZE", \
|
||||
.addr = A_SIZE, \
|
||||
.pre_write = size_pre_write, \
|
||||
.post_write = size_post_write, \
|
||||
.post_read = size_post_read \
|
||||
}, { \
|
||||
.name = #NAME "_STATUS", \
|
||||
.addr = A_STATUS, \
|
||||
.pre_write = status_pre_write, \
|
||||
.w1c = R_STATUS_DONE_CNT_MASK, \
|
||||
.ro = (R_STATUS_BUSY_MASK \
|
||||
| R_STATUS_FIFO_LEVEL_MASK \
|
||||
| R_STATUS_OUTSTANDING_MASK) \
|
||||
}, { \
|
||||
.name = #NAME "_CTRL", \
|
||||
.addr = A_CTRL, \
|
||||
.post_write = ctrl_post_write, \
|
||||
.reset = ((R_CTRL_TIMEOUT_VAL_RESET << R_CTRL_TIMEOUT_VAL_SHIFT) \
|
||||
| (R_CTRL_FIFO_THRESH_RESET << R_CTRL_FIFO_THRESH_SHIFT)\
|
||||
| (snd ? 0 : R_CTRL_FIFOTHRESH_RESET \
|
||||
<< R_CTRL_FIFOTHRESH_SHIFT)) \
|
||||
}, { \
|
||||
.name = #NAME "_CRC", \
|
||||
.addr = A_CRC, \
|
||||
}, { \
|
||||
.name = #NAME "_INT_STATUS", \
|
||||
.addr = A_INT_STATUS, \
|
||||
.pre_write = int_status_pre_write, \
|
||||
.post_write = int_status_post_write \
|
||||
}, { \
|
||||
.name = #NAME "_INT_ENABLE", \
|
||||
.addr = A_INT_ENABLE, \
|
||||
.pre_write = int_enable_pre_write, \
|
||||
.post_write = int_enable_post_write \
|
||||
}, { \
|
||||
.name = #NAME "_INT_DISABLE", \
|
||||
.addr = A_INT_DISABLE, \
|
||||
.pre_write = int_disable_pre_write, \
|
||||
.post_write = int_disable_post_write \
|
||||
}, { \
|
||||
.name = #NAME "_INT_MASK", \
|
||||
.addr = A_INT_MASK, \
|
||||
.ro = ~0, \
|
||||
.reset = XLNX_CSU_DMA_INT_R_MASK \
|
||||
}, { \
|
||||
.name = #NAME "_CTRL2", \
|
||||
.addr = A_CTRL2, \
|
||||
.reset = ((R_CTRL2_TIMEOUT_PRE_RESET \
|
||||
<< R_CTRL2_TIMEOUT_PRE_SHIFT) \
|
||||
| (R_CTRL2_MAX_OUTS_CMDS_RESET \
|
||||
<< R_CTRL2_MAX_OUTS_CMDS_SHIFT)) \
|
||||
}, { \
|
||||
.name = #NAME "_ADDR_MSB", \
|
||||
.addr = A_ADDR_MSB, \
|
||||
.pre_write = addr_msb_pre_write \
|
||||
} \
|
||||
}
|
||||
|
||||
DMACH_REGINFO(DMA_SRC, true),
|
||||
DMACH_REGINFO(DMA_DST, false)
|
||||
};
|
||||
|
||||
static const MemoryRegionOps xlnx_csu_dma_ops = {
|
||||
.read = register_read_memory,
|
||||
.write = register_write_memory,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
}
|
||||
};
|
||||
|
||||
static void xlnx_csu_dma_src_timeout_hit(void *opaque)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(opaque);
|
||||
|
||||
/* Ignore if the timeout is masked */
|
||||
if (!xlnx_csu_dma_timeout_enabled(s)) {
|
||||
return;
|
||||
}
|
||||
|
||||
s->regs[R_INT_STATUS] |= R_INT_STATUS_TIMEOUT_STRM_MASK;
|
||||
xlnx_csu_dma_update_irq(s);
|
||||
}
|
||||
|
||||
static size_t xlnx_csu_dma_stream_push(StreamSink *obj, uint8_t *buf,
|
||||
size_t len, bool eop)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(obj);
|
||||
uint32_t size = s->regs[R_SIZE];
|
||||
uint32_t mlen = MIN(size, len) & (~3); /* Size is word aligned */
|
||||
|
||||
/* Be called when it's DST */
|
||||
assert(s->is_dst);
|
||||
|
||||
if (size == 0 || len <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (len && (xlnx_csu_dma_is_paused(s) || mlen == 0)) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"csu-dma: DST channel dropping %zd b of data.\n", len);
|
||||
s->regs[R_INT_STATUS] |= R_INT_STATUS_FIFO_OVERFLOW_MASK;
|
||||
return len;
|
||||
}
|
||||
|
||||
if (xlnx_csu_dma_write(s, buf, mlen) != mlen) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
xlnx_csu_dma_advance(s, mlen);
|
||||
xlnx_csu_dma_update_irq(s);
|
||||
|
||||
return mlen;
|
||||
}
|
||||
|
||||
static bool xlnx_csu_dma_stream_can_push(StreamSink *obj,
|
||||
StreamCanPushNotifyFn notify,
|
||||
void *notify_opaque)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(obj);
|
||||
|
||||
if (s->regs[R_SIZE] != 0) {
|
||||
return true;
|
||||
} else {
|
||||
s->notify = notify;
|
||||
s->notify_opaque = notify_opaque;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void xlnx_csu_dma_reset(DeviceState *dev)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(dev);
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(s->regs_info); ++i) {
|
||||
register_reset(&s->regs_info[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void xlnx_csu_dma_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(dev);
|
||||
RegisterInfoArray *reg_array;
|
||||
|
||||
reg_array =
|
||||
register_init_block32(dev, xlnx_csu_dma_regs_info[!!s->is_dst],
|
||||
XLNX_CSU_DMA_R_MAX,
|
||||
s->regs_info, s->regs,
|
||||
&xlnx_csu_dma_ops,
|
||||
XLNX_CSU_DMA_ERR_DEBUG,
|
||||
XLNX_CSU_DMA_R_MAX * 4);
|
||||
memory_region_add_subregion(&s->iomem,
|
||||
0x0,
|
||||
®_array->mem);
|
||||
|
||||
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
|
||||
sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
|
||||
|
||||
if (!s->is_dst && !s->tx_dev) {
|
||||
error_setg(errp, "zynqmp.csu-dma: Stream not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
s->src_timer = ptimer_init(xlnx_csu_dma_src_timeout_hit,
|
||||
s, PTIMER_POLICY_DEFAULT);
|
||||
|
||||
if (s->dma_mr) {
|
||||
s->dma_as = g_malloc0(sizeof(AddressSpace));
|
||||
address_space_init(s->dma_as, s->dma_mr, NULL);
|
||||
} else {
|
||||
s->dma_as = &address_space_memory;
|
||||
}
|
||||
|
||||
s->attr = MEMTXATTRS_UNSPECIFIED;
|
||||
|
||||
s->r_size_last_word = 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_xlnx_csu_dma = {
|
||||
.name = TYPE_XLNX_CSU_DMA,
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.minimum_version_id_old = 0,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_PTIMER(src_timer, XlnxCSUDMA),
|
||||
VMSTATE_UINT16(width, XlnxCSUDMA),
|
||||
VMSTATE_BOOL(is_dst, XlnxCSUDMA),
|
||||
VMSTATE_BOOL(r_size_last_word, XlnxCSUDMA),
|
||||
VMSTATE_UINT32_ARRAY(regs, XlnxCSUDMA, XLNX_CSU_DMA_R_MAX),
|
||||
VMSTATE_END_OF_LIST(),
|
||||
}
|
||||
};
|
||||
|
||||
static Property xlnx_csu_dma_properties[] = {
|
||||
/*
|
||||
* Ref PG021, Stream Data Width:
|
||||
* Data width in bits of the AXI S2MM AXI4-Stream Data bus.
|
||||
* This value must be equal or less than the Memory Map Data Width.
|
||||
* Valid values are 8, 16, 32, 64, 128, 512 and 1024.
|
||||
* "dma-width" is the byte value of the "Stream Data Width".
|
||||
*/
|
||||
DEFINE_PROP_UINT16("dma-width", XlnxCSUDMA, width, 4),
|
||||
/*
|
||||
* The CSU DMA is a two-channel, simple DMA, allowing separate control of
|
||||
* the SRC (read) channel and DST (write) channel. "is-dst" is used to mark
|
||||
* which channel the device is connected to.
|
||||
*/
|
||||
DEFINE_PROP_BOOL("is-dst", XlnxCSUDMA, is_dst, true),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void xlnx_csu_dma_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
StreamSinkClass *ssc = STREAM_SINK_CLASS(klass);
|
||||
|
||||
dc->reset = xlnx_csu_dma_reset;
|
||||
dc->realize = xlnx_csu_dma_realize;
|
||||
dc->vmsd = &vmstate_xlnx_csu_dma;
|
||||
device_class_set_props(dc, xlnx_csu_dma_properties);
|
||||
|
||||
ssc->push = xlnx_csu_dma_stream_push;
|
||||
ssc->can_push = xlnx_csu_dma_stream_can_push;
|
||||
}
|
||||
|
||||
static void xlnx_csu_dma_init(Object *obj)
|
||||
{
|
||||
XlnxCSUDMA *s = XLNX_CSU_DMA(obj);
|
||||
|
||||
memory_region_init(&s->iomem, obj, TYPE_XLNX_CSU_DMA,
|
||||
XLNX_CSU_DMA_R_MAX * 4);
|
||||
|
||||
object_property_add_link(obj, "stream-connected-dma", TYPE_STREAM_SINK,
|
||||
(Object **)&s->tx_dev,
|
||||
qdev_prop_allow_set_link_before_realize,
|
||||
OBJ_PROP_LINK_STRONG);
|
||||
object_property_add_link(obj, "dma", TYPE_MEMORY_REGION,
|
||||
(Object **)&s->dma_mr,
|
||||
qdev_prop_allow_set_link_before_realize,
|
||||
OBJ_PROP_LINK_STRONG);
|
||||
}
|
||||
|
||||
static const TypeInfo xlnx_csu_dma_info = {
|
||||
.name = TYPE_XLNX_CSU_DMA,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(XlnxCSUDMA),
|
||||
.class_init = xlnx_csu_dma_class_init,
|
||||
.instance_init = xlnx_csu_dma_init,
|
||||
.interfaces = (InterfaceInfo[]) {
|
||||
{ TYPE_STREAM_SINK },
|
||||
{ }
|
||||
}
|
||||
};
|
||||
|
||||
static void xlnx_csu_dma_register_types(void)
|
||||
{
|
||||
type_register_static(&xlnx_csu_dma_info);
|
||||
}
|
||||
|
||||
type_init(xlnx_csu_dma_register_types)
|
|
@ -39,7 +39,7 @@ static void mips_cps_init(Object *obj)
|
|||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
MIPSCPSState *s = MIPS_CPS(obj);
|
||||
|
||||
s->clock = qdev_init_clock_in(DEVICE(obj), "clk-in", NULL, NULL);
|
||||
s->clock = qdev_init_clock_in(DEVICE(obj), "clk-in", NULL, NULL, 0);
|
||||
/*
|
||||
* Cover entire address space as there do not seem to be any
|
||||
* constraints for the base address of CPC and GIC.
|
||||
|
|
|
@ -2,6 +2,15 @@ config APPLESMC
|
|||
bool
|
||||
depends on ISA_BUS
|
||||
|
||||
config ARMSSE_CPUID
|
||||
bool
|
||||
|
||||
config ARMSSE_MHU
|
||||
bool
|
||||
|
||||
config ARMSSE_CPU_PWRCTRL
|
||||
bool
|
||||
|
||||
config MAX111X
|
||||
bool
|
||||
|
||||
|
|
149
hw/misc/armsse-cpu-pwrctrl.c
Normal file
149
hw/misc/armsse-cpu-pwrctrl.c
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Arm SSE CPU PWRCTRL register block
|
||||
*
|
||||
* Copyright (c) 2021 Linaro Limited
|
||||
* Written by Peter Maydell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is a model of the "CPU<N>_PWRCTRL block" which is part of the
|
||||
* Arm Corstone SSE-300 Example Subsystem and documented in
|
||||
* https://developer.arm.com/documentation/101773/0000
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/module.h"
|
||||
#include "trace.h"
|
||||
#include "qapi/error.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "hw/registerfields.h"
|
||||
#include "hw/misc/armsse-cpu-pwrctrl.h"
|
||||
|
||||
REG32(CPUPWRCFG, 0x0)
|
||||
REG32(PID4, 0xfd0)
|
||||
REG32(PID5, 0xfd4)
|
||||
REG32(PID6, 0xfd8)
|
||||
REG32(PID7, 0xfdc)
|
||||
REG32(PID0, 0xfe0)
|
||||
REG32(PID1, 0xfe4)
|
||||
REG32(PID2, 0xfe8)
|
||||
REG32(PID3, 0xfec)
|
||||
REG32(CID0, 0xff0)
|
||||
REG32(CID1, 0xff4)
|
||||
REG32(CID2, 0xff8)
|
||||
REG32(CID3, 0xffc)
|
||||
|
||||
/* PID/CID values */
|
||||
static const int cpu_pwrctrl_id[] = {
|
||||
0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
|
||||
0x5a, 0xb8, 0x0b, 0x00, /* PID0..PID3 */
|
||||
0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
|
||||
};
|
||||
|
||||
static uint64_t pwrctrl_read(void *opaque, hwaddr offset, unsigned size)
|
||||
{
|
||||
ARMSSECPUPwrCtrl *s = ARMSSE_CPU_PWRCTRL(opaque);
|
||||
uint64_t r;
|
||||
|
||||
switch (offset) {
|
||||
case A_CPUPWRCFG:
|
||||
r = s->cpupwrcfg;
|
||||
break;
|
||||
case A_PID4 ... A_CID3:
|
||||
r = cpu_pwrctrl_id[(offset - A_PID4) / 4];
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"SSE CPU_PWRCTRL read: bad offset %x\n", (int)offset);
|
||||
r = 0;
|
||||
break;
|
||||
}
|
||||
trace_armsse_cpu_pwrctrl_read(offset, r, size);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void pwrctrl_write(void *opaque, hwaddr offset,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
ARMSSECPUPwrCtrl *s = ARMSSE_CPU_PWRCTRL(opaque);
|
||||
|
||||
trace_armsse_cpu_pwrctrl_write(offset, value, size);
|
||||
|
||||
switch (offset) {
|
||||
case A_CPUPWRCFG:
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"SSE CPU_PWRCTRL: CPUPWRCFG unimplemented\n");
|
||||
s->cpupwrcfg = value;
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"SSE CPU_PWRCTRL write: bad offset 0x%x\n", (int)offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps pwrctrl_ops = {
|
||||
.read = pwrctrl_read,
|
||||
.write = pwrctrl_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.impl.min_access_size = 4,
|
||||
.impl.max_access_size = 4,
|
||||
.valid.min_access_size = 4,
|
||||
.valid.max_access_size = 4,
|
||||
};
|
||||
|
||||
static void pwrctrl_reset(DeviceState *dev)
|
||||
{
|
||||
ARMSSECPUPwrCtrl *s = ARMSSE_CPU_PWRCTRL(dev);
|
||||
|
||||
s->cpupwrcfg = 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription pwrctrl_vmstate = {
|
||||
.name = "armsse-cpu-pwrctrl",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32(cpupwrcfg, ARMSSECPUPwrCtrl),
|
||||
VMSTATE_END_OF_LIST()
|
||||
},
|
||||
};
|
||||
|
||||
static void pwrctrl_init(Object *obj)
|
||||
{
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
ARMSSECPUPwrCtrl *s = ARMSSE_CPU_PWRCTRL(obj);
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &pwrctrl_ops,
|
||||
s, "armsse-cpu-pwrctrl", 0x1000);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
}
|
||||
|
||||
static void pwrctrl_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->reset = pwrctrl_reset;
|
||||
dc->vmsd = &pwrctrl_vmstate;
|
||||
}
|
||||
|
||||
static const TypeInfo pwrctrl_info = {
|
||||
.name = TYPE_ARMSSE_CPU_PWRCTRL,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(ARMSSECPUPwrCtrl),
|
||||
.instance_init = pwrctrl_init,
|
||||
.class_init = pwrctrl_class_init,
|
||||
};
|
||||
|
||||
static void pwrctrl_register_types(void)
|
||||
{
|
||||
type_register_static(&pwrctrl_info);
|
||||
}
|
||||
|
||||
type_init(pwrctrl_register_types);
|
|
@ -107,7 +107,7 @@ static void pll_update(CprmanPllState *pll)
|
|||
clock_update_hz(pll->out, freq);
|
||||
}
|
||||
|
||||
static void pll_xosc_update(void *opaque)
|
||||
static void pll_xosc_update(void *opaque, ClockEvent event)
|
||||
{
|
||||
pll_update(CPRMAN_PLL(opaque));
|
||||
}
|
||||
|
@ -116,7 +116,8 @@ static void pll_init(Object *obj)
|
|||
{
|
||||
CprmanPllState *s = CPRMAN_PLL(obj);
|
||||
|
||||
s->xosc_in = qdev_init_clock_in(DEVICE(s), "xosc-in", pll_xosc_update, s);
|
||||
s->xosc_in = qdev_init_clock_in(DEVICE(s), "xosc-in", pll_xosc_update,
|
||||
s, ClockUpdate);
|
||||
s->out = qdev_init_clock_out(DEVICE(s), "out");
|
||||
}
|
||||
|
||||
|
@ -209,7 +210,7 @@ static void pll_update_all_channels(BCM2835CprmanState *s,
|
|||
}
|
||||
}
|
||||
|
||||
static void pll_channel_pll_in_update(void *opaque)
|
||||
static void pll_channel_pll_in_update(void *opaque, ClockEvent event)
|
||||
{
|
||||
pll_channel_update(CPRMAN_PLL_CHANNEL(opaque));
|
||||
}
|
||||
|
@ -219,7 +220,8 @@ static void pll_channel_init(Object *obj)
|
|||
CprmanPllChannelState *s = CPRMAN_PLL_CHANNEL(obj);
|
||||
|
||||
s->pll_in = qdev_init_clock_in(DEVICE(s), "pll-in",
|
||||
pll_channel_pll_in_update, s);
|
||||
pll_channel_pll_in_update, s,
|
||||
ClockUpdate);
|
||||
s->out = qdev_init_clock_out(DEVICE(s), "out");
|
||||
}
|
||||
|
||||
|
@ -303,7 +305,7 @@ static void clock_mux_update(CprmanClockMuxState *mux)
|
|||
clock_update_hz(mux->out, freq);
|
||||
}
|
||||
|
||||
static void clock_mux_src_update(void *opaque)
|
||||
static void clock_mux_src_update(void *opaque, ClockEvent event)
|
||||
{
|
||||
CprmanClockMuxState **backref = opaque;
|
||||
CprmanClockMuxState *s = *backref;
|
||||
|
@ -335,7 +337,8 @@ static void clock_mux_init(Object *obj)
|
|||
s->backref[i] = s;
|
||||
s->srcs[i] = qdev_init_clock_in(DEVICE(s), name,
|
||||
clock_mux_src_update,
|
||||
&s->backref[i]);
|
||||
&s->backref[i],
|
||||
ClockUpdate);
|
||||
g_free(name);
|
||||
}
|
||||
|
||||
|
@ -380,7 +383,7 @@ static void dsi0hsck_mux_update(CprmanDsi0HsckMuxState *s)
|
|||
clock_update(s->out, clock_get(src));
|
||||
}
|
||||
|
||||
static void dsi0hsck_mux_in_update(void *opaque)
|
||||
static void dsi0hsck_mux_in_update(void *opaque, ClockEvent event)
|
||||
{
|
||||
dsi0hsck_mux_update(CPRMAN_DSI0HSCK_MUX(opaque));
|
||||
}
|
||||
|
@ -390,8 +393,10 @@ static void dsi0hsck_mux_init(Object *obj)
|
|||
CprmanDsi0HsckMuxState *s = CPRMAN_DSI0HSCK_MUX(obj);
|
||||
DeviceState *dev = DEVICE(obj);
|
||||
|
||||
s->plla_in = qdev_init_clock_in(dev, "plla-in", dsi0hsck_mux_in_update, s);
|
||||
s->plld_in = qdev_init_clock_in(dev, "plld-in", dsi0hsck_mux_in_update, s);
|
||||
s->plla_in = qdev_init_clock_in(dev, "plla-in", dsi0hsck_mux_in_update,
|
||||
s, ClockUpdate);
|
||||
s->plld_in = qdev_init_clock_in(dev, "plld-in", dsi0hsck_mux_in_update,
|
||||
s, ClockUpdate);
|
||||
s->out = qdev_init_clock_out(DEVICE(s), "out");
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#include "hw/registerfields.h"
|
||||
#include "hw/irq.h"
|
||||
#include "hw/misc/iotkit-secctl.h"
|
||||
#include "hw/arm/armsse-version.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
|
||||
/* Registers in the secure privilege control block */
|
||||
REG32(SECRESPCFG, 0x10)
|
||||
|
@ -95,6 +97,19 @@ static const uint8_t iotkit_secctl_ns_idregs[] = {
|
|||
0x0d, 0xf0, 0x05, 0xb1,
|
||||
};
|
||||
|
||||
static const uint8_t iotkit_secctl_s_sse300_idregs[] = {
|
||||
0x04, 0x00, 0x00, 0x00,
|
||||
0x52, 0xb8, 0x2b, 0x00,
|
||||
0x0d, 0xf0, 0x05, 0xb1,
|
||||
};
|
||||
|
||||
static const uint8_t iotkit_secctl_ns_sse300_idregs[] = {
|
||||
0x04, 0x00, 0x00, 0x00,
|
||||
0x53, 0xb8, 0x2b, 0x00,
|
||||
0x0d, 0xf0, 0x05, 0xb1,
|
||||
};
|
||||
|
||||
|
||||
/* The register sets for the various PPCs (AHB internal, APB internal,
|
||||
* AHB expansion, APB expansion) are all set up so that they are
|
||||
* in 16-aligned blocks so offsets 0xN0, 0xN4, 0xN8, 0xNC are PPCs
|
||||
|
@ -213,7 +228,14 @@ static MemTxResult iotkit_secctl_s_read(void *opaque, hwaddr addr,
|
|||
case A_CID1:
|
||||
case A_CID2:
|
||||
case A_CID3:
|
||||
r = iotkit_secctl_s_idregs[(offset - A_PID4) / 4];
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_SSE300:
|
||||
r = iotkit_secctl_s_sse300_idregs[(offset - A_PID4) / 4];
|
||||
break;
|
||||
default:
|
||||
r = iotkit_secctl_s_idregs[(offset - A_PID4) / 4];
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case A_SECPPCINTCLR:
|
||||
case A_SECMSCINTCLR:
|
||||
|
@ -473,7 +495,14 @@ static MemTxResult iotkit_secctl_ns_read(void *opaque, hwaddr addr,
|
|||
case A_CID1:
|
||||
case A_CID2:
|
||||
case A_CID3:
|
||||
r = iotkit_secctl_ns_idregs[(offset - A_PID4) / 4];
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_SSE300:
|
||||
r = iotkit_secctl_ns_sse300_idregs[(offset - A_PID4) / 4];
|
||||
break;
|
||||
default:
|
||||
r = iotkit_secctl_ns_idregs[(offset - A_PID4) / 4];
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
|
@ -710,6 +739,16 @@ static void iotkit_secctl_init(Object *obj)
|
|||
sysbus_init_mmio(sbd, &s->ns_regs);
|
||||
}
|
||||
|
||||
static void iotkit_secctl_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
IoTKitSecCtl *s = IOTKIT_SECCTL(dev);
|
||||
|
||||
if (!armsse_version_valid(s->sse_version)) {
|
||||
error_setg(errp, "invalid sse-version value %d", s->sse_version);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static const VMStateDescription iotkit_secctl_ppc_vmstate = {
|
||||
.name = "iotkit-secctl-ppc",
|
||||
.version_id = 1,
|
||||
|
@ -775,12 +814,19 @@ static const VMStateDescription iotkit_secctl_vmstate = {
|
|||
},
|
||||
};
|
||||
|
||||
static Property iotkit_secctl_props[] = {
|
||||
DEFINE_PROP_UINT32("sse-version", IoTKitSecCtl, sse_version, 0),
|
||||
DEFINE_PROP_END_OF_LIST()
|
||||
};
|
||||
|
||||
static void iotkit_secctl_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->vmsd = &iotkit_secctl_vmstate;
|
||||
dc->reset = iotkit_secctl_reset;
|
||||
dc->realize = iotkit_secctl_realize;
|
||||
device_class_set_props(dc, iotkit_secctl_props);
|
||||
}
|
||||
|
||||
static const TypeInfo iotkit_secctl_info = {
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "hw/registerfields.h"
|
||||
#include "hw/misc/iotkit-sysctl.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "hw/arm/armsse-version.h"
|
||||
#include "target/arm/arm-powerctl.h"
|
||||
#include "target/arm/cpu.h"
|
||||
|
||||
|
@ -44,16 +45,22 @@ REG32(SWRESET, 0x108)
|
|||
FIELD(SWRESET, SWRESETREQ, 9, 1)
|
||||
REG32(GRETREG, 0x10c)
|
||||
REG32(INITSVTOR0, 0x110)
|
||||
FIELD(INITSVTOR0, LOCK, 0, 1)
|
||||
FIELD(INITSVTOR0, VTOR, 7, 25)
|
||||
REG32(INITSVTOR1, 0x114)
|
||||
REG32(CPUWAIT, 0x118)
|
||||
REG32(NMI_ENABLE, 0x11c) /* BUSWAIT in IoTKit */
|
||||
REG32(WICCTRL, 0x120)
|
||||
REG32(EWCTRL, 0x124)
|
||||
REG32(PWRCTRL, 0x1fc)
|
||||
FIELD(PWRCTRL, PPU_ACCESS_UNLOCK, 0, 1)
|
||||
FIELD(PWRCTRL, PPU_ACCESS_FILTER, 1, 1)
|
||||
REG32(PDCM_PD_SYS_SENSE, 0x200)
|
||||
REG32(PDCM_PD_CPU0_SENSE, 0x204)
|
||||
REG32(PDCM_PD_SRAM0_SENSE, 0x20c)
|
||||
REG32(PDCM_PD_SRAM1_SENSE, 0x210)
|
||||
REG32(PDCM_PD_SRAM2_SENSE, 0x214)
|
||||
REG32(PDCM_PD_SRAM3_SENSE, 0x218)
|
||||
REG32(PDCM_PD_SRAM2_SENSE, 0x214) /* PDCM_PD_VMR0_SENSE on SSE300 */
|
||||
REG32(PDCM_PD_SRAM3_SENSE, 0x218) /* PDCM_PD_VMR1_SENSE on SSE300 */
|
||||
REG32(PID4, 0xfd0)
|
||||
REG32(PID5, 0xfd4)
|
||||
REG32(PID6, 0xfd8)
|
||||
|
@ -68,12 +75,19 @@ REG32(CID2, 0xff8)
|
|||
REG32(CID3, 0xffc)
|
||||
|
||||
/* PID/CID values */
|
||||
static const int sysctl_id[] = {
|
||||
static const int iotkit_sysctl_id[] = {
|
||||
0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
|
||||
0x54, 0xb8, 0x0b, 0x00, /* PID0..PID3 */
|
||||
0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
|
||||
};
|
||||
|
||||
/* Also used by the SSE300 */
|
||||
static const int sse200_sysctl_id[] = {
|
||||
0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
|
||||
0x54, 0xb8, 0x1b, 0x00, /* PID0..PID3 */
|
||||
0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
|
||||
};
|
||||
|
||||
/*
|
||||
* Set the initial secure vector table offset address for the core.
|
||||
* This will take effect when the CPU next resets.
|
||||
|
@ -100,28 +114,52 @@ static uint64_t iotkit_sysctl_read(void *opaque, hwaddr offset,
|
|||
r = s->secure_debug;
|
||||
break;
|
||||
case A_SCSECCTRL:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
case ARMSSE_SSE300:
|
||||
r = s->scsecctrl;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
r = s->scsecctrl;
|
||||
break;
|
||||
case A_FCLK_DIV:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
case ARMSSE_SSE300:
|
||||
r = s->fclk_div;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
r = s->fclk_div;
|
||||
break;
|
||||
case A_SYSCLK_DIV:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
case ARMSSE_SSE300:
|
||||
r = s->sysclk_div;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
r = s->sysclk_div;
|
||||
break;
|
||||
case A_CLOCK_FORCE:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
case ARMSSE_SSE300:
|
||||
r = s->clock_force;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
r = s->clock_force;
|
||||
break;
|
||||
case A_RESET_SYNDROME:
|
||||
r = s->reset_syndrome;
|
||||
|
@ -136,63 +174,178 @@ static uint64_t iotkit_sysctl_read(void *opaque, hwaddr offset,
|
|||
r = s->initsvtor0;
|
||||
break;
|
||||
case A_INITSVTOR1:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
r = s->initsvtor1;
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
goto bad_offset;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
r = s->initsvtor1;
|
||||
break;
|
||||
case A_CPUWAIT:
|
||||
r = s->cpuwait;
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
case ARMSSE_SSE200:
|
||||
r = s->cpuwait;
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
/* In SSE300 this is reserved (for INITSVTOR2) */
|
||||
goto bad_offset;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
break;
|
||||
case A_NMI_ENABLE:
|
||||
/* In IoTKit this is named BUSWAIT but is marked reserved, R/O, zero */
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
/* In IoTKit this is named BUSWAIT but marked reserved, R/O, zero */
|
||||
r = 0;
|
||||
break;
|
||||
case ARMSSE_SSE200:
|
||||
r = s->nmi_enable;
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
/* In SSE300 this is reserved (for INITSVTOR3) */
|
||||
goto bad_offset;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
r = s->nmi_enable;
|
||||
break;
|
||||
case A_WICCTRL:
|
||||
r = s->wicctrl;
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
case ARMSSE_SSE200:
|
||||
r = s->wicctrl;
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
/* In SSE300 this offset is CPUWAIT */
|
||||
r = s->cpuwait;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
break;
|
||||
case A_EWCTRL:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
r = s->ewctrl;
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
/* In SSE300 this offset is is NMI_ENABLE */
|
||||
r = s->nmi_enable;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
break;
|
||||
case A_PWRCTRL:
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
case ARMSSE_SSE200:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE300:
|
||||
r = s->pwrctrl;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
r = s->ewctrl;
|
||||
break;
|
||||
case A_PDCM_PD_SYS_SENSE:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
case ARMSSE_SSE300:
|
||||
r = s->pdcm_pd_sys_sense;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
break;
|
||||
case A_PDCM_PD_CPU0_SENSE:
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
case ARMSSE_SSE200:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE300:
|
||||
r = s->pdcm_pd_cpu0_sense;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
r = s->pdcm_pd_sys_sense;
|
||||
break;
|
||||
case A_PDCM_PD_SRAM0_SENSE:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
r = s->pdcm_pd_sram0_sense;
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
goto bad_offset;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
r = s->pdcm_pd_sram0_sense;
|
||||
break;
|
||||
case A_PDCM_PD_SRAM1_SENSE:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
r = s->pdcm_pd_sram1_sense;
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
goto bad_offset;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
r = s->pdcm_pd_sram1_sense;
|
||||
break;
|
||||
case A_PDCM_PD_SRAM2_SENSE:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
r = s->pdcm_pd_sram2_sense;
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
r = s->pdcm_pd_vmr0_sense;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
r = s->pdcm_pd_sram2_sense;
|
||||
break;
|
||||
case A_PDCM_PD_SRAM3_SENSE:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
r = s->pdcm_pd_sram3_sense;
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
r = s->pdcm_pd_vmr1_sense;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
r = s->pdcm_pd_sram3_sense;
|
||||
break;
|
||||
case A_PID4 ... A_CID3:
|
||||
r = sysctl_id[(offset - A_PID4) / 4];
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
r = iotkit_sysctl_id[(offset - A_PID4) / 4];
|
||||
break;
|
||||
case ARMSSE_SSE200:
|
||||
case ARMSSE_SSE300:
|
||||
r = sse200_sysctl_id[(offset - A_PID4) / 4];
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
break;
|
||||
case A_SECDBGSET:
|
||||
case A_SECDBGCLR:
|
||||
|
@ -213,6 +366,21 @@ static uint64_t iotkit_sysctl_read(void *opaque, hwaddr offset,
|
|||
return r;
|
||||
}
|
||||
|
||||
static void cpuwait_write(IoTKitSysCtl *s, uint32_t value)
|
||||
{
|
||||
int num_cpus = (s->sse_version == ARMSSE_SSE300) ? 1 : 2;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_cpus; i++) {
|
||||
uint32_t mask = 1 << i;
|
||||
if ((s->cpuwait & mask) && !(value & mask)) {
|
||||
/* Powering up CPU 0 */
|
||||
arm_set_cpu_on_and_reset(i);
|
||||
}
|
||||
}
|
||||
s->cpuwait = value;
|
||||
}
|
||||
|
||||
static void iotkit_sysctl_write(void *opaque, hwaddr offset,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
|
@ -249,23 +417,53 @@ static void iotkit_sysctl_write(void *opaque, hwaddr offset,
|
|||
s->gretreg = value;
|
||||
break;
|
||||
case A_INITSVTOR0:
|
||||
s->initsvtor0 = value;
|
||||
set_init_vtor(0, s->initsvtor0);
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_SSE300:
|
||||
/* SSE300 has a LOCK bit which prevents further writes when set */
|
||||
if (s->initsvtor0 & R_INITSVTOR0_LOCK_MASK) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"IoTKit INITSVTOR0 write when register locked\n");
|
||||
break;
|
||||
}
|
||||
s->initsvtor0 = value;
|
||||
set_init_vtor(0, s->initsvtor0 & R_INITSVTOR0_VTOR_MASK);
|
||||
break;
|
||||
case ARMSSE_IOTKIT:
|
||||
case ARMSSE_SSE200:
|
||||
s->initsvtor0 = value;
|
||||
set_init_vtor(0, s->initsvtor0);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
break;
|
||||
case A_CPUWAIT:
|
||||
if ((s->cpuwait & 1) && !(value & 1)) {
|
||||
/* Powering up CPU 0 */
|
||||
arm_set_cpu_on_and_reset(0);
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
case ARMSSE_SSE200:
|
||||
cpuwait_write(s, value);
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
/* In SSE300 this is reserved (for INITSVTOR2) */
|
||||
goto bad_offset;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
if ((s->cpuwait & 2) && !(value & 2)) {
|
||||
/* Powering up CPU 1 */
|
||||
arm_set_cpu_on_and_reset(1);
|
||||
}
|
||||
s->cpuwait = value;
|
||||
break;
|
||||
case A_WICCTRL:
|
||||
qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl WICCTRL unimplemented\n");
|
||||
s->wicctrl = value;
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
case ARMSSE_SSE200:
|
||||
qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl WICCTRL unimplemented\n");
|
||||
s->wicctrl = value;
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
/* In SSE300 this offset is CPUWAIT */
|
||||
cpuwait_write(s, value);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
break;
|
||||
case A_SECDBGSET:
|
||||
/* write-1-to-set */
|
||||
|
@ -283,94 +481,214 @@ static void iotkit_sysctl_write(void *opaque, hwaddr offset,
|
|||
}
|
||||
break;
|
||||
case A_SCSECCTRL:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
case ARMSSE_SSE300:
|
||||
qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl SCSECCTRL unimplemented\n");
|
||||
s->scsecctrl = value;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl SCSECCTRL unimplemented\n");
|
||||
s->scsecctrl = value;
|
||||
break;
|
||||
case A_FCLK_DIV:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
case ARMSSE_SSE300:
|
||||
qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl FCLK_DIV unimplemented\n");
|
||||
s->fclk_div = value;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl FCLK_DIV unimplemented\n");
|
||||
s->fclk_div = value;
|
||||
break;
|
||||
case A_SYSCLK_DIV:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
case ARMSSE_SSE300:
|
||||
qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl SYSCLK_DIV unimplemented\n");
|
||||
s->sysclk_div = value;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl SYSCLK_DIV unimplemented\n");
|
||||
s->sysclk_div = value;
|
||||
break;
|
||||
case A_CLOCK_FORCE:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
case ARMSSE_SSE300:
|
||||
qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl CLOCK_FORCE unimplemented\n");
|
||||
s->clock_force = value;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl CLOCK_FORCE unimplemented\n");
|
||||
s->clock_force = value;
|
||||
break;
|
||||
case A_INITSVTOR1:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
s->initsvtor1 = value;
|
||||
set_init_vtor(1, s->initsvtor1);
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
goto bad_offset;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
s->initsvtor1 = value;
|
||||
set_init_vtor(1, s->initsvtor1);
|
||||
break;
|
||||
case A_EWCTRL:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl EWCTRL unimplemented\n");
|
||||
s->ewctrl = value;
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
/* In SSE300 this offset is is NMI_ENABLE */
|
||||
qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl NMI_ENABLE unimplemented\n");
|
||||
s->nmi_enable = value;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
break;
|
||||
case A_PWRCTRL:
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
case ARMSSE_SSE200:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE300:
|
||||
if (!(s->pwrctrl & R_PWRCTRL_PPU_ACCESS_UNLOCK_MASK)) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"IoTKit PWRCTRL write when register locked\n");
|
||||
break;
|
||||
}
|
||||
s->pwrctrl = value;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl EWCTRL unimplemented\n");
|
||||
s->ewctrl = value;
|
||||
break;
|
||||
case A_PDCM_PD_SYS_SENSE:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
case ARMSSE_SSE300:
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"IoTKit SysCtl PDCM_PD_SYS_SENSE unimplemented\n");
|
||||
s->pdcm_pd_sys_sense = value;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
break;
|
||||
case A_PDCM_PD_CPU0_SENSE:
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
case ARMSSE_SSE200:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE300:
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"IoTKit SysCtl PDCM_PD_CPU0_SENSE unimplemented\n");
|
||||
s->pdcm_pd_cpu0_sense = value;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"IoTKit SysCtl PDCM_PD_SYS_SENSE unimplemented\n");
|
||||
s->pdcm_pd_sys_sense = value;
|
||||
break;
|
||||
case A_PDCM_PD_SRAM0_SENSE:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"IoTKit SysCtl PDCM_PD_SRAM0_SENSE unimplemented\n");
|
||||
s->pdcm_pd_sram0_sense = value;
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
goto bad_offset;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"IoTKit SysCtl PDCM_PD_SRAM0_SENSE unimplemented\n");
|
||||
s->pdcm_pd_sram0_sense = value;
|
||||
break;
|
||||
case A_PDCM_PD_SRAM1_SENSE:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"IoTKit SysCtl PDCM_PD_SRAM1_SENSE unimplemented\n");
|
||||
s->pdcm_pd_sram1_sense = value;
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
goto bad_offset;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"IoTKit SysCtl PDCM_PD_SRAM1_SENSE unimplemented\n");
|
||||
s->pdcm_pd_sram1_sense = value;
|
||||
break;
|
||||
case A_PDCM_PD_SRAM2_SENSE:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"IoTKit SysCtl PDCM_PD_SRAM2_SENSE unimplemented\n");
|
||||
s->pdcm_pd_sram2_sense = value;
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"IoTKit SysCtl PDCM_PD_VMR0_SENSE unimplemented\n");
|
||||
s->pdcm_pd_vmr0_sense = value;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"IoTKit SysCtl PDCM_PD_SRAM2_SENSE unimplemented\n");
|
||||
s->pdcm_pd_sram2_sense = value;
|
||||
break;
|
||||
case A_PDCM_PD_SRAM3_SENSE:
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto bad_offset;
|
||||
case ARMSSE_SSE200:
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"IoTKit SysCtl PDCM_PD_SRAM3_SENSE unimplemented\n");
|
||||
s->pdcm_pd_sram3_sense = value;
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"IoTKit SysCtl PDCM_PD_VMR1_SENSE unimplemented\n");
|
||||
s->pdcm_pd_vmr1_sense = value;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"IoTKit SysCtl PDCM_PD_SRAM3_SENSE unimplemented\n");
|
||||
s->pdcm_pd_sram3_sense = value;
|
||||
break;
|
||||
case A_NMI_ENABLE:
|
||||
/* In IoTKit this is BUSWAIT: reserved, R/O, zero */
|
||||
if (!s->is_sse200) {
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
goto ro_offset;
|
||||
case ARMSSE_SSE200:
|
||||
qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl NMI_ENABLE unimplemented\n");
|
||||
s->nmi_enable = value;
|
||||
break;
|
||||
case ARMSSE_SSE300:
|
||||
/* In SSE300 this is reserved (for INITSVTOR3) */
|
||||
goto bad_offset;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
qemu_log_mask(LOG_UNIMP, "IoTKit SysCtl NMI_ENABLE unimplemented\n");
|
||||
s->nmi_enable = value;
|
||||
break;
|
||||
case A_SECDBGSTAT:
|
||||
case A_PID4 ... A_CID3:
|
||||
|
@ -417,11 +735,15 @@ static void iotkit_sysctl_reset(DeviceState *dev)
|
|||
s->clock_force = 0;
|
||||
s->nmi_enable = 0;
|
||||
s->ewctrl = 0;
|
||||
s->pwrctrl = 0x3;
|
||||
s->pdcm_pd_sys_sense = 0x7f;
|
||||
s->pdcm_pd_sram0_sense = 0;
|
||||
s->pdcm_pd_sram1_sense = 0;
|
||||
s->pdcm_pd_sram2_sense = 0;
|
||||
s->pdcm_pd_sram3_sense = 0;
|
||||
s->pdcm_pd_cpu0_sense = 0;
|
||||
s->pdcm_pd_vmr0_sense = 0;
|
||||
s->pdcm_pd_vmr1_sense = 0;
|
||||
}
|
||||
|
||||
static void iotkit_sysctl_init(Object *obj)
|
||||
|
@ -438,17 +760,38 @@ static void iotkit_sysctl_realize(DeviceState *dev, Error **errp)
|
|||
{
|
||||
IoTKitSysCtl *s = IOTKIT_SYSCTL(dev);
|
||||
|
||||
/* The top 4 bits of the SYS_VERSION register tell us if we're an SSE-200 */
|
||||
if (extract32(s->sys_version, 28, 4) == 2) {
|
||||
s->is_sse200 = true;
|
||||
if (!armsse_version_valid(s->sse_version)) {
|
||||
error_setg(errp, "invalid sse-version value %d", s->sse_version);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static bool sse300_needed(void *opaque)
|
||||
{
|
||||
IoTKitSysCtl *s = IOTKIT_SYSCTL(opaque);
|
||||
|
||||
return s->sse_version == ARMSSE_SSE300;
|
||||
}
|
||||
|
||||
static const VMStateDescription iotkit_sysctl_sse300_vmstate = {
|
||||
.name = "iotkit-sysctl/sse-300",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.needed = sse300_needed,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32(pwrctrl, IoTKitSysCtl),
|
||||
VMSTATE_UINT32(pdcm_pd_cpu0_sense, IoTKitSysCtl),
|
||||
VMSTATE_UINT32(pdcm_pd_vmr0_sense, IoTKitSysCtl),
|
||||
VMSTATE_UINT32(pdcm_pd_vmr1_sense, IoTKitSysCtl),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static bool sse200_needed(void *opaque)
|
||||
{
|
||||
IoTKitSysCtl *s = IOTKIT_SYSCTL(opaque);
|
||||
|
||||
return s->is_sse200;
|
||||
return s->sse_version != ARMSSE_IOTKIT;
|
||||
}
|
||||
|
||||
static const VMStateDescription iotkit_sysctl_sse200_vmstate = {
|
||||
|
@ -488,12 +831,13 @@ static const VMStateDescription iotkit_sysctl_vmstate = {
|
|||
},
|
||||
.subsections = (const VMStateDescription*[]) {
|
||||
&iotkit_sysctl_sse200_vmstate,
|
||||
&iotkit_sysctl_sse300_vmstate,
|
||||
NULL
|
||||
}
|
||||
};
|
||||
|
||||
static Property iotkit_sysctl_props[] = {
|
||||
DEFINE_PROP_UINT32("SYS_VERSION", IoTKitSysCtl, sys_version, 0),
|
||||
DEFINE_PROP_UINT32("sse-version", IoTKitSysCtl, sse_version, 0),
|
||||
DEFINE_PROP_UINT32("CPUWAIT_RST", IoTKitSysCtl, cpuwait_rst, 0),
|
||||
DEFINE_PROP_UINT32("INITSVTOR0_RST", IoTKitSysCtl, initsvtor0_rst,
|
||||
0x10000000),
|
||||
|
|
|
@ -26,9 +26,12 @@
|
|||
#include "hw/registerfields.h"
|
||||
#include "hw/misc/iotkit-sysinfo.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "hw/arm/armsse-version.h"
|
||||
|
||||
REG32(SYS_VERSION, 0x0)
|
||||
REG32(SYS_CONFIG, 0x4)
|
||||
REG32(SYS_CONFIG1, 0x8)
|
||||
REG32(IIDR, 0xfc8)
|
||||
REG32(PID4, 0xfd0)
|
||||
REG32(PID5, 0xfd4)
|
||||
REG32(PID6, 0xfd8)
|
||||
|
@ -49,6 +52,12 @@ static const int sysinfo_id[] = {
|
|||
0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
|
||||
};
|
||||
|
||||
static const int sysinfo_sse300_id[] = {
|
||||
0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
|
||||
0x58, 0xb8, 0x1b, 0x00, /* PID0..PID3 */
|
||||
0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
|
||||
};
|
||||
|
||||
static uint64_t iotkit_sysinfo_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
|
@ -63,10 +72,36 @@ static uint64_t iotkit_sysinfo_read(void *opaque, hwaddr offset,
|
|||
case A_SYS_CONFIG:
|
||||
r = s->sys_config;
|
||||
break;
|
||||
case A_SYS_CONFIG1:
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_SSE300:
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
goto bad_read;
|
||||
}
|
||||
break;
|
||||
case A_IIDR:
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_SSE300:
|
||||
return s->iidr;
|
||||
break;
|
||||
default:
|
||||
goto bad_read;
|
||||
}
|
||||
break;
|
||||
case A_PID4 ... A_CID3:
|
||||
r = sysinfo_id[(offset - A_PID4) / 4];
|
||||
switch (s->sse_version) {
|
||||
case ARMSSE_SSE300:
|
||||
r = sysinfo_sse300_id[(offset - A_PID4) / 4];
|
||||
break;
|
||||
default:
|
||||
r = sysinfo_id[(offset - A_PID4) / 4];
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
bad_read:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"IoTKit SysInfo read: bad offset %x\n", (int)offset);
|
||||
r = 0;
|
||||
|
@ -99,6 +134,8 @@ static const MemoryRegionOps iotkit_sysinfo_ops = {
|
|||
static Property iotkit_sysinfo_props[] = {
|
||||
DEFINE_PROP_UINT32("SYS_VERSION", IoTKitSysInfo, sys_version, 0),
|
||||
DEFINE_PROP_UINT32("SYS_CONFIG", IoTKitSysInfo, sys_config, 0),
|
||||
DEFINE_PROP_UINT32("sse-version", IoTKitSysInfo, sse_version, 0),
|
||||
DEFINE_PROP_UINT32("IIDR", IoTKitSysInfo, iidr, 0),
|
||||
DEFINE_PROP_END_OF_LIST()
|
||||
};
|
||||
|
||||
|
@ -112,6 +149,16 @@ static void iotkit_sysinfo_init(Object *obj)
|
|||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
}
|
||||
|
||||
static void iotkit_sysinfo_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
IoTKitSysInfo *s = IOTKIT_SYSINFO(dev);
|
||||
|
||||
if (!armsse_version_valid(s->sse_version)) {
|
||||
error_setg(errp, "invalid sse-version value %d", s->sse_version);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void iotkit_sysinfo_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
@ -120,7 +167,7 @@ static void iotkit_sysinfo_class_init(ObjectClass *klass, void *data)
|
|||
* This device has no guest-modifiable state and so it
|
||||
* does not need a reset function or VMState.
|
||||
*/
|
||||
|
||||
dc->realize = iotkit_sysinfo_realize;
|
||||
device_class_set_props(dc, iotkit_sysinfo_props);
|
||||
}
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@ softmmu_ss.add(when: 'CONFIG_TZ_MSC', if_true: files('tz-msc.c'))
|
|||
softmmu_ss.add(when: 'CONFIG_TZ_PPC', if_true: files('tz-ppc.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_IOTKIT_SECCTL', if_true: files('iotkit-secctl.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_IOTKIT_SYSINFO', if_true: files('iotkit-sysinfo.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_ARMSSE_CPU_PWRCTRL', if_true: files('armsse-cpu-pwrctrl.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_ARMSSE_CPUID', if_true: files('armsse-cpuid.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_ARMSSE_MHU', if_true: files('armsse-mhu.c'))
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "qemu/timer.h"
|
||||
|
||||
REG32(LED0, 0)
|
||||
REG32(DBGCTRL, 4)
|
||||
REG32(BUTTON, 8)
|
||||
REG32(CLK1HZ, 0x10)
|
||||
REG32(CLK100HZ, 0x14)
|
||||
|
@ -129,6 +130,12 @@ static uint64_t mps2_fpgaio_read(void *opaque, hwaddr offset, unsigned size)
|
|||
case A_LED0:
|
||||
r = s->led0;
|
||||
break;
|
||||
case A_DBGCTRL:
|
||||
if (!s->has_dbgctrl) {
|
||||
goto bad_offset;
|
||||
}
|
||||
r = s->dbgctrl;
|
||||
break;
|
||||
case A_BUTTON:
|
||||
/* User-pressable board buttons. We don't model that, so just return
|
||||
* zeroes.
|
||||
|
@ -195,6 +202,14 @@ static void mps2_fpgaio_write(void *opaque, hwaddr offset, uint64_t value,
|
|||
}
|
||||
}
|
||||
break;
|
||||
case A_DBGCTRL:
|
||||
if (!s->has_dbgctrl) {
|
||||
goto bad_offset;
|
||||
}
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"MPS2 FPGAIO: DBGCTRL unimplemented\n");
|
||||
s->dbgctrl = value;
|
||||
break;
|
||||
case A_PRESCALE:
|
||||
resync_counter(s);
|
||||
s->prescale = value;
|
||||
|
@ -225,6 +240,7 @@ static void mps2_fpgaio_write(void *opaque, hwaddr offset, uint64_t value,
|
|||
s->pscntr = value;
|
||||
break;
|
||||
default:
|
||||
bad_offset:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"MPS2 FPGAIO write: bad offset 0x%x\n", (int) offset);
|
||||
break;
|
||||
|
@ -285,41 +301,22 @@ static void mps2_fpgaio_realize(DeviceState *dev, Error **errp)
|
|||
}
|
||||
}
|
||||
|
||||
static bool mps2_fpgaio_counters_needed(void *opaque)
|
||||
{
|
||||
/* Currently vmstate.c insists all subsections have a 'needed' function */
|
||||
return true;
|
||||
}
|
||||
|
||||
static const VMStateDescription mps2_fpgaio_counters_vmstate = {
|
||||
.name = "mps2-fpgaio/counters",
|
||||
.version_id = 2,
|
||||
.minimum_version_id = 2,
|
||||
.needed = mps2_fpgaio_counters_needed,
|
||||
static const VMStateDescription mps2_fpgaio_vmstate = {
|
||||
.name = "mps2-fpgaio",
|
||||
.version_id = 3,
|
||||
.minimum_version_id = 3,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32(led0, MPS2FPGAIO),
|
||||
VMSTATE_UINT32(prescale, MPS2FPGAIO),
|
||||
VMSTATE_UINT32(misc, MPS2FPGAIO),
|
||||
VMSTATE_UINT32(dbgctrl, MPS2FPGAIO),
|
||||
VMSTATE_INT64(clk1hz_tick_offset, MPS2FPGAIO),
|
||||
VMSTATE_INT64(clk100hz_tick_offset, MPS2FPGAIO),
|
||||
VMSTATE_UINT32(counter, MPS2FPGAIO),
|
||||
VMSTATE_UINT32(pscntr, MPS2FPGAIO),
|
||||
VMSTATE_INT64(pscntr_sync_ticks, MPS2FPGAIO),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const VMStateDescription mps2_fpgaio_vmstate = {
|
||||
.name = "mps2-fpgaio",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32(led0, MPS2FPGAIO),
|
||||
VMSTATE_UINT32(prescale, MPS2FPGAIO),
|
||||
VMSTATE_UINT32(misc, MPS2FPGAIO),
|
||||
VMSTATE_END_OF_LIST()
|
||||
},
|
||||
.subsections = (const VMStateDescription*[]) {
|
||||
&mps2_fpgaio_counters_vmstate,
|
||||
NULL
|
||||
}
|
||||
};
|
||||
|
||||
static Property mps2_fpgaio_properties[] = {
|
||||
|
@ -328,6 +325,7 @@ static Property mps2_fpgaio_properties[] = {
|
|||
/* Number of LEDs controlled by LED0 register */
|
||||
DEFINE_PROP_UINT32("num-leds", MPS2FPGAIO, num_leds, 2),
|
||||
DEFINE_PROP_BOOL("has-switches", MPS2FPGAIO, has_switches, false),
|
||||
DEFINE_PROP_BOOL("has-dbgctrl", MPS2FPGAIO, has_dbgctrl, false),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
|
|
|
@ -110,14 +110,14 @@ static uint64_t mps2_scc_read(void *opaque, hwaddr offset, unsigned size)
|
|||
r = s->cfg1;
|
||||
break;
|
||||
case A_CFG2:
|
||||
if (scc_partno(s) != 0x524) {
|
||||
if (scc_partno(s) != 0x524 && scc_partno(s) != 0x547) {
|
||||
/* CFG2 reserved on other boards */
|
||||
goto bad_offset;
|
||||
}
|
||||
r = s->cfg2;
|
||||
break;
|
||||
case A_CFG3:
|
||||
if (scc_partno(s) == 0x524) {
|
||||
if (scc_partno(s) == 0x524 && scc_partno(s) == 0x547) {
|
||||
/* CFG3 reserved on AN524 */
|
||||
goto bad_offset;
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ static uint64_t mps2_scc_read(void *opaque, hwaddr offset, unsigned size)
|
|||
r = s->cfg4;
|
||||
break;
|
||||
case A_CFG5:
|
||||
if (scc_partno(s) != 0x524) {
|
||||
if (scc_partno(s) != 0x524 && scc_partno(s) != 0x547) {
|
||||
/* CFG5 reserved on other boards */
|
||||
goto bad_offset;
|
||||
}
|
||||
|
@ -185,7 +185,10 @@ static void mps2_scc_write(void *opaque, hwaddr offset, uint64_t value,
|
|||
|
||||
switch (offset) {
|
||||
case A_CFG0:
|
||||
/* TODO on some boards bit 0 controls RAM remapping */
|
||||
/*
|
||||
* TODO on some boards bit 0 controls RAM remapping;
|
||||
* on others bit 1 is CPU_WAIT.
|
||||
*/
|
||||
s->cfg0 = value;
|
||||
break;
|
||||
case A_CFG1:
|
||||
|
@ -195,7 +198,7 @@ static void mps2_scc_write(void *opaque, hwaddr offset, uint64_t value,
|
|||
}
|
||||
break;
|
||||
case A_CFG2:
|
||||
if (scc_partno(s) != 0x524) {
|
||||
if (scc_partno(s) != 0x524 && scc_partno(s) != 0x547) {
|
||||
/* CFG2 reserved on other boards */
|
||||
goto bad_offset;
|
||||
}
|
||||
|
@ -203,7 +206,7 @@ static void mps2_scc_write(void *opaque, hwaddr offset, uint64_t value,
|
|||
s->cfg2 = value;
|
||||
break;
|
||||
case A_CFG5:
|
||||
if (scc_partno(s) != 0x524) {
|
||||
if (scc_partno(s) != 0x524 && scc_partno(s) != 0x547) {
|
||||
/* CFG5 reserved on other boards */
|
||||
goto bad_offset;
|
||||
}
|
||||
|
|
|
@ -586,15 +586,26 @@ static const DividerInitInfo divider_init_info_list[] = {
|
|||
},
|
||||
};
|
||||
|
||||
static void npcm7xx_clk_update_pll_cb(void *opaque, ClockEvent event)
|
||||
{
|
||||
npcm7xx_clk_update_pll(opaque);
|
||||
}
|
||||
|
||||
static void npcm7xx_clk_pll_init(Object *obj)
|
||||
{
|
||||
NPCM7xxClockPLLState *pll = NPCM7XX_CLOCK_PLL(obj);
|
||||
|
||||
pll->clock_in = qdev_init_clock_in(DEVICE(pll), "clock-in",
|
||||
npcm7xx_clk_update_pll, pll);
|
||||
npcm7xx_clk_update_pll_cb, pll,
|
||||
ClockUpdate);
|
||||
pll->clock_out = qdev_init_clock_out(DEVICE(pll), "clock-out");
|
||||
}
|
||||
|
||||
static void npcm7xx_clk_update_sel_cb(void *opaque, ClockEvent event)
|
||||
{
|
||||
npcm7xx_clk_update_sel(opaque);
|
||||
}
|
||||
|
||||
static void npcm7xx_clk_sel_init(Object *obj)
|
||||
{
|
||||
int i;
|
||||
|
@ -603,16 +614,23 @@ static void npcm7xx_clk_sel_init(Object *obj)
|
|||
for (i = 0; i < NPCM7XX_CLK_SEL_MAX_INPUT; ++i) {
|
||||
sel->clock_in[i] = qdev_init_clock_in(DEVICE(sel),
|
||||
g_strdup_printf("clock-in[%d]", i),
|
||||
npcm7xx_clk_update_sel, sel);
|
||||
npcm7xx_clk_update_sel_cb, sel, ClockUpdate);
|
||||
}
|
||||
sel->clock_out = qdev_init_clock_out(DEVICE(sel), "clock-out");
|
||||
}
|
||||
|
||||
static void npcm7xx_clk_update_divider_cb(void *opaque, ClockEvent event)
|
||||
{
|
||||
npcm7xx_clk_update_divider(opaque);
|
||||
}
|
||||
|
||||
static void npcm7xx_clk_divider_init(Object *obj)
|
||||
{
|
||||
NPCM7xxClockDividerState *div = NPCM7XX_CLOCK_DIVIDER(obj);
|
||||
|
||||
div->clock_in = qdev_init_clock_in(DEVICE(div), "clock-in",
|
||||
npcm7xx_clk_update_divider, div);
|
||||
npcm7xx_clk_update_divider_cb,
|
||||
div, ClockUpdate);
|
||||
div->clock_out = qdev_init_clock_out(DEVICE(div), "clock-out");
|
||||
}
|
||||
|
||||
|
@ -875,7 +893,7 @@ static void npcm7xx_clk_init_clock_hierarchy(NPCM7xxCLKState *s)
|
|||
{
|
||||
int i;
|
||||
|
||||
s->clkref = qdev_init_clock_in(DEVICE(s), "clkref", NULL, NULL);
|
||||
s->clkref = qdev_init_clock_in(DEVICE(s), "clkref", NULL, NULL, 0);
|
||||
|
||||
/* First pass: init all converter modules */
|
||||
QEMU_BUILD_BUG_ON(ARRAY_SIZE(pll_init_info_list) != NPCM7XX_CLOCK_NR_PLLS);
|
||||
|
|
|
@ -493,7 +493,7 @@ static void npcm7xx_pwm_init(Object *obj)
|
|||
memory_region_init_io(&s->iomem, obj, &npcm7xx_pwm_ops, s,
|
||||
TYPE_NPCM7XX_PWM, 4 * KiB);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
s->clock = qdev_init_clock_in(DEVICE(s), "clock", NULL, NULL);
|
||||
s->clock = qdev_init_clock_in(DEVICE(s), "clock", NULL, NULL, 0);
|
||||
|
||||
for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) {
|
||||
object_property_add_uint32_ptr(obj, "freq[*]",
|
||||
|
|
|
@ -186,6 +186,10 @@ iotkit_sysctl_read(uint64_t offset, uint64_t data, unsigned size) "IoTKit SysCtl
|
|||
iotkit_sysctl_write(uint64_t offset, uint64_t data, unsigned size) "IoTKit SysCtl write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
|
||||
iotkit_sysctl_reset(void) "IoTKit SysCtl: reset"
|
||||
|
||||
# armsse-cpu-pwrctrl.c
|
||||
armsse_cpu_pwrctrl_read(uint64_t offset, uint64_t data, unsigned size) "SSE-300 CPU_PWRCTRL read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
|
||||
armsse_cpu_pwrctrl_write(uint64_t offset, uint64_t data, unsigned size) "SSE-300 CPU_PWRCTRL write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
|
||||
|
||||
# armsse-cpuid.c
|
||||
armsse_cpuid_read(uint64_t offset, uint64_t data, unsigned size) "SSE-200 CPU_IDENTITY read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
|
||||
armsse_cpuid_write(uint64_t offset, uint64_t data, unsigned size) "SSE-200 CPU_IDENTITY write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
|
||||
|
|
|
@ -307,9 +307,10 @@ static void zynq_slcr_propagate_clocks(ZynqSLCRState *s)
|
|||
clock_propagate(s->uart1_ref_clk);
|
||||
}
|
||||
|
||||
static void zynq_slcr_ps_clk_callback(void *opaque)
|
||||
static void zynq_slcr_ps_clk_callback(void *opaque, ClockEvent event)
|
||||
{
|
||||
ZynqSLCRState *s = (ZynqSLCRState *) opaque;
|
||||
|
||||
zynq_slcr_compute_clocks(s);
|
||||
zynq_slcr_propagate_clocks(s);
|
||||
}
|
||||
|
@ -576,7 +577,7 @@ static const MemoryRegionOps slcr_ops = {
|
|||
};
|
||||
|
||||
static const ClockPortInitArray zynq_slcr_clocks = {
|
||||
QDEV_CLOCK_IN(ZynqSLCRState, ps_clk, zynq_slcr_ps_clk_callback),
|
||||
QDEV_CLOCK_IN(ZynqSLCRState, ps_clk, zynq_slcr_ps_clk_callback, ClockUpdate),
|
||||
QDEV_CLOCK_OUT(ZynqSLCRState, uart0_ref_clk),
|
||||
QDEV_CLOCK_OUT(ZynqSLCRState, uart1_ref_clk),
|
||||
QDEV_CLOCK_END
|
||||
|
|
|
@ -176,7 +176,8 @@
|
|||
FIELD(GQSPI_FIFO_CTRL, GENERIC_FIFO_RESET, 0, 1)
|
||||
#define R_GQSPI_GFIFO_THRESH (0x150 / 4)
|
||||
#define R_GQSPI_DATA_STS (0x15c / 4)
|
||||
/* We use the snapshot register to hold the core state for the currently
|
||||
/*
|
||||
* We use the snapshot register to hold the core state for the currently
|
||||
* or most recently executed command. So the generic fifo format is defined
|
||||
* for the snapshot register
|
||||
*/
|
||||
|
@ -194,13 +195,6 @@
|
|||
#define R_GQSPI_MOD_ID (0x1fc / 4)
|
||||
#define R_GQSPI_MOD_ID_RESET (0x10a0000)
|
||||
|
||||
#define R_QSPIDMA_DST_CTRL (0x80c / 4)
|
||||
#define R_QSPIDMA_DST_CTRL_RESET (0x803ffa00)
|
||||
#define R_QSPIDMA_DST_I_MASK (0x820 / 4)
|
||||
#define R_QSPIDMA_DST_I_MASK_RESET (0xfe)
|
||||
#define R_QSPIDMA_DST_CTRL2 (0x824 / 4)
|
||||
#define R_QSPIDMA_DST_CTRL2_RESET (0x081bfff8)
|
||||
|
||||
/* size of TXRX FIFOs */
|
||||
#define RXFF_A (128)
|
||||
#define TXFF_A (128)
|
||||
|
@ -416,15 +410,13 @@ static void xlnx_zynqmp_qspips_reset(DeviceState *d)
|
|||
s->regs[R_GQSPI_GPIO] = 1;
|
||||
s->regs[R_GQSPI_LPBK_DLY_ADJ] = R_GQSPI_LPBK_DLY_ADJ_RESET;
|
||||
s->regs[R_GQSPI_MOD_ID] = R_GQSPI_MOD_ID_RESET;
|
||||
s->regs[R_QSPIDMA_DST_CTRL] = R_QSPIDMA_DST_CTRL_RESET;
|
||||
s->regs[R_QSPIDMA_DST_I_MASK] = R_QSPIDMA_DST_I_MASK_RESET;
|
||||
s->regs[R_QSPIDMA_DST_CTRL2] = R_QSPIDMA_DST_CTRL2_RESET;
|
||||
s->man_start_com_g = false;
|
||||
s->gqspi_irqline = 0;
|
||||
xlnx_zynqmp_qspips_update_ixr(s);
|
||||
}
|
||||
|
||||
/* N way (num) in place bit striper. Lay out row wise bits (MSB to LSB)
|
||||
/*
|
||||
* N way (num) in place bit striper. Lay out row wise bits (MSB to LSB)
|
||||
* column wise (from element 0 to N-1). num is the length of x, and dir
|
||||
* reverses the direction of the transform. Best illustrated by example:
|
||||
* Each digit in the below array is a single bit (num == 3):
|
||||
|
@ -637,8 +629,10 @@ static void xilinx_spips_flush_txfifo(XilinxSPIPS *s)
|
|||
tx_rx[i] = tx;
|
||||
}
|
||||
} else {
|
||||
/* Extract a dummy byte and generate dummy cycles according to the
|
||||
* link state */
|
||||
/*
|
||||
* Extract a dummy byte and generate dummy cycles according to the
|
||||
* link state
|
||||
*/
|
||||
tx = fifo8_pop(&s->tx_fifo);
|
||||
dummy_cycles = 8 / s->link_state;
|
||||
}
|
||||
|
@ -721,8 +715,9 @@ static void xilinx_spips_flush_txfifo(XilinxSPIPS *s)
|
|||
}
|
||||
break;
|
||||
case (SNOOP_ADDR):
|
||||
/* Address has been transmitted, transmit dummy cycles now if
|
||||
* needed */
|
||||
/*
|
||||
* Address has been transmitted, transmit dummy cycles now if needed
|
||||
*/
|
||||
if (s->cmd_dummies < 0) {
|
||||
s->snoop_state = SNOOP_NONE;
|
||||
} else {
|
||||
|
@ -876,7 +871,7 @@ static void xlnx_zynqmp_qspips_notify(void *opaque)
|
|||
}
|
||||
|
||||
static uint64_t xilinx_spips_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
unsigned size)
|
||||
{
|
||||
XilinxSPIPS *s = opaque;
|
||||
uint32_t mask = ~0;
|
||||
|
@ -970,7 +965,7 @@ static uint64_t xlnx_zynqmp_qspips_read(void *opaque,
|
|||
}
|
||||
|
||||
static void xilinx_spips_write(void *opaque, hwaddr addr,
|
||||
uint64_t value, unsigned size)
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
int mask = ~0;
|
||||
XilinxSPIPS *s = opaque;
|
||||
|
@ -1072,7 +1067,7 @@ static void xilinx_qspips_write(void *opaque, hwaddr addr,
|
|||
}
|
||||
|
||||
static void xlnx_zynqmp_qspips_write(void *opaque, hwaddr addr,
|
||||
uint64_t value, unsigned size)
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
XlnxZynqMPQSPIPS *s = XLNX_ZYNQMP_QSPIPS(opaque);
|
||||
uint32_t reg = addr / 4;
|
||||
|
|
|
@ -46,5 +46,11 @@ config RENESAS_TMR
|
|||
config RENESAS_CMT
|
||||
bool
|
||||
|
||||
config SSE_COUNTER
|
||||
bool
|
||||
|
||||
config SSE_TIMER
|
||||
bool
|
||||
|
||||
config AVR_TIMER16
|
||||
bool
|
||||
|
|
|
@ -449,7 +449,7 @@ static void cmsdk_apb_dualtimer_reset(DeviceState *dev)
|
|||
s->timeritop = 0;
|
||||
}
|
||||
|
||||
static void cmsdk_apb_dualtimer_clk_update(void *opaque)
|
||||
static void cmsdk_apb_dualtimer_clk_update(void *opaque, ClockEvent event)
|
||||
{
|
||||
CMSDKAPBDualTimer *s = CMSDK_APB_DUALTIMER(opaque);
|
||||
int i;
|
||||
|
@ -478,7 +478,8 @@ static void cmsdk_apb_dualtimer_init(Object *obj)
|
|||
sysbus_init_irq(sbd, &s->timermod[i].timerint);
|
||||
}
|
||||
s->timclk = qdev_init_clock_in(DEVICE(s), "TIMCLK",
|
||||
cmsdk_apb_dualtimer_clk_update, s);
|
||||
cmsdk_apb_dualtimer_clk_update, s,
|
||||
ClockUpdate);
|
||||
}
|
||||
|
||||
static void cmsdk_apb_dualtimer_realize(DeviceState *dev, Error **errp)
|
||||
|
|
|
@ -204,7 +204,7 @@ static void cmsdk_apb_timer_reset(DeviceState *dev)
|
|||
ptimer_transaction_commit(s->timer);
|
||||
}
|
||||
|
||||
static void cmsdk_apb_timer_clk_update(void *opaque)
|
||||
static void cmsdk_apb_timer_clk_update(void *opaque, ClockEvent event)
|
||||
{
|
||||
CMSDKAPBTimer *s = CMSDK_APB_TIMER(opaque);
|
||||
|
||||
|
@ -223,7 +223,7 @@ static void cmsdk_apb_timer_init(Object *obj)
|
|||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
sysbus_init_irq(sbd, &s->timerint);
|
||||
s->pclk = qdev_init_clock_in(DEVICE(s), "pclk",
|
||||
cmsdk_apb_timer_clk_update, s);
|
||||
cmsdk_apb_timer_clk_update, s, ClockUpdate);
|
||||
}
|
||||
|
||||
static void cmsdk_apb_timer_realize(DeviceState *dev, Error **errp)
|
||||
|
|
|
@ -32,6 +32,8 @@ softmmu_ss.add(when: 'CONFIG_PXA2XX', if_true: files('pxa2xx_timer.c'))
|
|||
softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_systmr.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_SH_TIMER', if_true: files('sh_timer.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_timer.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_SSE_COUNTER', if_true: files('sse-counter.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_SSE_TIMER', if_true: files('sse-timer.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_STM32F2XX_TIMER', if_true: files('stm32f2xx_timer.c'))
|
||||
softmmu_ss.add(when: 'CONFIG_XILINX', if_true: files('xilinx_timer.c'))
|
||||
|
||||
|
|
|
@ -138,8 +138,8 @@ static int64_t npcm7xx_timer_count_to_ns(NPCM7xxTimer *t, uint32_t count)
|
|||
/* Convert a time interval in nanoseconds to a timer cycle count. */
|
||||
static uint32_t npcm7xx_timer_ns_to_count(NPCM7xxTimer *t, int64_t ns)
|
||||
{
|
||||
return ns / clock_ticks_to_ns(t->ctrl->clock,
|
||||
npcm7xx_tcsr_prescaler(t->tcsr));
|
||||
return clock_ns_to_ticks(t->ctrl->clock, ns) /
|
||||
npcm7xx_tcsr_prescaler(t->tcsr);
|
||||
}
|
||||
|
||||
static uint32_t npcm7xx_watchdog_timer_prescaler(const NPCM7xxWatchdogTimer *t)
|
||||
|
@ -627,7 +627,7 @@ static void npcm7xx_timer_init(Object *obj)
|
|||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
qdev_init_gpio_out_named(dev, &w->reset_signal,
|
||||
NPCM7XX_WATCHDOG_RESET_GPIO_OUT, 1);
|
||||
s->clock = qdev_init_clock_in(dev, "clock", NULL, NULL);
|
||||
s->clock = qdev_init_clock_in(dev, "clock", NULL, NULL, 0);
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_npcm7xx_base_timer = {
|
||||
|
|
|
@ -46,8 +46,10 @@ REG8(TCCR, 10)
|
|||
FIELD(TCCR, CSS, 3, 2)
|
||||
FIELD(TCCR, TMRIS, 7, 1)
|
||||
|
||||
#define INTERNAL 0x01
|
||||
#define CASCADING 0x03
|
||||
#define CSS_EXTERNAL 0x00
|
||||
#define CSS_INTERNAL 0x01
|
||||
#define CSS_INVALID 0x02
|
||||
#define CSS_CASCADING 0x03
|
||||
#define CCLR_A 0x01
|
||||
#define CCLR_B 0x02
|
||||
|
||||
|
@ -72,7 +74,7 @@ static void update_events(RTMRState *tmr, int ch)
|
|||
/* event not happened */
|
||||
return ;
|
||||
}
|
||||
if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) == CASCADING) {
|
||||
if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) == CSS_CASCADING) {
|
||||
/* cascading mode */
|
||||
if (ch == 1) {
|
||||
tmr->next[ch] = none;
|
||||
|
@ -130,23 +132,32 @@ static uint16_t read_tcnt(RTMRState *tmr, unsigned size, int ch)
|
|||
if (delta > 0) {
|
||||
tmr->tick = now;
|
||||
|
||||
if (FIELD_EX8(tmr->tccr[1], TCCR, CSS) == INTERNAL) {
|
||||
switch (FIELD_EX8(tmr->tccr[1], TCCR, CSS)) {
|
||||
case CSS_INTERNAL:
|
||||
/* timer1 count update */
|
||||
elapsed = elapsed_time(tmr, 1, delta);
|
||||
if (elapsed >= 0x100) {
|
||||
ovf = elapsed >> 8;
|
||||
}
|
||||
tcnt[1] = tmr->tcnt[1] + (elapsed & 0xff);
|
||||
break;
|
||||
case CSS_INVALID: /* guest error to have set this */
|
||||
case CSS_EXTERNAL: /* QEMU doesn't implement these */
|
||||
case CSS_CASCADING:
|
||||
tcnt[1] = tmr->tcnt[1];
|
||||
break;
|
||||
}
|
||||
switch (FIELD_EX8(tmr->tccr[0], TCCR, CSS)) {
|
||||
case INTERNAL:
|
||||
case CSS_INTERNAL:
|
||||
elapsed = elapsed_time(tmr, 0, delta);
|
||||
tcnt[0] = tmr->tcnt[0] + elapsed;
|
||||
break;
|
||||
case CASCADING:
|
||||
if (ovf > 0) {
|
||||
tcnt[0] = tmr->tcnt[0] + ovf;
|
||||
}
|
||||
case CSS_CASCADING:
|
||||
tcnt[0] = tmr->tcnt[0] + ovf;
|
||||
break;
|
||||
case CSS_INVALID: /* guest error to have set this */
|
||||
case CSS_EXTERNAL: /* QEMU doesn't implement this */
|
||||
tcnt[0] = tmr->tcnt[0];
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
@ -330,7 +341,7 @@ static uint16_t issue_event(RTMRState *tmr, int ch, int sz,
|
|||
qemu_irq_pulse(tmr->cmia[ch]);
|
||||
}
|
||||
if (sz == 8 && ch == 0 &&
|
||||
FIELD_EX8(tmr->tccr[1], TCCR, CSS) == CASCADING) {
|
||||
FIELD_EX8(tmr->tccr[1], TCCR, CSS) == CSS_CASCADING) {
|
||||
tmr->tcnt[1]++;
|
||||
timer_events(tmr, 1);
|
||||
}
|
||||
|
@ -362,7 +373,7 @@ static void timer_events(RTMRState *tmr, int ch)
|
|||
uint16_t tcnt;
|
||||
|
||||
tmr->tcnt[ch] = read_tcnt(tmr, 1, ch);
|
||||
if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) != CASCADING) {
|
||||
if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) != CSS_CASCADING) {
|
||||
tmr->tcnt[ch] = issue_event(tmr, ch, 8,
|
||||
tmr->tcnt[ch],
|
||||
tmr->tcora[ch],
|
||||
|
|
474
hw/timer/sse-counter.c
Normal file
474
hw/timer/sse-counter.c
Normal file
|
@ -0,0 +1,474 @@
|
|||
/*
|
||||
* Arm SSE Subsystem System Counter
|
||||
*
|
||||
* Copyright (c) 2020 Linaro Limited
|
||||
* Written by Peter Maydell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is a model of the "System counter" which is documented in
|
||||
* the Arm SSE-123 Example Subsystem Technical Reference Manual:
|
||||
* https://developer.arm.com/documentation/101370/latest/
|
||||
*
|
||||
* The system counter is a non-stop 64-bit up-counter. It provides
|
||||
* this count value to other devices like the SSE system timer,
|
||||
* which are driven by this system timestamp rather than directly
|
||||
* from a clock. Internally to the counter the count is actually
|
||||
* 88-bit precision (64.24 fixed point), with a programmable scale factor.
|
||||
*
|
||||
* The hardware has the optional feature that it supports dynamic
|
||||
* clock switching, where two clock inputs are connected, and which
|
||||
* one is used is selected via a CLKSEL input signal. Since the
|
||||
* users of this device in QEMU don't use this feature, we only model
|
||||
* the HWCLKSW=0 configuration.
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "qapi/error.h"
|
||||
#include "trace.h"
|
||||
#include "hw/timer/sse-counter.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "hw/irq.h"
|
||||
#include "hw/registerfields.h"
|
||||
#include "hw/clock.h"
|
||||
#include "hw/qdev-clock.h"
|
||||
#include "migration/vmstate.h"
|
||||
|
||||
/* Registers in the control frame */
|
||||
REG32(CNTCR, 0x0)
|
||||
FIELD(CNTCR, EN, 0, 1)
|
||||
FIELD(CNTCR, HDBG, 1, 1)
|
||||
FIELD(CNTCR, SCEN, 2, 1)
|
||||
FIELD(CNTCR, INTRMASK, 3, 1)
|
||||
FIELD(CNTCR, PSLVERRDIS, 4, 1)
|
||||
FIELD(CNTCR, INTRCLR, 5, 1)
|
||||
/*
|
||||
* Although CNTCR defines interrupt-related bits, the counter doesn't
|
||||
* appear to actually have an interrupt output. So INTRCLR is
|
||||
* effectively a RAZ/WI bit, as are the reserved bits [31:6].
|
||||
*/
|
||||
#define CNTCR_VALID_MASK (R_CNTCR_EN_MASK | R_CNTCR_HDBG_MASK | \
|
||||
R_CNTCR_SCEN_MASK | R_CNTCR_INTRMASK_MASK | \
|
||||
R_CNTCR_PSLVERRDIS_MASK)
|
||||
REG32(CNTSR, 0x4)
|
||||
REG32(CNTCV_LO, 0x8)
|
||||
REG32(CNTCV_HI, 0xc)
|
||||
REG32(CNTSCR, 0x10) /* Aliased with CNTSCR0 */
|
||||
REG32(CNTID, 0x1c)
|
||||
FIELD(CNTID, CNTSC, 0, 4)
|
||||
FIELD(CNTID, CNTCS, 16, 1)
|
||||
FIELD(CNTID, CNTSELCLK, 17, 2)
|
||||
FIELD(CNTID, CNTSCR_OVR, 19, 1)
|
||||
REG32(CNTSCR0, 0xd0)
|
||||
REG32(CNTSCR1, 0xd4)
|
||||
|
||||
/* Registers in the status frame */
|
||||
REG32(STATUS_CNTCV_LO, 0x0)
|
||||
REG32(STATUS_CNTCV_HI, 0x4)
|
||||
|
||||
/* Standard ID registers, present in both frames */
|
||||
REG32(PID4, 0xFD0)
|
||||
REG32(PID5, 0xFD4)
|
||||
REG32(PID6, 0xFD8)
|
||||
REG32(PID7, 0xFDC)
|
||||
REG32(PID0, 0xFE0)
|
||||
REG32(PID1, 0xFE4)
|
||||
REG32(PID2, 0xFE8)
|
||||
REG32(PID3, 0xFEC)
|
||||
REG32(CID0, 0xFF0)
|
||||
REG32(CID1, 0xFF4)
|
||||
REG32(CID2, 0xFF8)
|
||||
REG32(CID3, 0xFFC)
|
||||
|
||||
/* PID/CID values */
|
||||
static const int control_id[] = {
|
||||
0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
|
||||
0xba, 0xb0, 0x0b, 0x00, /* PID0..PID3 */
|
||||
0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
|
||||
};
|
||||
|
||||
static const int status_id[] = {
|
||||
0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
|
||||
0xbb, 0xb0, 0x0b, 0x00, /* PID0..PID3 */
|
||||
0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
|
||||
};
|
||||
|
||||
static void sse_counter_notify_users(SSECounter *s)
|
||||
{
|
||||
/*
|
||||
* Notify users of the count timestamp that they may
|
||||
* need to recalculate.
|
||||
*/
|
||||
notifier_list_notify(&s->notifier_list, NULL);
|
||||
}
|
||||
|
||||
static bool sse_counter_enabled(SSECounter *s)
|
||||
{
|
||||
return (s->cntcr & R_CNTCR_EN_MASK) != 0;
|
||||
}
|
||||
|
||||
uint64_t sse_counter_tick_to_time(SSECounter *s, uint64_t tick)
|
||||
{
|
||||
if (!sse_counter_enabled(s)) {
|
||||
return UINT64_MAX;
|
||||
}
|
||||
|
||||
tick -= s->ticks_then;
|
||||
|
||||
if (s->cntcr & R_CNTCR_SCEN_MASK) {
|
||||
/* Adjust the tick count to account for the scale factor */
|
||||
tick = muldiv64(tick, 0x01000000, s->cntscr0);
|
||||
}
|
||||
|
||||
return s->ns_then + clock_ticks_to_ns(s->clk, tick);
|
||||
}
|
||||
|
||||
void sse_counter_register_consumer(SSECounter *s, Notifier *notifier)
|
||||
{
|
||||
/*
|
||||
* For the moment we assume that both we and the devices
|
||||
* which consume us last for the life of the simulation,
|
||||
* and so there is no mechanism for removing a notifier.
|
||||
*/
|
||||
notifier_list_add(&s->notifier_list, notifier);
|
||||
}
|
||||
|
||||
uint64_t sse_counter_for_timestamp(SSECounter *s, uint64_t now)
|
||||
{
|
||||
/* Return the CNTCV value for a particular timestamp (clock ns value). */
|
||||
uint64_t ticks;
|
||||
|
||||
if (!sse_counter_enabled(s)) {
|
||||
/* Counter is disabled and does not increment */
|
||||
return s->ticks_then;
|
||||
}
|
||||
|
||||
ticks = clock_ns_to_ticks(s->clk, now - s->ns_then);
|
||||
if (s->cntcr & R_CNTCR_SCEN_MASK) {
|
||||
/*
|
||||
* Scaling is enabled. The CNTSCR value is the amount added to
|
||||
* the underlying 88-bit counter for every tick of the
|
||||
* underlying clock; CNTCV is the top 64 bits of that full
|
||||
* 88-bit value. Multiplying the tick count by CNTSCR tells us
|
||||
* how much the full 88-bit counter has moved on; we then
|
||||
* divide that by 0x01000000 to find out how much the 64-bit
|
||||
* visible portion has advanced. muldiv64() gives us the
|
||||
* necessary at-least-88-bit precision for the intermediate
|
||||
* result.
|
||||
*/
|
||||
ticks = muldiv64(ticks, s->cntscr0, 0x01000000);
|
||||
}
|
||||
return s->ticks_then + ticks;
|
||||
}
|
||||
|
||||
static uint64_t sse_cntcv(SSECounter *s)
|
||||
{
|
||||
/* Return the CNTCV value for the current time */
|
||||
return sse_counter_for_timestamp(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
|
||||
}
|
||||
|
||||
static void sse_write_cntcv(SSECounter *s, uint32_t value, unsigned startbit)
|
||||
{
|
||||
/*
|
||||
* Write one 32-bit half of the counter value; startbit is the
|
||||
* bit position of this half in the 64-bit word, either 0 or 32.
|
||||
*/
|
||||
uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
uint64_t cntcv = sse_counter_for_timestamp(s, now);
|
||||
|
||||
cntcv = deposit64(cntcv, startbit, 32, value);
|
||||
s->ticks_then = cntcv;
|
||||
s->ns_then = now;
|
||||
sse_counter_notify_users(s);
|
||||
}
|
||||
|
||||
static uint64_t sse_counter_control_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
SSECounter *s = SSE_COUNTER(opaque);
|
||||
uint64_t r;
|
||||
|
||||
switch (offset) {
|
||||
case A_CNTCR:
|
||||
r = s->cntcr;
|
||||
break;
|
||||
case A_CNTSR:
|
||||
/*
|
||||
* The only bit here is DBGH, indicating that the counter has been
|
||||
* halted via the Halt-on-Debug signal. We don't implement halting
|
||||
* debug, so the whole register always reads as zero.
|
||||
*/
|
||||
r = 0;
|
||||
break;
|
||||
case A_CNTCV_LO:
|
||||
r = extract64(sse_cntcv(s), 0, 32);
|
||||
break;
|
||||
case A_CNTCV_HI:
|
||||
r = extract64(sse_cntcv(s), 32, 32);
|
||||
break;
|
||||
case A_CNTID:
|
||||
/*
|
||||
* For our implementation:
|
||||
* - CNTSCR can only be written when CNTCR.EN == 0
|
||||
* - HWCLKSW=0, so selected clock is always CLK0
|
||||
* - counter scaling is implemented
|
||||
*/
|
||||
r = (1 << R_CNTID_CNTSELCLK_SHIFT) | (1 << R_CNTID_CNTSC_SHIFT);
|
||||
break;
|
||||
case A_CNTSCR:
|
||||
case A_CNTSCR0:
|
||||
r = s->cntscr0;
|
||||
break;
|
||||
case A_CNTSCR1:
|
||||
/* If HWCLKSW == 0, CNTSCR1 is RAZ/WI */
|
||||
r = 0;
|
||||
break;
|
||||
case A_PID4 ... A_CID3:
|
||||
r = control_id[(offset - A_PID4) / 4];
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"SSE System Counter control frame read: bad offset 0x%x",
|
||||
(unsigned)offset);
|
||||
r = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
trace_sse_counter_control_read(offset, r, size);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void sse_counter_control_write(void *opaque, hwaddr offset,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
SSECounter *s = SSE_COUNTER(opaque);
|
||||
|
||||
trace_sse_counter_control_write(offset, value, size);
|
||||
|
||||
switch (offset) {
|
||||
case A_CNTCR:
|
||||
/*
|
||||
* Although CNTCR defines interrupt-related bits, the counter doesn't
|
||||
* appear to actually have an interrupt output. So INTRCLR is
|
||||
* effectively a RAZ/WI bit, as are the reserved bits [31:6].
|
||||
* The documentation does not explicitly say so, but we assume
|
||||
* that changing the scale factor while the counter is enabled
|
||||
* by toggling CNTCR.SCEN has the same behaviour (making the counter
|
||||
* value UNKNOWN) as changing it by writing to CNTSCR, and so we
|
||||
* don't need to try to recalculate for that case.
|
||||
*/
|
||||
value &= CNTCR_VALID_MASK;
|
||||
if ((value ^ s->cntcr) & R_CNTCR_EN_MASK) {
|
||||
/*
|
||||
* Whether the counter is being enabled or disabled, the
|
||||
* required action is the same: sync the (ns_then, ticks_then)
|
||||
* tuple.
|
||||
*/
|
||||
uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
s->ticks_then = sse_counter_for_timestamp(s, now);
|
||||
s->ns_then = now;
|
||||
sse_counter_notify_users(s);
|
||||
}
|
||||
s->cntcr = value;
|
||||
break;
|
||||
case A_CNTCV_LO:
|
||||
sse_write_cntcv(s, value, 0);
|
||||
break;
|
||||
case A_CNTCV_HI:
|
||||
sse_write_cntcv(s, value, 32);
|
||||
break;
|
||||
case A_CNTSCR:
|
||||
case A_CNTSCR0:
|
||||
/*
|
||||
* If the scale registers are changed when the counter is enabled,
|
||||
* the count value becomes UNKNOWN. So we don't try to recalculate
|
||||
* anything here but only do it on a write to CNTCR.EN.
|
||||
*/
|
||||
s->cntscr0 = value;
|
||||
break;
|
||||
case A_CNTSCR1:
|
||||
/* If HWCLKSW == 0, CNTSCR1 is RAZ/WI */
|
||||
break;
|
||||
case A_CNTSR:
|
||||
case A_CNTID:
|
||||
case A_PID4 ... A_CID3:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"SSE System Counter control frame: write to RO offset 0x%x\n",
|
||||
(unsigned)offset);
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"SSE System Counter control frame: write to bad offset 0x%x\n",
|
||||
(unsigned)offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t sse_counter_status_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
SSECounter *s = SSE_COUNTER(opaque);
|
||||
uint64_t r;
|
||||
|
||||
switch (offset) {
|
||||
case A_STATUS_CNTCV_LO:
|
||||
r = extract64(sse_cntcv(s), 0, 32);
|
||||
break;
|
||||
case A_STATUS_CNTCV_HI:
|
||||
r = extract64(sse_cntcv(s), 32, 32);
|
||||
break;
|
||||
case A_PID4 ... A_CID3:
|
||||
r = status_id[(offset - A_PID4) / 4];
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"SSE System Counter status frame read: bad offset 0x%x",
|
||||
(unsigned)offset);
|
||||
r = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
trace_sse_counter_status_read(offset, r, size);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void sse_counter_status_write(void *opaque, hwaddr offset,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
trace_sse_counter_status_write(offset, value, size);
|
||||
|
||||
switch (offset) {
|
||||
case A_STATUS_CNTCV_LO:
|
||||
case A_STATUS_CNTCV_HI:
|
||||
case A_PID4 ... A_CID3:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"SSE System Counter status frame: write to RO offset 0x%x\n",
|
||||
(unsigned)offset);
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"SSE System Counter status frame: write to bad offset 0x%x\n",
|
||||
(unsigned)offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps sse_counter_control_ops = {
|
||||
.read = sse_counter_control_read,
|
||||
.write = sse_counter_control_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid.min_access_size = 4,
|
||||
.valid.max_access_size = 4,
|
||||
};
|
||||
|
||||
static const MemoryRegionOps sse_counter_status_ops = {
|
||||
.read = sse_counter_status_read,
|
||||
.write = sse_counter_status_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid.min_access_size = 4,
|
||||
.valid.max_access_size = 4,
|
||||
};
|
||||
|
||||
static void sse_counter_reset(DeviceState *dev)
|
||||
{
|
||||
SSECounter *s = SSE_COUNTER(dev);
|
||||
|
||||
trace_sse_counter_reset();
|
||||
|
||||
s->cntcr = 0;
|
||||
s->cntscr0 = 0x01000000;
|
||||
s->ns_then = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
s->ticks_then = 0;
|
||||
}
|
||||
|
||||
static void sse_clk_callback(void *opaque, ClockEvent event)
|
||||
{
|
||||
SSECounter *s = SSE_COUNTER(opaque);
|
||||
uint64_t now;
|
||||
|
||||
switch (event) {
|
||||
case ClockPreUpdate:
|
||||
/*
|
||||
* Before the clock period updates, set (ticks_then, ns_then)
|
||||
* to the current time and tick count (as calculated with
|
||||
* the old clock period).
|
||||
*/
|
||||
if (sse_counter_enabled(s)) {
|
||||
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
s->ticks_then = sse_counter_for_timestamp(s, now);
|
||||
s->ns_then = now;
|
||||
}
|
||||
break;
|
||||
case ClockUpdate:
|
||||
sse_counter_notify_users(s);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void sse_counter_init(Object *obj)
|
||||
{
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
SSECounter *s = SSE_COUNTER(obj);
|
||||
|
||||
notifier_list_init(&s->notifier_list);
|
||||
|
||||
s->clk = qdev_init_clock_in(DEVICE(obj), "CLK", sse_clk_callback, s,
|
||||
ClockPreUpdate | ClockUpdate);
|
||||
memory_region_init_io(&s->control_mr, obj, &sse_counter_control_ops,
|
||||
s, "sse-counter-control", 0x1000);
|
||||
memory_region_init_io(&s->status_mr, obj, &sse_counter_status_ops,
|
||||
s, "sse-counter-status", 0x1000);
|
||||
sysbus_init_mmio(sbd, &s->control_mr);
|
||||
sysbus_init_mmio(sbd, &s->status_mr);
|
||||
}
|
||||
|
||||
static void sse_counter_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
SSECounter *s = SSE_COUNTER(dev);
|
||||
|
||||
if (!clock_has_source(s->clk)) {
|
||||
error_setg(errp, "SSE system counter: CLK must be connected");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static const VMStateDescription sse_counter_vmstate = {
|
||||
.name = "sse-counter",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_CLOCK(clk, SSECounter),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void sse_counter_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->realize = sse_counter_realize;
|
||||
dc->vmsd = &sse_counter_vmstate;
|
||||
dc->reset = sse_counter_reset;
|
||||
}
|
||||
|
||||
static const TypeInfo sse_counter_info = {
|
||||
.name = TYPE_SSE_COUNTER,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(SSECounter),
|
||||
.instance_init = sse_counter_init,
|
||||
.class_init = sse_counter_class_init,
|
||||
};
|
||||
|
||||
static void sse_counter_register_types(void)
|
||||
{
|
||||
type_register_static(&sse_counter_info);
|
||||
}
|
||||
|
||||
type_init(sse_counter_register_types);
|
470
hw/timer/sse-timer.c
Normal file
470
hw/timer/sse-timer.c
Normal file
|
@ -0,0 +1,470 @@
|
|||
/*
|
||||
* Arm SSE Subsystem System Timer
|
||||
*
|
||||
* Copyright (c) 2020 Linaro Limited
|
||||
* Written by Peter Maydell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is a model of the "System timer" which is documented in
|
||||
* the Arm SSE-123 Example Subsystem Technical Reference Manual:
|
||||
* https://developer.arm.com/documentation/101370/latest/
|
||||
*
|
||||
* The timer is based around a simple 64-bit incrementing counter
|
||||
* (readable from CNTPCT_HI/LO). The timer fires when
|
||||
* Counter - CompareValue >= 0.
|
||||
* The CompareValue is guest-writable, via CNTP_CVAL_HI/LO.
|
||||
* CNTP_TVAL is an alternative view of the CompareValue defined by
|
||||
* TimerValue = CompareValue[31:0] - Counter[31:0]
|
||||
* which can be both read and written.
|
||||
* This part is similar to the generic timer in an Arm A-class CPU.
|
||||
*
|
||||
* The timer also has a separate auto-increment timer. When this
|
||||
* timer is enabled, then the AutoIncrValue is set to:
|
||||
* AutoIncrValue = Reload + Counter
|
||||
* and this timer fires when
|
||||
* Counter - AutoIncrValue >= 0
|
||||
* at which point, an interrupt is generated and the new AutoIncrValue
|
||||
* is calculated.
|
||||
* When the auto-increment timer is enabled, interrupt generation
|
||||
* via the compare/timervalue registers is disabled.
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "qapi/error.h"
|
||||
#include "trace.h"
|
||||
#include "hw/timer/sse-timer.h"
|
||||
#include "hw/timer/sse-counter.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "hw/irq.h"
|
||||
#include "hw/registerfields.h"
|
||||
#include "hw/clock.h"
|
||||
#include "hw/qdev-clock.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "migration/vmstate.h"
|
||||
|
||||
REG32(CNTPCT_LO, 0x0)
|
||||
REG32(CNTPCT_HI, 0x4)
|
||||
REG32(CNTFRQ, 0x10)
|
||||
REG32(CNTP_CVAL_LO, 0x20)
|
||||
REG32(CNTP_CVAL_HI, 0x24)
|
||||
REG32(CNTP_TVAL, 0x28)
|
||||
REG32(CNTP_CTL, 0x2c)
|
||||
FIELD(CNTP_CTL, ENABLE, 0, 1)
|
||||
FIELD(CNTP_CTL, IMASK, 1, 1)
|
||||
FIELD(CNTP_CTL, ISTATUS, 2, 1)
|
||||
REG32(CNTP_AIVAL_LO, 0x40)
|
||||
REG32(CNTP_AIVAL_HI, 0x44)
|
||||
REG32(CNTP_AIVAL_RELOAD, 0x48)
|
||||
REG32(CNTP_AIVAL_CTL, 0x4c)
|
||||
FIELD(CNTP_AIVAL_CTL, EN, 0, 1)
|
||||
FIELD(CNTP_AIVAL_CTL, CLR, 1, 1)
|
||||
REG32(CNTP_CFG, 0x50)
|
||||
FIELD(CNTP_CFG, AIVAL, 0, 4)
|
||||
#define R_CNTP_CFG_AIVAL_IMPLEMENTED 1
|
||||
REG32(PID4, 0xFD0)
|
||||
REG32(PID5, 0xFD4)
|
||||
REG32(PID6, 0xFD8)
|
||||
REG32(PID7, 0xFDC)
|
||||
REG32(PID0, 0xFE0)
|
||||
REG32(PID1, 0xFE4)
|
||||
REG32(PID2, 0xFE8)
|
||||
REG32(PID3, 0xFEC)
|
||||
REG32(CID0, 0xFF0)
|
||||
REG32(CID1, 0xFF4)
|
||||
REG32(CID2, 0xFF8)
|
||||
REG32(CID3, 0xFFC)
|
||||
|
||||
/* PID/CID values */
|
||||
static const int timer_id[] = {
|
||||
0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
|
||||
0xb7, 0xb0, 0x0b, 0x00, /* PID0..PID3 */
|
||||
0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
|
||||
};
|
||||
|
||||
static bool sse_is_autoinc(SSETimer *s)
|
||||
{
|
||||
return (s->cntp_aival_ctl & R_CNTP_AIVAL_CTL_EN_MASK) != 0;
|
||||
}
|
||||
|
||||
static bool sse_enabled(SSETimer *s)
|
||||
{
|
||||
return (s->cntp_ctl & R_CNTP_CTL_ENABLE_MASK) != 0;
|
||||
}
|
||||
|
||||
static uint64_t sse_cntpct(SSETimer *s)
|
||||
{
|
||||
/* Return the CNTPCT value for the current time */
|
||||
return sse_counter_for_timestamp(s->counter,
|
||||
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
|
||||
}
|
||||
|
||||
static bool sse_timer_status(SSETimer *s)
|
||||
{
|
||||
/*
|
||||
* Return true if timer condition is met. This is used for both
|
||||
* the CNTP_CTL.ISTATUS bit and for whether (unless masked) we
|
||||
* assert our IRQ.
|
||||
* The documentation is unclear about the behaviour of ISTATUS when
|
||||
* in autoincrement mode; we assume that it follows CNTP_AIVAL_CTL.CLR
|
||||
* (ie whether the autoincrement timer is asserting the interrupt).
|
||||
*/
|
||||
if (!sse_enabled(s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sse_is_autoinc(s)) {
|
||||
return s->cntp_aival_ctl & R_CNTP_AIVAL_CTL_CLR_MASK;
|
||||
} else {
|
||||
return sse_cntpct(s) >= s->cntp_cval;
|
||||
}
|
||||
}
|
||||
|
||||
static void sse_update_irq(SSETimer *s)
|
||||
{
|
||||
bool irqstate = (!(s->cntp_ctl & R_CNTP_CTL_IMASK_MASK) &&
|
||||
sse_timer_status(s));
|
||||
|
||||
qemu_set_irq(s->irq, irqstate);
|
||||
}
|
||||
|
||||
static void sse_set_timer(SSETimer *s, uint64_t nexttick)
|
||||
{
|
||||
/* Set the timer to expire at nexttick */
|
||||
uint64_t expiry = sse_counter_tick_to_time(s->counter, nexttick);
|
||||
|
||||
if (expiry <= INT64_MAX) {
|
||||
timer_mod_ns(&s->timer, expiry);
|
||||
} else {
|
||||
/*
|
||||
* nexttick is so far in the future that it would overflow the
|
||||
* signed 64-bit range of a QEMUTimer. Since timer_mod_ns()
|
||||
* expiry times are absolute, not relative, we are never going
|
||||
* to be able to set the timer to this value, so we must just
|
||||
* assume that guest execution can never run so long that it
|
||||
* reaches the theoretical point when the timer fires.
|
||||
* This is also the code path for "counter is not running",
|
||||
* which is signalled by expiry == UINT64_MAX.
|
||||
*/
|
||||
timer_del(&s->timer);
|
||||
}
|
||||
}
|
||||
|
||||
static void sse_recalc_timer(SSETimer *s)
|
||||
{
|
||||
/* Recalculate the normal timer */
|
||||
uint64_t count, nexttick;
|
||||
|
||||
if (sse_is_autoinc(s)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sse_enabled(s)) {
|
||||
timer_del(&s->timer);
|
||||
return;
|
||||
}
|
||||
|
||||
count = sse_cntpct(s);
|
||||
|
||||
if (count >= s->cntp_cval) {
|
||||
/*
|
||||
* Timer condition already met. In theory we have a transition when
|
||||
* the count rolls back over to 0, but that is so far in the future
|
||||
* that it is not representable as a timer_mod() expiry, so in
|
||||
* fact sse_set_timer() will always just delete the timer.
|
||||
*/
|
||||
nexttick = UINT64_MAX;
|
||||
} else {
|
||||
/* Next transition is when count hits cval */
|
||||
nexttick = s->cntp_cval;
|
||||
}
|
||||
sse_set_timer(s, nexttick);
|
||||
sse_update_irq(s);
|
||||
}
|
||||
|
||||
static void sse_autoinc(SSETimer *s)
|
||||
{
|
||||
/* Auto-increment the AIVAL, and set the timer accordingly */
|
||||
s->cntp_aival = sse_cntpct(s) + s->cntp_aival_reload;
|
||||
sse_set_timer(s, s->cntp_aival);
|
||||
}
|
||||
|
||||
static void sse_timer_cb(void *opaque)
|
||||
{
|
||||
SSETimer *s = SSE_TIMER(opaque);
|
||||
|
||||
if (sse_is_autoinc(s)) {
|
||||
uint64_t count = sse_cntpct(s);
|
||||
|
||||
if (count >= s->cntp_aival) {
|
||||
/* Timer condition met, set CLR and do another autoinc */
|
||||
s->cntp_aival_ctl |= R_CNTP_AIVAL_CTL_CLR_MASK;
|
||||
s->cntp_aival = count + s->cntp_aival_reload;
|
||||
}
|
||||
sse_set_timer(s, s->cntp_aival);
|
||||
sse_update_irq(s);
|
||||
} else {
|
||||
sse_recalc_timer(s);
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t sse_timer_read(void *opaque, hwaddr offset, unsigned size)
|
||||
{
|
||||
SSETimer *s = SSE_TIMER(opaque);
|
||||
uint64_t r;
|
||||
|
||||
switch (offset) {
|
||||
case A_CNTPCT_LO:
|
||||
r = extract64(sse_cntpct(s), 0, 32);
|
||||
break;
|
||||
case A_CNTPCT_HI:
|
||||
r = extract64(sse_cntpct(s), 32, 32);
|
||||
break;
|
||||
case A_CNTFRQ:
|
||||
r = s->cntfrq;
|
||||
break;
|
||||
case A_CNTP_CVAL_LO:
|
||||
r = extract64(s->cntp_cval, 0, 32);
|
||||
break;
|
||||
case A_CNTP_CVAL_HI:
|
||||
r = extract64(s->cntp_cval, 32, 32);
|
||||
break;
|
||||
case A_CNTP_TVAL:
|
||||
r = extract64(s->cntp_cval - sse_cntpct(s), 0, 32);
|
||||
break;
|
||||
case A_CNTP_CTL:
|
||||
r = s->cntp_ctl;
|
||||
if (sse_timer_status(s)) {
|
||||
r |= R_CNTP_CTL_ISTATUS_MASK;
|
||||
}
|
||||
break;
|
||||
case A_CNTP_AIVAL_LO:
|
||||
r = extract64(s->cntp_aival, 0, 32);
|
||||
break;
|
||||
case A_CNTP_AIVAL_HI:
|
||||
r = extract64(s->cntp_aival, 32, 32);
|
||||
break;
|
||||
case A_CNTP_AIVAL_RELOAD:
|
||||
r = s->cntp_aival_reload;
|
||||
break;
|
||||
case A_CNTP_AIVAL_CTL:
|
||||
/*
|
||||
* All the bits of AIVAL_CTL are documented as WO, but this is probably
|
||||
* a documentation error. We implement them as readable.
|
||||
*/
|
||||
r = s->cntp_aival_ctl;
|
||||
break;
|
||||
case A_CNTP_CFG:
|
||||
r = R_CNTP_CFG_AIVAL_IMPLEMENTED << R_CNTP_CFG_AIVAL_SHIFT;
|
||||
break;
|
||||
case A_PID4 ... A_CID3:
|
||||
r = timer_id[(offset - A_PID4) / 4];
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"SSE System Timer read: bad offset 0x%x",
|
||||
(unsigned) offset);
|
||||
r = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
trace_sse_timer_read(offset, r, size);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void sse_timer_write(void *opaque, hwaddr offset, uint64_t value,
|
||||
unsigned size)
|
||||
{
|
||||
SSETimer *s = SSE_TIMER(opaque);
|
||||
|
||||
trace_sse_timer_write(offset, value, size);
|
||||
|
||||
switch (offset) {
|
||||
case A_CNTFRQ:
|
||||
s->cntfrq = value;
|
||||
break;
|
||||
case A_CNTP_CVAL_LO:
|
||||
s->cntp_cval = deposit64(s->cntp_cval, 0, 32, value);
|
||||
sse_recalc_timer(s);
|
||||
break;
|
||||
case A_CNTP_CVAL_HI:
|
||||
s->cntp_cval = deposit64(s->cntp_cval, 32, 32, value);
|
||||
sse_recalc_timer(s);
|
||||
break;
|
||||
case A_CNTP_TVAL:
|
||||
s->cntp_cval = sse_cntpct(s) + sextract64(value, 0, 32);
|
||||
sse_recalc_timer(s);
|
||||
break;
|
||||
case A_CNTP_CTL:
|
||||
{
|
||||
uint32_t old_ctl = s->cntp_ctl;
|
||||
value &= R_CNTP_CTL_ENABLE_MASK | R_CNTP_CTL_IMASK_MASK;
|
||||
s->cntp_ctl = value;
|
||||
if ((old_ctl ^ s->cntp_ctl) & R_CNTP_CTL_ENABLE_MASK) {
|
||||
if (sse_enabled(s)) {
|
||||
if (sse_is_autoinc(s)) {
|
||||
sse_autoinc(s);
|
||||
} else {
|
||||
sse_recalc_timer(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
sse_update_irq(s);
|
||||
break;
|
||||
}
|
||||
case A_CNTP_AIVAL_RELOAD:
|
||||
s->cntp_aival_reload = value;
|
||||
break;
|
||||
case A_CNTP_AIVAL_CTL:
|
||||
{
|
||||
uint32_t old_ctl = s->cntp_aival_ctl;
|
||||
|
||||
/* EN bit is writeable; CLR bit is write-0-to-clear, write-1-ignored */
|
||||
s->cntp_aival_ctl &= ~R_CNTP_AIVAL_CTL_EN_MASK;
|
||||
s->cntp_aival_ctl |= value & R_CNTP_AIVAL_CTL_EN_MASK;
|
||||
if (!(value & R_CNTP_AIVAL_CTL_CLR_MASK)) {
|
||||
s->cntp_aival_ctl &= ~R_CNTP_AIVAL_CTL_CLR_MASK;
|
||||
}
|
||||
if ((old_ctl ^ s->cntp_aival_ctl) & R_CNTP_AIVAL_CTL_EN_MASK) {
|
||||
/* Auto-increment toggled on/off */
|
||||
if (sse_enabled(s)) {
|
||||
if (sse_is_autoinc(s)) {
|
||||
sse_autoinc(s);
|
||||
} else {
|
||||
sse_recalc_timer(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
sse_update_irq(s);
|
||||
break;
|
||||
}
|
||||
case A_CNTPCT_LO:
|
||||
case A_CNTPCT_HI:
|
||||
case A_CNTP_CFG:
|
||||
case A_CNTP_AIVAL_LO:
|
||||
case A_CNTP_AIVAL_HI:
|
||||
case A_PID4 ... A_CID3:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"SSE System Timer write: write to RO offset 0x%x\n",
|
||||
(unsigned)offset);
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"SSE System Timer write: bad offset 0x%x\n",
|
||||
(unsigned)offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps sse_timer_ops = {
|
||||
.read = sse_timer_read,
|
||||
.write = sse_timer_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid.min_access_size = 4,
|
||||
.valid.max_access_size = 4,
|
||||
};
|
||||
|
||||
static void sse_timer_reset(DeviceState *dev)
|
||||
{
|
||||
SSETimer *s = SSE_TIMER(dev);
|
||||
|
||||
trace_sse_timer_reset();
|
||||
|
||||
timer_del(&s->timer);
|
||||
s->cntfrq = 0;
|
||||
s->cntp_ctl = 0;
|
||||
s->cntp_cval = 0;
|
||||
s->cntp_aival = 0;
|
||||
s->cntp_aival_ctl = 0;
|
||||
s->cntp_aival_reload = 0;
|
||||
}
|
||||
|
||||
static void sse_timer_counter_callback(Notifier *notifier, void *data)
|
||||
{
|
||||
SSETimer *s = container_of(notifier, SSETimer, counter_notifier);
|
||||
|
||||
/* System counter told us we need to recalculate */
|
||||
if (sse_enabled(s)) {
|
||||
if (sse_is_autoinc(s)) {
|
||||
sse_set_timer(s, s->cntp_aival);
|
||||
} else {
|
||||
sse_recalc_timer(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sse_timer_init(Object *obj)
|
||||
{
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
SSETimer *s = SSE_TIMER(obj);
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &sse_timer_ops,
|
||||
s, "sse-timer", 0x1000);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
sysbus_init_irq(sbd, &s->irq);
|
||||
}
|
||||
|
||||
static void sse_timer_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
SSETimer *s = SSE_TIMER(dev);
|
||||
|
||||
if (!s->counter) {
|
||||
error_setg(errp, "counter property was not set");
|
||||
}
|
||||
|
||||
s->counter_notifier.notify = sse_timer_counter_callback;
|
||||
sse_counter_register_consumer(s->counter, &s->counter_notifier);
|
||||
|
||||
timer_init_ns(&s->timer, QEMU_CLOCK_VIRTUAL, sse_timer_cb, s);
|
||||
}
|
||||
|
||||
static const VMStateDescription sse_timer_vmstate = {
|
||||
.name = "sse-timer",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_TIMER(timer, SSETimer),
|
||||
VMSTATE_UINT32(cntfrq, SSETimer),
|
||||
VMSTATE_UINT32(cntp_ctl, SSETimer),
|
||||
VMSTATE_UINT64(cntp_cval, SSETimer),
|
||||
VMSTATE_UINT64(cntp_aival, SSETimer),
|
||||
VMSTATE_UINT32(cntp_aival_ctl, SSETimer),
|
||||
VMSTATE_UINT32(cntp_aival_reload, SSETimer),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static Property sse_timer_properties[] = {
|
||||
DEFINE_PROP_LINK("counter", SSETimer, counter, TYPE_SSE_COUNTER, SSECounter *),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void sse_timer_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->realize = sse_timer_realize;
|
||||
dc->vmsd = &sse_timer_vmstate;
|
||||
dc->reset = sse_timer_reset;
|
||||
device_class_set_props(dc, sse_timer_properties);
|
||||
}
|
||||
|
||||
static const TypeInfo sse_timer_info = {
|
||||
.name = TYPE_SSE_TIMER,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(SSETimer),
|
||||
.instance_init = sse_timer_init,
|
||||
.class_init = sse_timer_class_init,
|
||||
};
|
||||
|
||||
static void sse_timer_register_types(void)
|
||||
{
|
||||
type_register_static(&sse_timer_info);
|
||||
}
|
||||
|
||||
type_init(sse_timer_register_types);
|
|
@ -93,3 +93,15 @@ avr_timer16_interrupt_count(uint8_t cnt) "count: %u"
|
|||
avr_timer16_interrupt_overflow(const char *reason) "overflow: %s"
|
||||
avr_timer16_next_alarm(uint64_t delay_ns) "next alarm: %" PRIu64 " ns from now"
|
||||
avr_timer16_clksrc_update(uint64_t freq_hz, uint64_t period_ns, uint64_t delay_s) "timer frequency: %" PRIu64 " Hz, period: %" PRIu64 " ns (%" PRId64 " us)"
|
||||
|
||||
# sse_counter.c
|
||||
sse_counter_control_read(uint64_t offset, uint64_t data, unsigned size) "SSE system counter control frame read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
|
||||
sse_counter_control_write(uint64_t offset, uint64_t data, unsigned size) "SSE system counter control framen write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
|
||||
sse_counter_status_read(uint64_t offset, uint64_t data, unsigned size) "SSE system counter status frame read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
|
||||
sse_counter_status_write(uint64_t offset, uint64_t data, unsigned size) "SSE system counter status frame write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
|
||||
sse_counter_reset(void) "SSE system counter: reset"
|
||||
|
||||
# sse_timer.c
|
||||
sse_timer_read(uint64_t offset, uint64_t data, unsigned size) "SSE system timer read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
|
||||
sse_timer_write(uint64_t offset, uint64_t data, unsigned size) "SSE system timer write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
|
||||
sse_timer_reset(void) "SSE system timer: reset"
|
||||
|
|
|
@ -310,7 +310,7 @@ static void cmsdk_apb_watchdog_reset(DeviceState *dev)
|
|||
ptimer_transaction_commit(s->timer);
|
||||
}
|
||||
|
||||
static void cmsdk_apb_watchdog_clk_update(void *opaque)
|
||||
static void cmsdk_apb_watchdog_clk_update(void *opaque, ClockEvent event)
|
||||
{
|
||||
CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(opaque);
|
||||
|
||||
|
@ -329,7 +329,8 @@ static void cmsdk_apb_watchdog_init(Object *obj)
|
|||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
sysbus_init_irq(sbd, &s->wdogint);
|
||||
s->wdogclk = qdev_init_clock_in(DEVICE(s), "WDOGCLK",
|
||||
cmsdk_apb_watchdog_clk_update, s);
|
||||
cmsdk_apb_watchdog_clk_update, s,
|
||||
ClockUpdate);
|
||||
|
||||
s->is_luminary = false;
|
||||
s->id = cmsdk_apb_watchdog_id;
|
||||
|
|
42
include/hw/arm/armsse-version.h
Normal file
42
include/hw/arm/armsse-version.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* ARM SSE (Subsystems for Embedded): IoTKit, SSE-200
|
||||
*
|
||||
* Copyright (c) 2020 Linaro Limited
|
||||
* Written by Peter Maydell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef ARMSSE_VERSION_H
|
||||
#define ARMSSE_VERSION_H
|
||||
|
||||
|
||||
/*
|
||||
* Define an enumeration of the possible values of the sse-version
|
||||
* property implemented by various sub-devices of the SSE, and
|
||||
* a validation function that checks that a valid value has been passed.
|
||||
* These are arbitrary QEMU-internal values (nobody should be creating
|
||||
* the sub-devices of the SSE except for the SSE object itself), but
|
||||
* we pick obvious numbers for the benefit of people debugging with gdb.
|
||||
*/
|
||||
enum {
|
||||
ARMSSE_IOTKIT = 0,
|
||||
ARMSSE_SSE200 = 200,
|
||||
ARMSSE_SSE300 = 300,
|
||||
};
|
||||
|
||||
static inline bool armsse_version_valid(uint32_t sse_version)
|
||||
{
|
||||
switch (sse_version) {
|
||||
case ARMSSE_IOTKIT:
|
||||
case ARMSSE_SSE200:
|
||||
case ARMSSE_SSE300:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -97,11 +97,14 @@
|
|||
#include "hw/misc/tz-mpc.h"
|
||||
#include "hw/timer/cmsdk-apb-timer.h"
|
||||
#include "hw/timer/cmsdk-apb-dualtimer.h"
|
||||
#include "hw/timer/sse-counter.h"
|
||||
#include "hw/timer/sse-timer.h"
|
||||
#include "hw/watchdog/cmsdk-apb-watchdog.h"
|
||||
#include "hw/misc/iotkit-sysctl.h"
|
||||
#include "hw/misc/iotkit-sysinfo.h"
|
||||
#include "hw/misc/armsse-cpuid.h"
|
||||
#include "hw/misc/armsse-mhu.h"
|
||||
#include "hw/misc/armsse-cpu-pwrctrl.h"
|
||||
#include "hw/misc/unimp.h"
|
||||
#include "hw/or-irq.h"
|
||||
#include "hw/clock.h"
|
||||
|
@ -120,12 +123,14 @@ OBJECT_DECLARE_TYPE(ARMSSE, ARMSSEClass,
|
|||
*/
|
||||
#define TYPE_IOTKIT "iotkit"
|
||||
#define TYPE_SSE200 "sse-200"
|
||||
#define TYPE_SSE300 "sse-300"
|
||||
|
||||
/* We have an IRQ splitter and an OR gate input for each external PPC
|
||||
* and the 2 internal PPCs
|
||||
*/
|
||||
#define NUM_INTERNAL_PPCS 2
|
||||
#define NUM_EXTERNAL_PPCS (IOTS_NUM_AHB_EXP_PPC + IOTS_NUM_APB_EXP_PPC)
|
||||
#define NUM_PPCS (NUM_EXTERNAL_PPCS + 2)
|
||||
#define NUM_PPCS (NUM_EXTERNAL_PPCS + NUM_INTERNAL_PPCS)
|
||||
|
||||
#define MAX_SRAM_BANKS 4
|
||||
#if MAX_SRAM_BANKS > IOTS_NUM_MPC
|
||||
|
@ -134,15 +139,10 @@ OBJECT_DECLARE_TYPE(ARMSSE, ARMSSEClass,
|
|||
|
||||
#define SSE_MAX_CPUS 2
|
||||
|
||||
/* These define what each PPU in the ppu[] index is for */
|
||||
#define CPU0CORE_PPU 0
|
||||
#define CPU1CORE_PPU 1
|
||||
#define DBG_PPU 2
|
||||
#define RAM0_PPU 3
|
||||
#define RAM1_PPU 4
|
||||
#define RAM2_PPU 5
|
||||
#define RAM3_PPU 6
|
||||
#define NUM_PPUS 7
|
||||
#define NUM_PPUS 8
|
||||
|
||||
/* Number of CPU IRQs used by the SSE itself */
|
||||
#define NUM_SSE_IRQS 32
|
||||
|
||||
struct ARMSSE {
|
||||
/*< private >*/
|
||||
|
@ -152,12 +152,9 @@ struct ARMSSE {
|
|||
ARMv7MState armv7m[SSE_MAX_CPUS];
|
||||
CPUClusterState cluster[SSE_MAX_CPUS];
|
||||
IoTKitSecCtl secctl;
|
||||
TZPPC apb_ppc0;
|
||||
TZPPC apb_ppc1;
|
||||
TZPPC apb_ppc[NUM_INTERNAL_PPCS];
|
||||
TZMPC mpc[IOTS_NUM_MPC];
|
||||
CMSDKAPBTimer timer0;
|
||||
CMSDKAPBTimer timer1;
|
||||
CMSDKAPBTimer s32ktimer;
|
||||
CMSDKAPBTimer timer[3];
|
||||
qemu_or_irq ppc_irq_orgate;
|
||||
SplitIRQ sec_resp_splitter;
|
||||
SplitIRQ ppc_irq_splitter[NUM_PPCS];
|
||||
|
@ -165,24 +162,27 @@ struct ARMSSE {
|
|||
qemu_or_irq mpc_irq_orgate;
|
||||
qemu_or_irq nmi_orgate;
|
||||
|
||||
SplitIRQ cpu_irq_splitter[32];
|
||||
SplitIRQ cpu_irq_splitter[NUM_SSE_IRQS];
|
||||
|
||||
CMSDKAPBDualTimer dualtimer;
|
||||
|
||||
CMSDKAPBWatchdog s32kwatchdog;
|
||||
CMSDKAPBWatchdog nswatchdog;
|
||||
CMSDKAPBWatchdog swatchdog;
|
||||
CMSDKAPBWatchdog cmsdk_watchdog[3];
|
||||
|
||||
SSECounter sse_counter;
|
||||
SSETimer sse_timer[4];
|
||||
|
||||
IoTKitSysCtl sysctl;
|
||||
IoTKitSysCtl sysinfo;
|
||||
|
||||
ARMSSEMHU mhu[2];
|
||||
UnimplementedDeviceState ppu[NUM_PPUS];
|
||||
UnimplementedDeviceState unimp[NUM_PPUS];
|
||||
UnimplementedDeviceState cachectrl[SSE_MAX_CPUS];
|
||||
UnimplementedDeviceState cpusecctrl[SSE_MAX_CPUS];
|
||||
|
||||
ARMSSECPUID cpuid[SSE_MAX_CPUS];
|
||||
|
||||
ARMSSECPUPwrCtrl cpu_pwrctrl[SSE_MAX_CPUS];
|
||||
|
||||
/*
|
||||
* 'container' holds all devices seen by all CPUs.
|
||||
* 'cpu_container[i]' is the view that CPU i has: this has the
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "target/arm/cpu.h"
|
||||
#include "qom/object.h"
|
||||
#include "net/can_emu.h"
|
||||
#include "hw/dma/xlnx_csu_dma.h"
|
||||
|
||||
#define TYPE_XLNX_ZYNQMP "xlnx,zynqmp"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(XlnxZynqMPState, XLNX_ZYNQMP)
|
||||
|
@ -60,7 +61,8 @@ OBJECT_DECLARE_SIMPLE_TYPE(XlnxZynqMPState, XLNX_ZYNQMP)
|
|||
|
||||
#define XLNX_ZYNQMP_GIC_REGIONS 6
|
||||
|
||||
/* ZynqMP maps the ARM GIC regions (GICC, GICD ...) at consecutive 64k offsets
|
||||
/*
|
||||
* ZynqMP maps the ARM GIC regions (GICC, GICD ...) at consecutive 64k offsets
|
||||
* and under-decodes the 64k region. This mirrors the 4k regions to every 4k
|
||||
* aligned address in the 64k region. To implement each GIC region needs a
|
||||
* number of memory region aliases.
|
||||
|
@ -107,6 +109,7 @@ struct XlnxZynqMPState {
|
|||
XlnxZynqMPRTC rtc;
|
||||
XlnxZDMA gdma[XLNX_ZYNQMP_NUM_GDMA_CH];
|
||||
XlnxZDMA adma[XLNX_ZYNQMP_NUM_ADMA_CH];
|
||||
XlnxCSUDMA qspi_dma;
|
||||
|
||||
char *boot_cpu;
|
||||
ARMCPU *boot_cpu_ptr;
|
||||
|
|
|
@ -22,7 +22,18 @@
|
|||
#define TYPE_CLOCK "clock"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(Clock, CLOCK)
|
||||
|
||||
typedef void ClockCallback(void *opaque);
|
||||
/*
|
||||
* Argument to ClockCallback functions indicating why the callback
|
||||
* has been called. A mask of these values logically ORed together
|
||||
* is used to specify which events are interesting when the callback
|
||||
* is registered, so these values must all be different bit values.
|
||||
*/
|
||||
typedef enum ClockEvent {
|
||||
ClockUpdate = 1, /* Clock period has just updated */
|
||||
ClockPreUpdate = 2, /* Clock period is about to update */
|
||||
} ClockEvent;
|
||||
|
||||
typedef void ClockCallback(void *opaque, ClockEvent event);
|
||||
|
||||
/*
|
||||
* clock store a value representing the clock's period in 2^-32ns unit.
|
||||
|
@ -50,6 +61,7 @@ typedef void ClockCallback(void *opaque);
|
|||
* @canonical_path: clock path string cache (used for trace purpose)
|
||||
* @callback: called when clock changes
|
||||
* @callback_opaque: argument for @callback
|
||||
* @callback_events: mask of events when callback should be called
|
||||
* @source: source (or parent in clock tree) of the clock
|
||||
* @children: list of clocks connected to this one (it is their source)
|
||||
* @sibling: structure used to form a clock list
|
||||
|
@ -67,6 +79,7 @@ struct Clock {
|
|||
char *canonical_path;
|
||||
ClockCallback *callback;
|
||||
void *callback_opaque;
|
||||
unsigned int callback_events;
|
||||
|
||||
/* Clocks are organized in a clock tree */
|
||||
Clock *source;
|
||||
|
@ -114,10 +127,15 @@ Clock *clock_new(Object *parent, const char *name);
|
|||
* @clk: the clock to register the callback into
|
||||
* @cb: the callback function
|
||||
* @opaque: the argument to the callback
|
||||
* @events: the events the callback should be called for
|
||||
* (logical OR of ClockEvent enum values)
|
||||
*
|
||||
* Register a callback called on every clock update.
|
||||
* Note that a clock has only one callback: you cannot register
|
||||
* different callback functions for different events.
|
||||
*/
|
||||
void clock_set_callback(Clock *clk, ClockCallback *cb, void *opaque);
|
||||
void clock_set_callback(Clock *clk, ClockCallback *cb,
|
||||
void *opaque, unsigned int events);
|
||||
|
||||
/**
|
||||
* clock_clear_callback:
|
||||
|
@ -268,6 +286,47 @@ static inline uint64_t clock_ticks_to_ns(const Clock *clk, uint64_t ticks)
|
|||
return ns_low >> 32 | ns_high << 32;
|
||||
}
|
||||
|
||||
/**
|
||||
* clock_ns_to_ticks:
|
||||
* @clk: the clock to query
|
||||
* @ns: duration in nanoseconds
|
||||
*
|
||||
* Returns the number of ticks this clock would make in the given
|
||||
* number of nanoseconds. Because a clock can have a period which
|
||||
* is not a whole number of nanoseconds, it is important to use this
|
||||
* function rather than attempting to obtain a "period in nanoseconds"
|
||||
* value and then dividing the duration by that value.
|
||||
*
|
||||
* If the clock is stopped (ie it has period zero), returns 0.
|
||||
*
|
||||
* For some inputs the result could overflow a 64-bit value (because
|
||||
* the clock's period is short and the duration is long). In these
|
||||
* cases we truncate the result to a 64-bit value. This is on the
|
||||
* assumption that generally the result is going to be used to report
|
||||
* a 32-bit or 64-bit guest register value, so wrapping either cannot
|
||||
* happen or is the desired behaviour.
|
||||
*/
|
||||
static inline uint64_t clock_ns_to_ticks(const Clock *clk, uint64_t ns)
|
||||
{
|
||||
/*
|
||||
* ticks = duration_in_ns / period_in_ns
|
||||
* = ns / (period / 2^32)
|
||||
* = (ns * 2^32) / period
|
||||
* The hi, lo inputs to divu128() are (ns << 32) as a 128 bit value.
|
||||
*/
|
||||
uint64_t lo = ns << 32;
|
||||
uint64_t hi = ns >> 32;
|
||||
if (clk->period == 0) {
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* Ignore divu128() return value as we've caught div-by-zero and don't
|
||||
* need different behaviour for overflow.
|
||||
*/
|
||||
divu128(&lo, &hi, clk->period);
|
||||
return lo;
|
||||
}
|
||||
|
||||
/**
|
||||
* clock_is_enabled:
|
||||
* @clk: a clock
|
||||
|
|
52
include/hw/dma/xlnx_csu_dma.h
Normal file
52
include/hw/dma/xlnx_csu_dma.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Xilinx Platform CSU Stream DMA emulation
|
||||
*
|
||||
* This implementation is based on
|
||||
* https://github.com/Xilinx/qemu/blob/master/hw/dma/csu_stream_dma.c
|
||||
*
|
||||
* 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 or
|
||||
* (at your option) version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef XLNX_CSU_DMA_H
|
||||
#define XLNX_CSU_DMA_H
|
||||
|
||||
#define TYPE_XLNX_CSU_DMA "xlnx.csu_dma"
|
||||
|
||||
#define XLNX_CSU_DMA_R_MAX (0x2c / 4)
|
||||
|
||||
typedef struct XlnxCSUDMA {
|
||||
SysBusDevice busdev;
|
||||
MemoryRegion iomem;
|
||||
MemTxAttrs attr;
|
||||
MemoryRegion *dma_mr;
|
||||
AddressSpace *dma_as;
|
||||
qemu_irq irq;
|
||||
StreamSink *tx_dev; /* Used as generic StreamSink */
|
||||
ptimer_state *src_timer;
|
||||
|
||||
uint16_t width;
|
||||
bool is_dst;
|
||||
bool r_size_last_word;
|
||||
|
||||
StreamCanPushNotifyFn notify;
|
||||
void *notify_opaque;
|
||||
|
||||
uint32_t regs[XLNX_CSU_DMA_R_MAX];
|
||||
RegisterInfo regs_info[XLNX_CSU_DMA_R_MAX];
|
||||
} XlnxCSUDMA;
|
||||
|
||||
#define XLNX_CSU_DMA(obj) \
|
||||
OBJECT_CHECK(XlnxCSUDMA, (obj), TYPE_XLNX_CSU_DMA)
|
||||
|
||||
#endif
|
40
include/hw/misc/armsse-cpu-pwrctrl.h
Normal file
40
include/hw/misc/armsse-cpu-pwrctrl.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* ARM SSE CPU PWRCTRL register block
|
||||
*
|
||||
* Copyright (c) 2021 Linaro Limited
|
||||
* Written by Peter Maydell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is a model of the "CPU<N>_PWRCTRL block" which is part of the
|
||||
* Arm Corstone SSE-300 Example Subsystem and documented in
|
||||
* https://developer.arm.com/documentation/101773/0000
|
||||
*
|
||||
* QEMU interface:
|
||||
* + sysbus MMIO region 0: the register bank
|
||||
*/
|
||||
|
||||
#ifndef HW_MISC_ARMSSE_CPU_PWRCTRL_H
|
||||
#define HW_MISC_ARMSSE_CPU_PWRCTRL_H
|
||||
|
||||
#include "hw/sysbus.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define TYPE_ARMSSE_CPU_PWRCTRL "armsse-cpu-pwrctrl"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(ARMSSECPUPwrCtrl, ARMSSE_CPU_PWRCTRL)
|
||||
|
||||
struct ARMSSECPUPwrCtrl {
|
||||
/*< private >*/
|
||||
SysBusDevice parent_obj;
|
||||
|
||||
/*< public >*/
|
||||
MemoryRegion iomem;
|
||||
|
||||
uint32_t cpupwrcfg;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -120,6 +120,8 @@ struct IoTKitSecCtl {
|
|||
IoTKitSecCtlPPC apb[IOTS_NUM_APB_PPC];
|
||||
IoTKitSecCtlPPC apbexp[IOTS_NUM_APB_EXP_PPC];
|
||||
IoTKitSecCtlPPC ahbexp[IOTS_NUM_APB_EXP_PPC];
|
||||
|
||||
uint32_t sse_version;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -17,9 +17,8 @@
|
|||
* "system control register" blocks.
|
||||
*
|
||||
* QEMU interface:
|
||||
* + QOM property "SYS_VERSION": value of the SYS_VERSION register of the
|
||||
* system information block of the SSE
|
||||
* (used to identify whether to provide SSE-200-only registers)
|
||||
* + QOM property "sse-version": indicates which SSE version this is part of
|
||||
* (used to identify whether to provide SSE-200-only registers, etc)
|
||||
* + sysbus MMIO region 0: the system information register bank
|
||||
* + sysbus MMIO region 1: the system control register bank
|
||||
*/
|
||||
|
@ -54,19 +53,21 @@ struct IoTKitSysCtl {
|
|||
uint32_t initsvtor1;
|
||||
uint32_t nmi_enable;
|
||||
uint32_t ewctrl;
|
||||
uint32_t pwrctrl;
|
||||
uint32_t pdcm_pd_sys_sense;
|
||||
uint32_t pdcm_pd_sram0_sense;
|
||||
uint32_t pdcm_pd_sram1_sense;
|
||||
uint32_t pdcm_pd_sram2_sense;
|
||||
uint32_t pdcm_pd_sram3_sense;
|
||||
uint32_t pdcm_pd_cpu0_sense;
|
||||
uint32_t pdcm_pd_vmr0_sense;
|
||||
uint32_t pdcm_pd_vmr1_sense;
|
||||
|
||||
/* Properties */
|
||||
uint32_t sys_version;
|
||||
uint32_t sse_version;
|
||||
uint32_t cpuwait_rst;
|
||||
uint32_t initsvtor0_rst;
|
||||
uint32_t initsvtor1_rst;
|
||||
|
||||
bool is_sse200;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -38,6 +38,8 @@ struct IoTKitSysInfo {
|
|||
/* Properties */
|
||||
uint32_t sys_version;
|
||||
uint32_t sys_config;
|
||||
uint32_t sse_version;
|
||||
uint32_t iidr;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -39,10 +39,12 @@ struct MPS2FPGAIO {
|
|||
LEDState *led[MPS2FPGAIO_MAX_LEDS];
|
||||
uint32_t num_leds;
|
||||
bool has_switches;
|
||||
bool has_dbgctrl;
|
||||
|
||||
uint32_t led0;
|
||||
uint32_t prescale;
|
||||
uint32_t misc;
|
||||
uint32_t dbgctrl;
|
||||
|
||||
/* QEMU_CLOCK_VIRTUAL time at which counter and pscntr were last synced */
|
||||
int64_t pscntr_sync_ticks;
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
* @name: the name of the clock (can't be NULL).
|
||||
* @callback: optional callback to be called on update or NULL.
|
||||
* @opaque: argument for the callback
|
||||
* @events: the events the callback should be called for
|
||||
* (logical OR of ClockEvent enum values)
|
||||
* @returns: a pointer to the newly added clock
|
||||
*
|
||||
* Add an input clock to device @dev as a clock named @name.
|
||||
|
@ -29,7 +31,8 @@
|
|||
* The callback will be called with @opaque as opaque parameter.
|
||||
*/
|
||||
Clock *qdev_init_clock_in(DeviceState *dev, const char *name,
|
||||
ClockCallback *callback, void *opaque);
|
||||
ClockCallback *callback, void *opaque,
|
||||
unsigned int events);
|
||||
|
||||
/**
|
||||
* qdev_init_clock_out:
|
||||
|
@ -105,6 +108,7 @@ void qdev_finalize_clocklist(DeviceState *dev);
|
|||
* @output: indicates whether the clock is input or output
|
||||
* @callback: for inputs, optional callback to be called on clock's update
|
||||
* with device as opaque
|
||||
* @callback_events: mask of ClockEvent values for when callback is called
|
||||
* @offset: optional offset to store the ClockIn or ClockOut pointer in device
|
||||
* state structure (0 means unused)
|
||||
*/
|
||||
|
@ -112,6 +116,7 @@ struct ClockPortInitElem {
|
|||
const char *name;
|
||||
bool is_output;
|
||||
ClockCallback *callback;
|
||||
unsigned int callback_events;
|
||||
size_t offset;
|
||||
};
|
||||
|
||||
|
@ -119,10 +124,11 @@ struct ClockPortInitElem {
|
|||
(offsetof(devstate, field) + \
|
||||
type_check(Clock *, typeof_field(devstate, field)))
|
||||
|
||||
#define QDEV_CLOCK(out_not_in, devstate, field, cb) { \
|
||||
#define QDEV_CLOCK(out_not_in, devstate, field, cb, cbevents) { \
|
||||
.name = (stringify(field)), \
|
||||
.is_output = out_not_in, \
|
||||
.callback = cb, \
|
||||
.callback_events = cbevents, \
|
||||
.offset = clock_offset_value(devstate, field), \
|
||||
}
|
||||
|
||||
|
@ -133,14 +139,15 @@ struct ClockPortInitElem {
|
|||
* @field: a field in @_devstate (must be Clock*)
|
||||
* @callback: (for input only) callback (or NULL) to be called with the device
|
||||
* state as argument
|
||||
* @cbevents: (for input only) ClockEvent mask for when callback is called
|
||||
*
|
||||
* The name of the clock will be derived from @field
|
||||
*/
|
||||
#define QDEV_CLOCK_IN(devstate, field, callback) \
|
||||
QDEV_CLOCK(false, devstate, field, callback)
|
||||
#define QDEV_CLOCK_IN(devstate, field, callback, cbevents) \
|
||||
QDEV_CLOCK(false, devstate, field, callback, cbevents)
|
||||
|
||||
#define QDEV_CLOCK_OUT(devstate, field) \
|
||||
QDEV_CLOCK(true, devstate, field, NULL)
|
||||
QDEV_CLOCK(true, devstate, field, NULL, 0)
|
||||
|
||||
#define QDEV_CLOCK_END { .name = NULL }
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
typedef struct XilinxSPIPS XilinxSPIPS;
|
||||
|
||||
#define XLNX_SPIPS_R_MAX (0x100 / 4)
|
||||
#define XLNX_ZYNQMP_SPIPS_R_MAX (0x830 / 4)
|
||||
#define XLNX_ZYNQMP_SPIPS_R_MAX (0x200 / 4)
|
||||
|
||||
/* Bite off 4k chunks at a time */
|
||||
#define LQSPI_CACHE_SIZE 1024
|
||||
|
|
105
include/hw/timer/sse-counter.h
Normal file
105
include/hw/timer/sse-counter.h
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Arm SSE Subsystem System Counter
|
||||
*
|
||||
* Copyright (c) 2020 Linaro Limited
|
||||
* Written by Peter Maydell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is a model of the "System counter" which is documented in
|
||||
* the Arm SSE-123 Example Subsystem Technical Reference Manual:
|
||||
* https://developer.arm.com/documentation/101370/latest/
|
||||
*
|
||||
* QEMU interface:
|
||||
* + Clock input "CLK": clock
|
||||
* + sysbus MMIO region 0: the control register frame
|
||||
* + sysbus MMIO region 1: the status register frame
|
||||
*
|
||||
* Consumers of the system counter's timestamp, such as the SSE
|
||||
* System Timer device, can also use the APIs sse_counter_for_timestamp(),
|
||||
* sse_counter_tick_to_time() and sse_counter_register_consumer() to
|
||||
* interact with an instance of the System Counter. Generally the
|
||||
* consumer device should have a QOM link property which the board
|
||||
* code can set to the appropriate instance of the system counter.
|
||||
*/
|
||||
|
||||
#ifndef SSE_COUNTER_H
|
||||
#define SSE_COUNTER_H
|
||||
|
||||
#include "hw/sysbus.h"
|
||||
#include "qom/object.h"
|
||||
#include "qemu/notify.h"
|
||||
|
||||
#define TYPE_SSE_COUNTER "sse-counter"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(SSECounter, SSE_COUNTER)
|
||||
|
||||
struct SSECounter {
|
||||
/*< private >*/
|
||||
SysBusDevice parent_obj;
|
||||
|
||||
/*< public >*/
|
||||
MemoryRegion control_mr;
|
||||
MemoryRegion status_mr;
|
||||
Clock *clk;
|
||||
NotifierList notifier_list;
|
||||
|
||||
uint32_t cntcr;
|
||||
uint32_t cntscr0;
|
||||
|
||||
/*
|
||||
* These are used for handling clock frequency changes: they are a
|
||||
* tuple of (QEMU_CLOCK_VIRTUAL timestamp, CNTCV at that time),
|
||||
* taken when the clock frequency changes. sse_cntcv() needs them
|
||||
* to calculate the current CNTCV.
|
||||
*/
|
||||
uint64_t ns_then;
|
||||
uint64_t ticks_then;
|
||||
};
|
||||
|
||||
/*
|
||||
* These functions are the interface by which a consumer of
|
||||
* the system timestamp (such as the SSE system timer device)
|
||||
* can communicate with the SSECounter.
|
||||
*/
|
||||
|
||||
/**
|
||||
* sse_counter_for_timestamp:
|
||||
* @counter: SSECounter
|
||||
* @ns: timestamp of QEMU_CLOCK_VIRTUAL in nanoseconds
|
||||
*
|
||||
* Returns the value of the timestamp counter at the specified
|
||||
* point in time (assuming that no changes to scale factor, enable, etc
|
||||
* happen in the meantime).
|
||||
*/
|
||||
uint64_t sse_counter_for_timestamp(SSECounter *counter, uint64_t ns);
|
||||
|
||||
/**
|
||||
* sse_counter_tick_to_time:
|
||||
* @counter: SSECounter
|
||||
* @tick: tick value
|
||||
*
|
||||
* Returns the time (a QEMU_CLOCK_VIRTUAL timestamp in nanoseconds)
|
||||
* when the timestamp counter will reach the specified tick count.
|
||||
* If the counter is not currently running, returns UINT64_MAX.
|
||||
*/
|
||||
uint64_t sse_counter_tick_to_time(SSECounter *counter, uint64_t tick);
|
||||
|
||||
/**
|
||||
* sse_counter_register_consumer:
|
||||
* @counter: SSECounter
|
||||
* @notifier: Notifier which is notified on counter changes
|
||||
*
|
||||
* Registers @notifier with the SSECounter. When the counter's
|
||||
* configuration changes in a way that might invalidate information
|
||||
* previously returned via sse_counter_for_timestamp() or
|
||||
* sse_counter_tick_to_time(), the notifier will be called.
|
||||
* Devices which consume the timestamp counter can use this as
|
||||
* a cue to recalculate timer events.
|
||||
*/
|
||||
void sse_counter_register_consumer(SSECounter *counter, Notifier *notifier);
|
||||
|
||||
#endif
|
53
include/hw/timer/sse-timer.h
Normal file
53
include/hw/timer/sse-timer.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Arm SSE Subsystem System Timer
|
||||
*
|
||||
* Copyright (c) 2020 Linaro Limited
|
||||
* Written by Peter Maydell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is a model of the "System timer" which is documented in
|
||||
* the Arm SSE-123 Example Subsystem Technical Reference Manual:
|
||||
* https://developer.arm.com/documentation/101370/latest/
|
||||
*
|
||||
* QEMU interface:
|
||||
* + QOM property "counter": link property to be set to the
|
||||
* TYPE_SSE_COUNTER timestamp counter device this timer runs off
|
||||
* + sysbus MMIO region 0: the register bank
|
||||
* + sysbus IRQ 0: timer interrupt
|
||||
*/
|
||||
|
||||
#ifndef SSE_TIMER_H
|
||||
#define SSE_TIMER_H
|
||||
|
||||
#include "hw/sysbus.h"
|
||||
#include "qom/object.h"
|
||||
#include "hw/timer/sse-counter.h"
|
||||
|
||||
#define TYPE_SSE_TIMER "sse-timer"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(SSETimer, SSE_TIMER)
|
||||
|
||||
struct SSETimer {
|
||||
/*< private >*/
|
||||
SysBusDevice parent_obj;
|
||||
|
||||
/*< public >*/
|
||||
MemoryRegion iomem;
|
||||
qemu_irq irq;
|
||||
SSECounter *counter;
|
||||
QEMUTimer timer;
|
||||
Notifier counter_notifier;
|
||||
|
||||
uint32_t cntfrq;
|
||||
uint32_t cntp_ctl;
|
||||
uint64_t cntp_cval;
|
||||
uint64_t cntp_aival;
|
||||
uint32_t cntp_aival_ctl;
|
||||
uint32_t cntp_aival_reload;
|
||||
};
|
||||
|
||||
#endif
|
335
target/arm/cpu.c
335
target/arm/cpu.c
|
@ -1922,331 +1922,6 @@ static ObjectClass *arm_cpu_class_by_name(const char *cpu_model)
|
|||
return oc;
|
||||
}
|
||||
|
||||
/* CPU models. These are not needed for the AArch64 linux-user build. */
|
||||
#if !defined(CONFIG_USER_ONLY) || !defined(TARGET_AARCH64)
|
||||
|
||||
static const ARMCPRegInfo cortexa8_cp_reginfo[] = {
|
||||
{ .name = "L2LOCKDOWN", .cp = 15, .crn = 9, .crm = 0, .opc1 = 1, .opc2 = 0,
|
||||
.access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 },
|
||||
{ .name = "L2AUXCR", .cp = 15, .crn = 9, .crm = 0, .opc1 = 1, .opc2 = 2,
|
||||
.access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 },
|
||||
REGINFO_SENTINEL
|
||||
};
|
||||
|
||||
static void cortex_a8_initfn(Object *obj)
|
||||
{
|
||||
ARMCPU *cpu = ARM_CPU(obj);
|
||||
|
||||
cpu->dtb_compatible = "arm,cortex-a8";
|
||||
set_feature(&cpu->env, ARM_FEATURE_V7);
|
||||
set_feature(&cpu->env, ARM_FEATURE_NEON);
|
||||
set_feature(&cpu->env, ARM_FEATURE_THUMB2EE);
|
||||
set_feature(&cpu->env, ARM_FEATURE_DUMMY_C15_REGS);
|
||||
set_feature(&cpu->env, ARM_FEATURE_EL3);
|
||||
cpu->midr = 0x410fc080;
|
||||
cpu->reset_fpsid = 0x410330c0;
|
||||
cpu->isar.mvfr0 = 0x11110222;
|
||||
cpu->isar.mvfr1 = 0x00011111;
|
||||
cpu->ctr = 0x82048004;
|
||||
cpu->reset_sctlr = 0x00c50078;
|
||||
cpu->isar.id_pfr0 = 0x1031;
|
||||
cpu->isar.id_pfr1 = 0x11;
|
||||
cpu->isar.id_dfr0 = 0x400;
|
||||
cpu->id_afr0 = 0;
|
||||
cpu->isar.id_mmfr0 = 0x31100003;
|
||||
cpu->isar.id_mmfr1 = 0x20000000;
|
||||
cpu->isar.id_mmfr2 = 0x01202000;
|
||||
cpu->isar.id_mmfr3 = 0x11;
|
||||
cpu->isar.id_isar0 = 0x00101111;
|
||||
cpu->isar.id_isar1 = 0x12112111;
|
||||
cpu->isar.id_isar2 = 0x21232031;
|
||||
cpu->isar.id_isar3 = 0x11112131;
|
||||
cpu->isar.id_isar4 = 0x00111142;
|
||||
cpu->isar.dbgdidr = 0x15141000;
|
||||
cpu->clidr = (1 << 27) | (2 << 24) | 3;
|
||||
cpu->ccsidr[0] = 0xe007e01a; /* 16k L1 dcache. */
|
||||
cpu->ccsidr[1] = 0x2007e01a; /* 16k L1 icache. */
|
||||
cpu->ccsidr[2] = 0xf0000000; /* No L2 icache. */
|
||||
cpu->reset_auxcr = 2;
|
||||
define_arm_cp_regs(cpu, cortexa8_cp_reginfo);
|
||||
}
|
||||
|
||||
static const ARMCPRegInfo cortexa9_cp_reginfo[] = {
|
||||
/*
|
||||
* power_control should be set to maximum latency. Again,
|
||||
* default to 0 and set by private hook
|
||||
*/
|
||||
{ .name = "A9_PWRCTL", .cp = 15, .crn = 15, .crm = 0, .opc1 = 0, .opc2 = 0,
|
||||
.access = PL1_RW, .resetvalue = 0,
|
||||
.fieldoffset = offsetof(CPUARMState, cp15.c15_power_control) },
|
||||
{ .name = "A9_DIAG", .cp = 15, .crn = 15, .crm = 0, .opc1 = 0, .opc2 = 1,
|
||||
.access = PL1_RW, .resetvalue = 0,
|
||||
.fieldoffset = offsetof(CPUARMState, cp15.c15_diagnostic) },
|
||||
{ .name = "A9_PWRDIAG", .cp = 15, .crn = 15, .crm = 0, .opc1 = 0, .opc2 = 2,
|
||||
.access = PL1_RW, .resetvalue = 0,
|
||||
.fieldoffset = offsetof(CPUARMState, cp15.c15_power_diagnostic) },
|
||||
{ .name = "NEONBUSY", .cp = 15, .crn = 15, .crm = 1, .opc1 = 0, .opc2 = 0,
|
||||
.access = PL1_RW, .resetvalue = 0, .type = ARM_CP_CONST },
|
||||
/* TLB lockdown control */
|
||||
{ .name = "TLB_LOCKR", .cp = 15, .crn = 15, .crm = 4, .opc1 = 5, .opc2 = 2,
|
||||
.access = PL1_W, .resetvalue = 0, .type = ARM_CP_NOP },
|
||||
{ .name = "TLB_LOCKW", .cp = 15, .crn = 15, .crm = 4, .opc1 = 5, .opc2 = 4,
|
||||
.access = PL1_W, .resetvalue = 0, .type = ARM_CP_NOP },
|
||||
{ .name = "TLB_VA", .cp = 15, .crn = 15, .crm = 5, .opc1 = 5, .opc2 = 2,
|
||||
.access = PL1_RW, .resetvalue = 0, .type = ARM_CP_CONST },
|
||||
{ .name = "TLB_PA", .cp = 15, .crn = 15, .crm = 6, .opc1 = 5, .opc2 = 2,
|
||||
.access = PL1_RW, .resetvalue = 0, .type = ARM_CP_CONST },
|
||||
{ .name = "TLB_ATTR", .cp = 15, .crn = 15, .crm = 7, .opc1 = 5, .opc2 = 2,
|
||||
.access = PL1_RW, .resetvalue = 0, .type = ARM_CP_CONST },
|
||||
REGINFO_SENTINEL
|
||||
};
|
||||
|
||||
static void cortex_a9_initfn(Object *obj)
|
||||
{
|
||||
ARMCPU *cpu = ARM_CPU(obj);
|
||||
|
||||
cpu->dtb_compatible = "arm,cortex-a9";
|
||||
set_feature(&cpu->env, ARM_FEATURE_V7);
|
||||
set_feature(&cpu->env, ARM_FEATURE_NEON);
|
||||
set_feature(&cpu->env, ARM_FEATURE_THUMB2EE);
|
||||
set_feature(&cpu->env, ARM_FEATURE_EL3);
|
||||
/*
|
||||
* Note that A9 supports the MP extensions even for
|
||||
* A9UP and single-core A9MP (which are both different
|
||||
* and valid configurations; we don't model A9UP).
|
||||
*/
|
||||
set_feature(&cpu->env, ARM_FEATURE_V7MP);
|
||||
set_feature(&cpu->env, ARM_FEATURE_CBAR);
|
||||
cpu->midr = 0x410fc090;
|
||||
cpu->reset_fpsid = 0x41033090;
|
||||
cpu->isar.mvfr0 = 0x11110222;
|
||||
cpu->isar.mvfr1 = 0x01111111;
|
||||
cpu->ctr = 0x80038003;
|
||||
cpu->reset_sctlr = 0x00c50078;
|
||||
cpu->isar.id_pfr0 = 0x1031;
|
||||
cpu->isar.id_pfr1 = 0x11;
|
||||
cpu->isar.id_dfr0 = 0x000;
|
||||
cpu->id_afr0 = 0;
|
||||
cpu->isar.id_mmfr0 = 0x00100103;
|
||||
cpu->isar.id_mmfr1 = 0x20000000;
|
||||
cpu->isar.id_mmfr2 = 0x01230000;
|
||||
cpu->isar.id_mmfr3 = 0x00002111;
|
||||
cpu->isar.id_isar0 = 0x00101111;
|
||||
cpu->isar.id_isar1 = 0x13112111;
|
||||
cpu->isar.id_isar2 = 0x21232041;
|
||||
cpu->isar.id_isar3 = 0x11112131;
|
||||
cpu->isar.id_isar4 = 0x00111142;
|
||||
cpu->isar.dbgdidr = 0x35141000;
|
||||
cpu->clidr = (1 << 27) | (1 << 24) | 3;
|
||||
cpu->ccsidr[0] = 0xe00fe019; /* 16k L1 dcache. */
|
||||
cpu->ccsidr[1] = 0x200fe019; /* 16k L1 icache. */
|
||||
define_arm_cp_regs(cpu, cortexa9_cp_reginfo);
|
||||
}
|
||||
|
||||
#ifndef CONFIG_USER_ONLY
|
||||
static uint64_t a15_l2ctlr_read(CPUARMState *env, const ARMCPRegInfo *ri)
|
||||
{
|
||||
MachineState *ms = MACHINE(qdev_get_machine());
|
||||
|
||||
/*
|
||||
* Linux wants the number of processors from here.
|
||||
* Might as well set the interrupt-controller bit too.
|
||||
*/
|
||||
return ((ms->smp.cpus - 1) << 24) | (1 << 23);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const ARMCPRegInfo cortexa15_cp_reginfo[] = {
|
||||
#ifndef CONFIG_USER_ONLY
|
||||
{ .name = "L2CTLR", .cp = 15, .crn = 9, .crm = 0, .opc1 = 1, .opc2 = 2,
|
||||
.access = PL1_RW, .resetvalue = 0, .readfn = a15_l2ctlr_read,
|
||||
.writefn = arm_cp_write_ignore, },
|
||||
#endif
|
||||
{ .name = "L2ECTLR", .cp = 15, .crn = 9, .crm = 0, .opc1 = 1, .opc2 = 3,
|
||||
.access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 },
|
||||
REGINFO_SENTINEL
|
||||
};
|
||||
|
||||
static void cortex_a7_initfn(Object *obj)
|
||||
{
|
||||
ARMCPU *cpu = ARM_CPU(obj);
|
||||
|
||||
cpu->dtb_compatible = "arm,cortex-a7";
|
||||
set_feature(&cpu->env, ARM_FEATURE_V7VE);
|
||||
set_feature(&cpu->env, ARM_FEATURE_NEON);
|
||||
set_feature(&cpu->env, ARM_FEATURE_THUMB2EE);
|
||||
set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER);
|
||||
set_feature(&cpu->env, ARM_FEATURE_DUMMY_C15_REGS);
|
||||
set_feature(&cpu->env, ARM_FEATURE_CBAR_RO);
|
||||
set_feature(&cpu->env, ARM_FEATURE_EL2);
|
||||
set_feature(&cpu->env, ARM_FEATURE_EL3);
|
||||
set_feature(&cpu->env, ARM_FEATURE_PMU);
|
||||
cpu->kvm_target = QEMU_KVM_ARM_TARGET_CORTEX_A7;
|
||||
cpu->midr = 0x410fc075;
|
||||
cpu->reset_fpsid = 0x41023075;
|
||||
cpu->isar.mvfr0 = 0x10110222;
|
||||
cpu->isar.mvfr1 = 0x11111111;
|
||||
cpu->ctr = 0x84448003;
|
||||
cpu->reset_sctlr = 0x00c50078;
|
||||
cpu->isar.id_pfr0 = 0x00001131;
|
||||
cpu->isar.id_pfr1 = 0x00011011;
|
||||
cpu->isar.id_dfr0 = 0x02010555;
|
||||
cpu->id_afr0 = 0x00000000;
|
||||
cpu->isar.id_mmfr0 = 0x10101105;
|
||||
cpu->isar.id_mmfr1 = 0x40000000;
|
||||
cpu->isar.id_mmfr2 = 0x01240000;
|
||||
cpu->isar.id_mmfr3 = 0x02102211;
|
||||
/*
|
||||
* a7_mpcore_r0p5_trm, page 4-4 gives 0x01101110; but
|
||||
* table 4-41 gives 0x02101110, which includes the arm div insns.
|
||||
*/
|
||||
cpu->isar.id_isar0 = 0x02101110;
|
||||
cpu->isar.id_isar1 = 0x13112111;
|
||||
cpu->isar.id_isar2 = 0x21232041;
|
||||
cpu->isar.id_isar3 = 0x11112131;
|
||||
cpu->isar.id_isar4 = 0x10011142;
|
||||
cpu->isar.dbgdidr = 0x3515f005;
|
||||
cpu->clidr = 0x0a200023;
|
||||
cpu->ccsidr[0] = 0x701fe00a; /* 32K L1 dcache */
|
||||
cpu->ccsidr[1] = 0x201fe00a; /* 32K L1 icache */
|
||||
cpu->ccsidr[2] = 0x711fe07a; /* 4096K L2 unified cache */
|
||||
define_arm_cp_regs(cpu, cortexa15_cp_reginfo); /* Same as A15 */
|
||||
}
|
||||
|
||||
static void cortex_a15_initfn(Object *obj)
|
||||
{
|
||||
ARMCPU *cpu = ARM_CPU(obj);
|
||||
|
||||
cpu->dtb_compatible = "arm,cortex-a15";
|
||||
set_feature(&cpu->env, ARM_FEATURE_V7VE);
|
||||
set_feature(&cpu->env, ARM_FEATURE_NEON);
|
||||
set_feature(&cpu->env, ARM_FEATURE_THUMB2EE);
|
||||
set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER);
|
||||
set_feature(&cpu->env, ARM_FEATURE_DUMMY_C15_REGS);
|
||||
set_feature(&cpu->env, ARM_FEATURE_CBAR_RO);
|
||||
set_feature(&cpu->env, ARM_FEATURE_EL2);
|
||||
set_feature(&cpu->env, ARM_FEATURE_EL3);
|
||||
set_feature(&cpu->env, ARM_FEATURE_PMU);
|
||||
cpu->kvm_target = QEMU_KVM_ARM_TARGET_CORTEX_A15;
|
||||
cpu->midr = 0x412fc0f1;
|
||||
cpu->reset_fpsid = 0x410430f0;
|
||||
cpu->isar.mvfr0 = 0x10110222;
|
||||
cpu->isar.mvfr1 = 0x11111111;
|
||||
cpu->ctr = 0x8444c004;
|
||||
cpu->reset_sctlr = 0x00c50078;
|
||||
cpu->isar.id_pfr0 = 0x00001131;
|
||||
cpu->isar.id_pfr1 = 0x00011011;
|
||||
cpu->isar.id_dfr0 = 0x02010555;
|
||||
cpu->id_afr0 = 0x00000000;
|
||||
cpu->isar.id_mmfr0 = 0x10201105;
|
||||
cpu->isar.id_mmfr1 = 0x20000000;
|
||||
cpu->isar.id_mmfr2 = 0x01240000;
|
||||
cpu->isar.id_mmfr3 = 0x02102211;
|
||||
cpu->isar.id_isar0 = 0x02101110;
|
||||
cpu->isar.id_isar1 = 0x13112111;
|
||||
cpu->isar.id_isar2 = 0x21232041;
|
||||
cpu->isar.id_isar3 = 0x11112131;
|
||||
cpu->isar.id_isar4 = 0x10011142;
|
||||
cpu->isar.dbgdidr = 0x3515f021;
|
||||
cpu->clidr = 0x0a200023;
|
||||
cpu->ccsidr[0] = 0x701fe00a; /* 32K L1 dcache */
|
||||
cpu->ccsidr[1] = 0x201fe00a; /* 32K L1 icache */
|
||||
cpu->ccsidr[2] = 0x711fe07a; /* 4096K L2 unified cache */
|
||||
define_arm_cp_regs(cpu, cortexa15_cp_reginfo);
|
||||
}
|
||||
|
||||
#ifndef TARGET_AARCH64
|
||||
/*
|
||||
* -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, and need not care about KVM.
|
||||
*/
|
||||
static void arm_max_initfn(Object *obj)
|
||||
{
|
||||
ARMCPU *cpu = ARM_CPU(obj);
|
||||
|
||||
cortex_a15_initfn(obj);
|
||||
|
||||
/* 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;
|
||||
|
||||
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.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.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;
|
||||
|
||||
t = cpu->isar.id_pfr0;
|
||||
t = FIELD_DP32(t, ID_PFR0, DIT, 1);
|
||||
cpu->isar.id_pfr0 = t;
|
||||
|
||||
t = cpu->isar.id_pfr2;
|
||||
t = FIELD_DP32(t, ID_PFR2, SSBS, 1);
|
||||
cpu->isar.id_pfr2 = t;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !defined(CONFIG_USER_ONLY) || !defined(TARGET_AARCH64) */
|
||||
|
||||
static const ARMCPUInfo arm_cpus[] = {
|
||||
#if !defined(CONFIG_USER_ONLY) || !defined(TARGET_AARCH64)
|
||||
{ .name = "cortex-a7", .initfn = cortex_a7_initfn },
|
||||
{ .name = "cortex-a8", .initfn = cortex_a8_initfn },
|
||||
{ .name = "cortex-a9", .initfn = cortex_a9_initfn },
|
||||
{ .name = "cortex-a15", .initfn = cortex_a15_initfn },
|
||||
#ifndef TARGET_AARCH64
|
||||
{ .name = "max", .initfn = arm_max_initfn },
|
||||
#endif
|
||||
#ifdef CONFIG_USER_ONLY
|
||||
{ .name = "any", .initfn = arm_max_initfn },
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
static Property arm_cpu_properties[] = {
|
||||
DEFINE_PROP_UINT32("psci-conduit", ARMCPU, psci_conduit, 0),
|
||||
DEFINE_PROP_UINT64("midr", ARMCPU, midr, 0),
|
||||
|
@ -2390,21 +2065,11 @@ static const TypeInfo arm_cpu_type_info = {
|
|||
|
||||
static void arm_cpu_register_types(void)
|
||||
{
|
||||
const size_t cpu_count = ARRAY_SIZE(arm_cpus);
|
||||
|
||||
type_register_static(&arm_cpu_type_info);
|
||||
|
||||
#ifdef CONFIG_KVM
|
||||
type_register_static(&host_arm_cpu_type_info);
|
||||
#endif
|
||||
|
||||
if (cpu_count) {
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < cpu_count; ++i) {
|
||||
arm_cpu_register(&arm_cpus[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type_init(arm_cpu_register_types)
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
#endif /* CONFIG_TCG */
|
||||
#include "internals.h"
|
||||
#include "target/arm/idau.h"
|
||||
#if !defined(CONFIG_USER_ONLY)
|
||||
#include "hw/boards.h"
|
||||
#endif
|
||||
|
||||
/* CPU models. These are not needed for the AArch64 linux-user build. */
|
||||
#if !defined(CONFIG_USER_ONLY) || !defined(TARGET_AARCH64)
|
||||
|
@ -255,6 +258,236 @@ static void arm11mpcore_initfn(Object *obj)
|
|||
cpu->reset_auxcr = 1;
|
||||
}
|
||||
|
||||
static const ARMCPRegInfo cortexa8_cp_reginfo[] = {
|
||||
{ .name = "L2LOCKDOWN", .cp = 15, .crn = 9, .crm = 0, .opc1 = 1, .opc2 = 0,
|
||||
.access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 },
|
||||
{ .name = "L2AUXCR", .cp = 15, .crn = 9, .crm = 0, .opc1 = 1, .opc2 = 2,
|
||||
.access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 },
|
||||
REGINFO_SENTINEL
|
||||
};
|
||||
|
||||
static void cortex_a8_initfn(Object *obj)
|
||||
{
|
||||
ARMCPU *cpu = ARM_CPU(obj);
|
||||
|
||||
cpu->dtb_compatible = "arm,cortex-a8";
|
||||
set_feature(&cpu->env, ARM_FEATURE_V7);
|
||||
set_feature(&cpu->env, ARM_FEATURE_NEON);
|
||||
set_feature(&cpu->env, ARM_FEATURE_THUMB2EE);
|
||||
set_feature(&cpu->env, ARM_FEATURE_DUMMY_C15_REGS);
|
||||
set_feature(&cpu->env, ARM_FEATURE_EL3);
|
||||
cpu->midr = 0x410fc080;
|
||||
cpu->reset_fpsid = 0x410330c0;
|
||||
cpu->isar.mvfr0 = 0x11110222;
|
||||
cpu->isar.mvfr1 = 0x00011111;
|
||||
cpu->ctr = 0x82048004;
|
||||
cpu->reset_sctlr = 0x00c50078;
|
||||
cpu->isar.id_pfr0 = 0x1031;
|
||||
cpu->isar.id_pfr1 = 0x11;
|
||||
cpu->isar.id_dfr0 = 0x400;
|
||||
cpu->id_afr0 = 0;
|
||||
cpu->isar.id_mmfr0 = 0x31100003;
|
||||
cpu->isar.id_mmfr1 = 0x20000000;
|
||||
cpu->isar.id_mmfr2 = 0x01202000;
|
||||
cpu->isar.id_mmfr3 = 0x11;
|
||||
cpu->isar.id_isar0 = 0x00101111;
|
||||
cpu->isar.id_isar1 = 0x12112111;
|
||||
cpu->isar.id_isar2 = 0x21232031;
|
||||
cpu->isar.id_isar3 = 0x11112131;
|
||||
cpu->isar.id_isar4 = 0x00111142;
|
||||
cpu->isar.dbgdidr = 0x15141000;
|
||||
cpu->clidr = (1 << 27) | (2 << 24) | 3;
|
||||
cpu->ccsidr[0] = 0xe007e01a; /* 16k L1 dcache. */
|
||||
cpu->ccsidr[1] = 0x2007e01a; /* 16k L1 icache. */
|
||||
cpu->ccsidr[2] = 0xf0000000; /* No L2 icache. */
|
||||
cpu->reset_auxcr = 2;
|
||||
define_arm_cp_regs(cpu, cortexa8_cp_reginfo);
|
||||
}
|
||||
|
||||
static const ARMCPRegInfo cortexa9_cp_reginfo[] = {
|
||||
/*
|
||||
* power_control should be set to maximum latency. Again,
|
||||
* default to 0 and set by private hook
|
||||
*/
|
||||
{ .name = "A9_PWRCTL", .cp = 15, .crn = 15, .crm = 0, .opc1 = 0, .opc2 = 0,
|
||||
.access = PL1_RW, .resetvalue = 0,
|
||||
.fieldoffset = offsetof(CPUARMState, cp15.c15_power_control) },
|
||||
{ .name = "A9_DIAG", .cp = 15, .crn = 15, .crm = 0, .opc1 = 0, .opc2 = 1,
|
||||
.access = PL1_RW, .resetvalue = 0,
|
||||
.fieldoffset = offsetof(CPUARMState, cp15.c15_diagnostic) },
|
||||
{ .name = "A9_PWRDIAG", .cp = 15, .crn = 15, .crm = 0, .opc1 = 0, .opc2 = 2,
|
||||
.access = PL1_RW, .resetvalue = 0,
|
||||
.fieldoffset = offsetof(CPUARMState, cp15.c15_power_diagnostic) },
|
||||
{ .name = "NEONBUSY", .cp = 15, .crn = 15, .crm = 1, .opc1 = 0, .opc2 = 0,
|
||||
.access = PL1_RW, .resetvalue = 0, .type = ARM_CP_CONST },
|
||||
/* TLB lockdown control */
|
||||
{ .name = "TLB_LOCKR", .cp = 15, .crn = 15, .crm = 4, .opc1 = 5, .opc2 = 2,
|
||||
.access = PL1_W, .resetvalue = 0, .type = ARM_CP_NOP },
|
||||
{ .name = "TLB_LOCKW", .cp = 15, .crn = 15, .crm = 4, .opc1 = 5, .opc2 = 4,
|
||||
.access = PL1_W, .resetvalue = 0, .type = ARM_CP_NOP },
|
||||
{ .name = "TLB_VA", .cp = 15, .crn = 15, .crm = 5, .opc1 = 5, .opc2 = 2,
|
||||
.access = PL1_RW, .resetvalue = 0, .type = ARM_CP_CONST },
|
||||
{ .name = "TLB_PA", .cp = 15, .crn = 15, .crm = 6, .opc1 = 5, .opc2 = 2,
|
||||
.access = PL1_RW, .resetvalue = 0, .type = ARM_CP_CONST },
|
||||
{ .name = "TLB_ATTR", .cp = 15, .crn = 15, .crm = 7, .opc1 = 5, .opc2 = 2,
|
||||
.access = PL1_RW, .resetvalue = 0, .type = ARM_CP_CONST },
|
||||
REGINFO_SENTINEL
|
||||
};
|
||||
|
||||
static void cortex_a9_initfn(Object *obj)
|
||||
{
|
||||
ARMCPU *cpu = ARM_CPU(obj);
|
||||
|
||||
cpu->dtb_compatible = "arm,cortex-a9";
|
||||
set_feature(&cpu->env, ARM_FEATURE_V7);
|
||||
set_feature(&cpu->env, ARM_FEATURE_NEON);
|
||||
set_feature(&cpu->env, ARM_FEATURE_THUMB2EE);
|
||||
set_feature(&cpu->env, ARM_FEATURE_EL3);
|
||||
/*
|
||||
* Note that A9 supports the MP extensions even for
|
||||
* A9UP and single-core A9MP (which are both different
|
||||
* and valid configurations; we don't model A9UP).
|
||||
*/
|
||||
set_feature(&cpu->env, ARM_FEATURE_V7MP);
|
||||
set_feature(&cpu->env, ARM_FEATURE_CBAR);
|
||||
cpu->midr = 0x410fc090;
|
||||
cpu->reset_fpsid = 0x41033090;
|
||||
cpu->isar.mvfr0 = 0x11110222;
|
||||
cpu->isar.mvfr1 = 0x01111111;
|
||||
cpu->ctr = 0x80038003;
|
||||
cpu->reset_sctlr = 0x00c50078;
|
||||
cpu->isar.id_pfr0 = 0x1031;
|
||||
cpu->isar.id_pfr1 = 0x11;
|
||||
cpu->isar.id_dfr0 = 0x000;
|
||||
cpu->id_afr0 = 0;
|
||||
cpu->isar.id_mmfr0 = 0x00100103;
|
||||
cpu->isar.id_mmfr1 = 0x20000000;
|
||||
cpu->isar.id_mmfr2 = 0x01230000;
|
||||
cpu->isar.id_mmfr3 = 0x00002111;
|
||||
cpu->isar.id_isar0 = 0x00101111;
|
||||
cpu->isar.id_isar1 = 0x13112111;
|
||||
cpu->isar.id_isar2 = 0x21232041;
|
||||
cpu->isar.id_isar3 = 0x11112131;
|
||||
cpu->isar.id_isar4 = 0x00111142;
|
||||
cpu->isar.dbgdidr = 0x35141000;
|
||||
cpu->clidr = (1 << 27) | (1 << 24) | 3;
|
||||
cpu->ccsidr[0] = 0xe00fe019; /* 16k L1 dcache. */
|
||||
cpu->ccsidr[1] = 0x200fe019; /* 16k L1 icache. */
|
||||
define_arm_cp_regs(cpu, cortexa9_cp_reginfo);
|
||||
}
|
||||
|
||||
#ifndef CONFIG_USER_ONLY
|
||||
static uint64_t a15_l2ctlr_read(CPUARMState *env, const ARMCPRegInfo *ri)
|
||||
{
|
||||
MachineState *ms = MACHINE(qdev_get_machine());
|
||||
|
||||
/*
|
||||
* Linux wants the number of processors from here.
|
||||
* Might as well set the interrupt-controller bit too.
|
||||
*/
|
||||
return ((ms->smp.cpus - 1) << 24) | (1 << 23);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const ARMCPRegInfo cortexa15_cp_reginfo[] = {
|
||||
#ifndef CONFIG_USER_ONLY
|
||||
{ .name = "L2CTLR", .cp = 15, .crn = 9, .crm = 0, .opc1 = 1, .opc2 = 2,
|
||||
.access = PL1_RW, .resetvalue = 0, .readfn = a15_l2ctlr_read,
|
||||
.writefn = arm_cp_write_ignore, },
|
||||
#endif
|
||||
{ .name = "L2ECTLR", .cp = 15, .crn = 9, .crm = 0, .opc1 = 1, .opc2 = 3,
|
||||
.access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 },
|
||||
REGINFO_SENTINEL
|
||||
};
|
||||
|
||||
static void cortex_a7_initfn(Object *obj)
|
||||
{
|
||||
ARMCPU *cpu = ARM_CPU(obj);
|
||||
|
||||
cpu->dtb_compatible = "arm,cortex-a7";
|
||||
set_feature(&cpu->env, ARM_FEATURE_V7VE);
|
||||
set_feature(&cpu->env, ARM_FEATURE_NEON);
|
||||
set_feature(&cpu->env, ARM_FEATURE_THUMB2EE);
|
||||
set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER);
|
||||
set_feature(&cpu->env, ARM_FEATURE_DUMMY_C15_REGS);
|
||||
set_feature(&cpu->env, ARM_FEATURE_CBAR_RO);
|
||||
set_feature(&cpu->env, ARM_FEATURE_EL2);
|
||||
set_feature(&cpu->env, ARM_FEATURE_EL3);
|
||||
set_feature(&cpu->env, ARM_FEATURE_PMU);
|
||||
cpu->kvm_target = QEMU_KVM_ARM_TARGET_CORTEX_A7;
|
||||
cpu->midr = 0x410fc075;
|
||||
cpu->reset_fpsid = 0x41023075;
|
||||
cpu->isar.mvfr0 = 0x10110222;
|
||||
cpu->isar.mvfr1 = 0x11111111;
|
||||
cpu->ctr = 0x84448003;
|
||||
cpu->reset_sctlr = 0x00c50078;
|
||||
cpu->isar.id_pfr0 = 0x00001131;
|
||||
cpu->isar.id_pfr1 = 0x00011011;
|
||||
cpu->isar.id_dfr0 = 0x02010555;
|
||||
cpu->id_afr0 = 0x00000000;
|
||||
cpu->isar.id_mmfr0 = 0x10101105;
|
||||
cpu->isar.id_mmfr1 = 0x40000000;
|
||||
cpu->isar.id_mmfr2 = 0x01240000;
|
||||
cpu->isar.id_mmfr3 = 0x02102211;
|
||||
/*
|
||||
* a7_mpcore_r0p5_trm, page 4-4 gives 0x01101110; but
|
||||
* table 4-41 gives 0x02101110, which includes the arm div insns.
|
||||
*/
|
||||
cpu->isar.id_isar0 = 0x02101110;
|
||||
cpu->isar.id_isar1 = 0x13112111;
|
||||
cpu->isar.id_isar2 = 0x21232041;
|
||||
cpu->isar.id_isar3 = 0x11112131;
|
||||
cpu->isar.id_isar4 = 0x10011142;
|
||||
cpu->isar.dbgdidr = 0x3515f005;
|
||||
cpu->clidr = 0x0a200023;
|
||||
cpu->ccsidr[0] = 0x701fe00a; /* 32K L1 dcache */
|
||||
cpu->ccsidr[1] = 0x201fe00a; /* 32K L1 icache */
|
||||
cpu->ccsidr[2] = 0x711fe07a; /* 4096K L2 unified cache */
|
||||
define_arm_cp_regs(cpu, cortexa15_cp_reginfo); /* Same as A15 */
|
||||
}
|
||||
|
||||
static void cortex_a15_initfn(Object *obj)
|
||||
{
|
||||
ARMCPU *cpu = ARM_CPU(obj);
|
||||
|
||||
cpu->dtb_compatible = "arm,cortex-a15";
|
||||
set_feature(&cpu->env, ARM_FEATURE_V7VE);
|
||||
set_feature(&cpu->env, ARM_FEATURE_NEON);
|
||||
set_feature(&cpu->env, ARM_FEATURE_THUMB2EE);
|
||||
set_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER);
|
||||
set_feature(&cpu->env, ARM_FEATURE_DUMMY_C15_REGS);
|
||||
set_feature(&cpu->env, ARM_FEATURE_CBAR_RO);
|
||||
set_feature(&cpu->env, ARM_FEATURE_EL2);
|
||||
set_feature(&cpu->env, ARM_FEATURE_EL3);
|
||||
set_feature(&cpu->env, ARM_FEATURE_PMU);
|
||||
cpu->kvm_target = QEMU_KVM_ARM_TARGET_CORTEX_A15;
|
||||
cpu->midr = 0x412fc0f1;
|
||||
cpu->reset_fpsid = 0x410430f0;
|
||||
cpu->isar.mvfr0 = 0x10110222;
|
||||
cpu->isar.mvfr1 = 0x11111111;
|
||||
cpu->ctr = 0x8444c004;
|
||||
cpu->reset_sctlr = 0x00c50078;
|
||||
cpu->isar.id_pfr0 = 0x00001131;
|
||||
cpu->isar.id_pfr1 = 0x00011011;
|
||||
cpu->isar.id_dfr0 = 0x02010555;
|
||||
cpu->id_afr0 = 0x00000000;
|
||||
cpu->isar.id_mmfr0 = 0x10201105;
|
||||
cpu->isar.id_mmfr1 = 0x20000000;
|
||||
cpu->isar.id_mmfr2 = 0x01240000;
|
||||
cpu->isar.id_mmfr3 = 0x02102211;
|
||||
cpu->isar.id_isar0 = 0x02101110;
|
||||
cpu->isar.id_isar1 = 0x13112111;
|
||||
cpu->isar.id_isar2 = 0x21232041;
|
||||
cpu->isar.id_isar3 = 0x11112131;
|
||||
cpu->isar.id_isar4 = 0x10011142;
|
||||
cpu->isar.dbgdidr = 0x3515f021;
|
||||
cpu->clidr = 0x0a200023;
|
||||
cpu->ccsidr[0] = 0x701fe00a; /* 32K L1 dcache */
|
||||
cpu->ccsidr[1] = 0x201fe00a; /* 32K L1 icache */
|
||||
cpu->ccsidr[2] = 0x711fe07a; /* 4096K L2 unified cache */
|
||||
define_arm_cp_regs(cpu, cortexa15_cp_reginfo);
|
||||
}
|
||||
|
||||
static void cortex_m0_initfn(Object *obj)
|
||||
{
|
||||
ARMCPU *cpu = ARM_CPU(obj);
|
||||
|
@ -695,6 +928,81 @@ static void arm_v7m_class_init(ObjectClass *oc, void *data)
|
|||
cc->gdb_core_xml_file = "arm-m-profile.xml";
|
||||
}
|
||||
|
||||
#ifndef TARGET_AARCH64
|
||||
/*
|
||||
* -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, and need not care about KVM.
|
||||
*/
|
||||
static void arm_max_initfn(Object *obj)
|
||||
{
|
||||
ARMCPU *cpu = ARM_CPU(obj);
|
||||
|
||||
cortex_a15_initfn(obj);
|
||||
|
||||
/* 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;
|
||||
|
||||
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.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.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;
|
||||
|
||||
t = cpu->isar.id_pfr0;
|
||||
t = FIELD_DP32(t, ID_PFR0, DIT, 1);
|
||||
cpu->isar.id_pfr0 = t;
|
||||
|
||||
t = cpu->isar.id_pfr2;
|
||||
t = FIELD_DP32(t, ID_PFR2, SSBS, 1);
|
||||
cpu->isar.id_pfr2 = t;
|
||||
}
|
||||
#endif /* CONFIG_USER_ONLY */
|
||||
}
|
||||
#endif /* !TARGET_AARCH64 */
|
||||
|
||||
static const ARMCPUInfo arm_tcg_cpus[] = {
|
||||
{ .name = "arm926", .initfn = arm926_initfn },
|
||||
{ .name = "arm946", .initfn = arm946_initfn },
|
||||
|
@ -708,6 +1016,10 @@ static const ARMCPUInfo arm_tcg_cpus[] = {
|
|||
{ .name = "arm1136", .initfn = arm1136_initfn },
|
||||
{ .name = "arm1176", .initfn = arm1176_initfn },
|
||||
{ .name = "arm11mpcore", .initfn = arm11mpcore_initfn },
|
||||
{ .name = "cortex-a7", .initfn = cortex_a7_initfn },
|
||||
{ .name = "cortex-a8", .initfn = cortex_a8_initfn },
|
||||
{ .name = "cortex-a9", .initfn = cortex_a9_initfn },
|
||||
{ .name = "cortex-a15", .initfn = cortex_a15_initfn },
|
||||
{ .name = "cortex-m0", .initfn = cortex_m0_initfn,
|
||||
.class_init = arm_v7m_class_init },
|
||||
{ .name = "cortex-m3", .initfn = cortex_m3_initfn,
|
||||
|
@ -738,6 +1050,12 @@ static const ARMCPUInfo arm_tcg_cpus[] = {
|
|||
{ .name = "pxa270-b1", .initfn = pxa270b1_initfn },
|
||||
{ .name = "pxa270-c0", .initfn = pxa270c0_initfn },
|
||||
{ .name = "pxa270-c5", .initfn = pxa270c5_initfn },
|
||||
#ifndef TARGET_AARCH64
|
||||
{ .name = "max", .initfn = arm_max_initfn },
|
||||
#endif
|
||||
#ifdef CONFIG_USER_ONLY
|
||||
{ .name = "any", .initfn = arm_max_initfn },
|
||||
#endif
|
||||
};
|
||||
|
||||
static const TypeInfo idau_interface_type_info = {
|
||||
|
|
|
@ -653,7 +653,7 @@ static void mips_cpu_initfn(Object *obj)
|
|||
MIPSCPUClass *mcc = MIPS_CPU_GET_CLASS(obj);
|
||||
|
||||
cpu_set_cpustate_pointers(cpu);
|
||||
cpu->clock = qdev_init_clock_in(DEVICE(obj), "clk-in", NULL, cpu);
|
||||
cpu->clock = qdev_init_clock_in(DEVICE(obj), "clk-in", NULL, cpu, 0);
|
||||
env->cpu_model = mcc->cpu_def;
|
||||
}
|
||||
|
||||
|
|
|
@ -157,6 +157,7 @@ qtests_npcm7xx = \
|
|||
'npcm7xx_watchdog_timer-test'] + \
|
||||
(slirp.found() ? ['npcm7xx_emc-test'] : [])
|
||||
qtests_arm = \
|
||||
(config_all_devices.has_key('CONFIG_MPS2') ? ['sse-timer-test'] : []) + \
|
||||
(config_all_devices.has_key('CONFIG_CMSDK_APB_DUALTIMER') ? ['cmsdk-apb-dualtimer-test'] : []) + \
|
||||
(config_all_devices.has_key('CONFIG_CMSDK_APB_TIMER') ? ['cmsdk-apb-timer-test'] : []) + \
|
||||
(config_all_devices.has_key('CONFIG_CMSDK_APB_WATCHDOG') ? ['cmsdk-apb-watchdog-test'] : []) + \
|
||||
|
|
240
tests/qtest/sse-timer-test.c
Normal file
240
tests/qtest/sse-timer-test.c
Normal file
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* QTest testcase for the SSE timer device
|
||||
*
|
||||
* Copyright (c) 2021 Linaro Limited
|
||||
*
|
||||
* 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 "libqtest-single.h"
|
||||
|
||||
/*
|
||||
* SSE-123/SSE-300 timer in the mps3-an547 board, where it is driven
|
||||
* at 32MHz, so 31.25ns per tick.
|
||||
*/
|
||||
#define TIMER_BASE 0x48000000
|
||||
|
||||
/* PERIPHNSPPC0 register in the SSE-300 Secure Access Configuration block */
|
||||
#define PERIPHNSPPC0 (0x50080000 + 0x70)
|
||||
|
||||
/* Base of the System Counter control frame */
|
||||
#define COUNTER_BASE 0x58100000
|
||||
|
||||
/* SSE counter register offsets in the control frame */
|
||||
#define CNTCR 0
|
||||
#define CNTSR 0x4
|
||||
#define CNTCV_LO 0x8
|
||||
#define CNTCV_HI 0xc
|
||||
#define CNTSCR 0x10
|
||||
|
||||
/* SSE timer register offsets */
|
||||
#define CNTPCT_LO 0
|
||||
#define CNTPCT_HI 4
|
||||
#define CNTFRQ 0x10
|
||||
#define CNTP_CVAL_LO 0x20
|
||||
#define CNTP_CVAL_HI 0x24
|
||||
#define CNTP_TVAL 0x28
|
||||
#define CNTP_CTL 0x2c
|
||||
#define CNTP_AIVAL_LO 0x40
|
||||
#define CNTP_AIVAL_HI 0x44
|
||||
#define CNTP_AIVAL_RELOAD 0x48
|
||||
#define CNTP_AIVAL_CTL 0x4c
|
||||
|
||||
/* 4 ticks in nanoseconds (so we can work in integers) */
|
||||
#define FOUR_TICKS 125
|
||||
|
||||
static void clock_step_ticks(uint64_t ticks)
|
||||
{
|
||||
/*
|
||||
* Advance the qtest clock by however many nanoseconds we
|
||||
* need to move the timer forward the specified number of ticks.
|
||||
* ticks must be a multiple of 4, so we get a whole number of ns.
|
||||
*/
|
||||
assert(!(ticks & 3));
|
||||
clock_step(FOUR_TICKS * (ticks >> 2));
|
||||
}
|
||||
|
||||
static void reset_counter_and_timer(void)
|
||||
{
|
||||
/*
|
||||
* Reset the system counter and the timer between tests. This
|
||||
* isn't a full reset, but it's sufficient for what the tests check.
|
||||
*/
|
||||
writel(COUNTER_BASE + CNTCR, 0);
|
||||
writel(TIMER_BASE + CNTP_CTL, 0);
|
||||
writel(TIMER_BASE + CNTP_AIVAL_CTL, 0);
|
||||
writel(COUNTER_BASE + CNTCV_LO, 0);
|
||||
writel(COUNTER_BASE + CNTCV_HI, 0);
|
||||
}
|
||||
|
||||
static void test_counter(void)
|
||||
{
|
||||
/* Basic counter functionality test */
|
||||
|
||||
reset_counter_and_timer();
|
||||
/* The counter should start disabled: check that it doesn't move */
|
||||
clock_step_ticks(100);
|
||||
g_assert_cmpuint(readl(COUNTER_BASE + CNTCV_LO), ==, 0);
|
||||
g_assert_cmpuint(readl(COUNTER_BASE + CNTCV_HI), ==, 0);
|
||||
/* Now enable it and check that it does count */
|
||||
writel(COUNTER_BASE + CNTCR, 1);
|
||||
clock_step_ticks(100);
|
||||
g_assert_cmpuint(readl(COUNTER_BASE + CNTCV_LO), ==, 100);
|
||||
g_assert_cmpuint(readl(COUNTER_BASE + CNTCV_HI), ==, 0);
|
||||
/* Check the counter scaling functionality */
|
||||
writel(COUNTER_BASE + CNTCR, 0);
|
||||
writel(COUNTER_BASE + CNTSCR, 0x00100000); /* 1/16th normal speed */
|
||||
writel(COUNTER_BASE + CNTCR, 5); /* EN, SCEN */
|
||||
clock_step_ticks(160);
|
||||
g_assert_cmpuint(readl(COUNTER_BASE + CNTCV_LO), ==, 110);
|
||||
g_assert_cmpuint(readl(COUNTER_BASE + CNTCV_HI), ==, 0);
|
||||
}
|
||||
|
||||
static void test_timer(void)
|
||||
{
|
||||
/* Basic timer functionality test */
|
||||
|
||||
reset_counter_and_timer();
|
||||
/*
|
||||
* The timer is behind a Peripheral Protection Controller, and
|
||||
* qtest accesses are always non-secure (no memory attributes),
|
||||
* so we must program the PPC to accept NS transactions. TIMER0
|
||||
* is on port 0 of PPC0, controlled by bit 0 of this register.
|
||||
*/
|
||||
writel(PERIPHNSPPC0, 1);
|
||||
/* We must enable the System Counter or the timer won't run. */
|
||||
writel(COUNTER_BASE + CNTCR, 1);
|
||||
|
||||
/* Timer starts disabled and with a counter of 0 */
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 0);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTPCT_LO), ==, 0);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTPCT_HI), ==, 0);
|
||||
|
||||
/* Turn it on */
|
||||
writel(TIMER_BASE + CNTP_CTL, 1);
|
||||
|
||||
/* Is the timer ticking? */
|
||||
clock_step_ticks(100);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTPCT_LO), ==, 100);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTPCT_HI), ==, 0);
|
||||
|
||||
/* Set the CompareValue to 4000 ticks */
|
||||
writel(TIMER_BASE + CNTP_CVAL_LO, 4000);
|
||||
writel(TIMER_BASE + CNTP_CVAL_HI, 0);
|
||||
|
||||
/* Check TVAL view of the counter */
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_TVAL), ==, 3900);
|
||||
|
||||
/* Advance to the CompareValue mark and check ISTATUS is set */
|
||||
clock_step_ticks(3900);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_TVAL), ==, 0);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 5);
|
||||
|
||||
/* Now exercise the auto-reload part of the timer */
|
||||
writel(TIMER_BASE + CNTP_AIVAL_RELOAD, 200);
|
||||
writel(TIMER_BASE + CNTP_AIVAL_CTL, 1);
|
||||
|
||||
/* Check AIVAL was reloaded and that ISTATUS is now clear */
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_AIVAL_LO), ==, 4200);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_AIVAL_HI), ==, 0);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 1);
|
||||
|
||||
/*
|
||||
* Check that when we advance forward to the reload time the interrupt
|
||||
* fires and the value reloads
|
||||
*/
|
||||
clock_step_ticks(100);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 1);
|
||||
clock_step_ticks(100);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 5);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_AIVAL_LO), ==, 4400);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_AIVAL_HI), ==, 0);
|
||||
|
||||
clock_step_ticks(100);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 5);
|
||||
/* Check that writing 0 to CLR clears the interrupt */
|
||||
writel(TIMER_BASE + CNTP_AIVAL_CTL, 1);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 1);
|
||||
/* Check that when we move forward to the reload time it fires again */
|
||||
clock_step_ticks(100);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 5);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_AIVAL_LO), ==, 4600);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_AIVAL_HI), ==, 0);
|
||||
|
||||
/*
|
||||
* Step the clock far enough that we overflow the low half of the
|
||||
* CNTPCT and AIVAL registers, and check that their high halves
|
||||
* give the right values. We do the forward movement in
|
||||
* non-autoinc mode because otherwise it takes forever as the
|
||||
* timer has to emulate all the 'reload at t + N, t + 2N, etc'
|
||||
* steps.
|
||||
*/
|
||||
writel(TIMER_BASE + CNTP_AIVAL_CTL, 0);
|
||||
clock_step_ticks(0x42ULL << 32);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTPCT_LO), ==, 4400);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTPCT_HI), ==, 0x42);
|
||||
|
||||
/* Turn on the autoinc again to check AIVAL_HI */
|
||||
writel(TIMER_BASE + CNTP_AIVAL_CTL, 1);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_AIVAL_LO), ==, 4600);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_AIVAL_HI), ==, 0x42);
|
||||
}
|
||||
|
||||
static void test_timer_scale_change(void)
|
||||
{
|
||||
/*
|
||||
* Test that the timer responds correctly to counter
|
||||
* scaling changes while it has an active timer.
|
||||
*/
|
||||
reset_counter_and_timer();
|
||||
/* Give ourselves access to the timer, and enable the counter and timer */
|
||||
writel(PERIPHNSPPC0, 1);
|
||||
writel(COUNTER_BASE + CNTCR, 1);
|
||||
writel(TIMER_BASE + CNTP_CTL, 1);
|
||||
/* Set the CompareValue to 4000 ticks */
|
||||
writel(TIMER_BASE + CNTP_CVAL_LO, 4000);
|
||||
writel(TIMER_BASE + CNTP_CVAL_HI, 0);
|
||||
/* Advance halfway and check ISTATUS is not set */
|
||||
clock_step_ticks(2000);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 1);
|
||||
/* Reprogram the counter to run at 1/16th speed */
|
||||
writel(COUNTER_BASE + CNTCR, 0);
|
||||
writel(COUNTER_BASE + CNTSCR, 0x00100000); /* 1/16th normal speed */
|
||||
writel(COUNTER_BASE + CNTCR, 5); /* EN, SCEN */
|
||||
/* Advance to where the timer would have fired and check it has not */
|
||||
clock_step_ticks(2000);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 1);
|
||||
/* Advance to where the timer must fire at the new clock rate */
|
||||
clock_step_ticks(29996);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 1);
|
||||
clock_step_ticks(4);
|
||||
g_assert_cmpuint(readl(TIMER_BASE + CNTP_CTL), ==, 5);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int r;
|
||||
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
qtest_start("-machine mps3-an547");
|
||||
|
||||
qtest_add_func("/sse-timer/counter", test_counter);
|
||||
qtest_add_func("/sse-timer/timer", test_timer);
|
||||
qtest_add_func("/sse-timer/timer-scale-change", test_timer_scale_change);
|
||||
|
||||
r = g_test_run();
|
||||
|
||||
qtest_end();
|
||||
|
||||
return r;
|
||||
}
|
Loading…
Reference in a new issue