mirror of
https://github.com/qemu/qemu.git
synced 2024-11-27 05:43:47 +08:00
target-arm queue:
* hw/arm/omap1: Remove unused omap_uwire_attach() method * stm32f405: Add RCC device to stm32f405 SoC * arm/gicv3: add missing casts * hw/misc: Create STM32L4x5 SYSCFG clock * hw/arm: Add SPI to Allwinner A10 * hw/intc/omap_intc: Remove now-unnecessary abstract base class * hw/char/pl011: Use correct masks for IBRD and FBRD * docs/devel: Convert txt files to rST * Remove MAX111X, MAX7310, DSCM-1XXXX, pcmcia devices (used only by now-removed omap/pxa2xx boards) * vl.c: Remove pxa2xx-specific -portrait and -rotate options * dma: Fix function names in documentation * hw/arm/xilinx_zynq: Add various missing unimplemented devices -----BEGIN PGP SIGNATURE----- iQJNBAABCAA3FiEE4aXFk81BneKOgxXPPCUl7RQ2DN4FAmcOeWEZHHBldGVyLm1h eWRlbGxAbGluYXJvLm9yZwAKCRA8JSXtFDYM3jCMD/482mpT1s+mrEJFWSJJXU4G 8kr4Zj6+NafbayJ0vHTkpSbkEbPxuvDiUqmlnbI+3o11i+Z3IyiaGZbba7dyNnKl 02MdQavL0dB+eMrcFNofRRvwvsposuj2ixgwTQe6L32HSFdHerVVwuhHM/wfwyCh DKt7gPRovD/7CtwDOSpyW7cK64WK1IUlE8VEsbFdQbCPkopm55LQ2sLT4TshadpG A6xcxyLN0x/lHgCmvijB1T09LSc1nQpUEQNIokC4f1Rmy6HNgGDYY1G7GAJf99mT nWhATuuhZThiYfRbN5KQoS9tGEUduxtkGhHiOgpdXpgc3cS7RusCHoqAnibpsVh3 TgAkaRAX1d/jQ2KYR2h2jI3nh66ObhrFRT3dkzRZrIvmK9zeWUKmS9lzZ94aVfPH +MtBPwsO5OhzEABs8WpMY9V1nYaYDsFATMc1akUSaSLn1Er9Uz66NIk+J4Lob4P0 78IPvTmwvAIITiqQvkISsc37n5a2/toeaffU2hPKtQLlhyilWynEZA5YItrXSTuk gYIBxyZSbzGj/ofZ9T9C0GDLbhJp9ksNIpIqRUiHOH3z9b85r7HVZORp+COw/ZXR UGak6rpJ+XVOxVL/cPRTvZB0RbUHIZh7WLNH2G7Tfv4E4llqL81iuImHXVh/2CXO 9GWr9qbDLDYQ+BI7ipLAYg== =n2CA -----END PGP SIGNATURE----- Merge tag 'pull-target-arm-20241015-1' of https://git.linaro.org/people/pmaydell/qemu-arm into staging target-arm queue: * hw/arm/omap1: Remove unused omap_uwire_attach() method * stm32f405: Add RCC device to stm32f405 SoC * arm/gicv3: add missing casts * hw/misc: Create STM32L4x5 SYSCFG clock * hw/arm: Add SPI to Allwinner A10 * hw/intc/omap_intc: Remove now-unnecessary abstract base class * hw/char/pl011: Use correct masks for IBRD and FBRD * docs/devel: Convert txt files to rST * Remove MAX111X, MAX7310, DSCM-1XXXX, pcmcia devices (used only by now-removed omap/pxa2xx boards) * vl.c: Remove pxa2xx-specific -portrait and -rotate options * dma: Fix function names in documentation * hw/arm/xilinx_zynq: Add various missing unimplemented devices # -----BEGIN PGP SIGNATURE----- # # iQJNBAABCAA3FiEE4aXFk81BneKOgxXPPCUl7RQ2DN4FAmcOeWEZHHBldGVyLm1h # eWRlbGxAbGluYXJvLm9yZwAKCRA8JSXtFDYM3jCMD/482mpT1s+mrEJFWSJJXU4G # 8kr4Zj6+NafbayJ0vHTkpSbkEbPxuvDiUqmlnbI+3o11i+Z3IyiaGZbba7dyNnKl # 02MdQavL0dB+eMrcFNofRRvwvsposuj2ixgwTQe6L32HSFdHerVVwuhHM/wfwyCh # DKt7gPRovD/7CtwDOSpyW7cK64WK1IUlE8VEsbFdQbCPkopm55LQ2sLT4TshadpG # A6xcxyLN0x/lHgCmvijB1T09LSc1nQpUEQNIokC4f1Rmy6HNgGDYY1G7GAJf99mT # nWhATuuhZThiYfRbN5KQoS9tGEUduxtkGhHiOgpdXpgc3cS7RusCHoqAnibpsVh3 # TgAkaRAX1d/jQ2KYR2h2jI3nh66ObhrFRT3dkzRZrIvmK9zeWUKmS9lzZ94aVfPH # +MtBPwsO5OhzEABs8WpMY9V1nYaYDsFATMc1akUSaSLn1Er9Uz66NIk+J4Lob4P0 # 78IPvTmwvAIITiqQvkISsc37n5a2/toeaffU2hPKtQLlhyilWynEZA5YItrXSTuk # gYIBxyZSbzGj/ofZ9T9C0GDLbhJp9ksNIpIqRUiHOH3z9b85r7HVZORp+COw/ZXR # UGak6rpJ+XVOxVL/cPRTvZB0RbUHIZh7WLNH2G7Tfv4E4llqL81iuImHXVh/2CXO # 9GWr9qbDLDYQ+BI7ipLAYg== # =n2CA # -----END PGP SIGNATURE----- # gpg: Signature made Tue 15 Oct 2024 15:17:05 BST # gpg: using RSA key E1A5C593CD419DE28E8315CF3C2525ED14360CDE # gpg: issuer "peter.maydell@linaro.org" # gpg: Good signature from "Peter Maydell <peter.maydell@linaro.org>" [ultimate] # gpg: aka "Peter Maydell <pmaydell@gmail.com>" [ultimate] # gpg: aka "Peter Maydell <pmaydell@chiark.greenend.org.uk>" [ultimate] # gpg: aka "Peter Maydell <peter@archaic.org.uk>" [ultimate] # Primary key fingerprint: E1A5 C593 CD41 9DE2 8E83 15CF 3C25 25ED 1436 0CDE * tag 'pull-target-arm-20241015-1' of https://git.linaro.org/people/pmaydell/qemu-arm: (28 commits) hw/arm/xilinx_zynq: Add various missing unimplemented devices dma: Fix function names in documentation vl.c: Remove pxa2xx-specific -portrait and -rotate options hw/block: Remove ecc hw: Remove PCMCIA subsystem hw/ide: Remove DSCM-1XXXX microdrive device model hw/gpio: Remove MAX7310 device hw/adc: Remove MAX111X device docs/devel/lockcnt: Include kernel-doc API documentation include: Move QemuLockCnt APIs to their own header docs/devel/rcu: Convert to rST format docs/devel/multiple-iothreads: Convert to rST format docs/devel/lockcnt: Convert to rST format docs/devel/blkverify: Convert to rST format docs/devel/blkdebug: Convert to rST format hw/char/pl011: Use correct masks for IBRD and FBRD hw/intc/omap_intc: Remove now-unnecessary abstract base class hw/arm: Add SPI to Allwinner A10 hw/ssi: Allwinner A10 SPI emulation tests/qtest: Check STM32L4x5 clock connections ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
f774a67750
10
MAINTAINERS
10
MAINTAINERS
@ -1059,6 +1059,8 @@ S: Maintained
|
||||
F: hw/arm/stm32f405_soc.c
|
||||
F: hw/misc/stm32f4xx_syscfg.c
|
||||
F: hw/misc/stm32f4xx_exti.c
|
||||
F: hw/misc/stm32_rcc.c
|
||||
F: include/hw/misc/stm32_rcc.h
|
||||
|
||||
Netduino 2
|
||||
M: Alistair Francis <alistair@alistair23.me>
|
||||
@ -3056,11 +3058,13 @@ F: qapi/run-state.json
|
||||
Read, Copy, Update (RCU)
|
||||
M: Paolo Bonzini <pbonzini@redhat.com>
|
||||
S: Maintained
|
||||
F: docs/devel/lockcnt.txt
|
||||
F: docs/devel/rcu.txt
|
||||
F: docs/devel/lockcnt.rst
|
||||
F: docs/devel/rcu.rst
|
||||
F: include/qemu/rcu*.h
|
||||
F: include/qemu/lockcnt.h
|
||||
F: tests/unit/rcutorture.c
|
||||
F: tests/unit/test-rcu-*.c
|
||||
F: util/lockcnt.c
|
||||
F: util/rcu.c
|
||||
|
||||
Human Monitor (HMP)
|
||||
@ -3888,6 +3892,7 @@ M: Stefan Hajnoczi <stefanha@redhat.com>
|
||||
L: qemu-block@nongnu.org
|
||||
S: Supported
|
||||
F: block/blkverify.c
|
||||
F: docs/devel/blkverify.rst
|
||||
|
||||
bochs
|
||||
M: Stefan Hajnoczi <stefanha@redhat.com>
|
||||
@ -3965,6 +3970,7 @@ M: Hanna Reitz <hreitz@redhat.com>
|
||||
L: qemu-block@nongnu.org
|
||||
S: Supported
|
||||
F: block/blkdebug.c
|
||||
F: docs/devel/blkdebug.rst
|
||||
|
||||
vpc
|
||||
M: Kevin Wolf <kwolf@redhat.com>
|
||||
|
@ -25,6 +25,7 @@
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/lockcnt.h"
|
||||
#include "qemu/thread.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "hw/core/cpu.h"
|
||||
|
@ -532,6 +532,29 @@ security model option, or switch to ``virtiofs``. The virtiofs daemon
|
||||
``virtiofsd`` uses vhost to eliminate the high latency costs of the 9p
|
||||
``proxy`` backend.
|
||||
|
||||
``-portrait`` and ``-rotate`` (since 9.2)
|
||||
'''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
The ``-portrait`` and ``-rotate`` options were documented as only
|
||||
working with the PXA LCD device, and all the machine types using
|
||||
that display device were removed in 9.2, so these options also
|
||||
have been dropped.
|
||||
|
||||
These options were intended to simulate a mobile device being
|
||||
rotated by the user, and had three effects:
|
||||
|
||||
* the display output was rotated by 90, 180 or 270 degrees
|
||||
* the mouse/trackpad input was rotated the opposite way
|
||||
* the machine model would signal to the guest about its
|
||||
orientation
|
||||
|
||||
Of these three things, the input-rotation was coded without being
|
||||
restricted to boards which supported the full set of device-rotation
|
||||
handling, so in theory the options were usable on other machine models
|
||||
to produce an odd effect (rotating input but not display output). But
|
||||
this was never intended or documented behaviour, so we have dropped
|
||||
the options along with the machine models they were intended for.
|
||||
|
||||
User-mode emulator command line arguments
|
||||
-----------------------------------------
|
||||
|
||||
|
@ -1,162 +0,0 @@
|
||||
Block I/O error injection using blkdebug
|
||||
----------------------------------------
|
||||
Copyright (C) 2014-2015 Red Hat Inc
|
||||
|
||||
This work is licensed under the terms of the GNU GPL, version 2 or later. See
|
||||
the COPYING file in the top-level directory.
|
||||
|
||||
The blkdebug block driver is a rule-based error injection engine. It can be
|
||||
used to exercise error code paths in block drivers including ENOSPC (out of
|
||||
space) and EIO.
|
||||
|
||||
This document gives an overview of the features available in blkdebug.
|
||||
|
||||
Background
|
||||
----------
|
||||
Block drivers have many error code paths that handle I/O errors. Image formats
|
||||
are especially complex since metadata I/O errors during cluster allocation or
|
||||
while updating tables happen halfway through request processing and require
|
||||
discipline to keep image files consistent.
|
||||
|
||||
Error injection allows test cases to trigger I/O errors at specific points.
|
||||
This way, all error paths can be tested to make sure they are correct.
|
||||
|
||||
Rules
|
||||
-----
|
||||
The blkdebug block driver takes a list of "rules" that tell the error injection
|
||||
engine when to fail an I/O request.
|
||||
|
||||
Each I/O request is evaluated against the rules. If a rule matches the request
|
||||
then its "action" is executed.
|
||||
|
||||
Rules can be placed in a configuration file; the configuration file
|
||||
follows the same .ini-like format used by QEMU's -readconfig option, and
|
||||
each section of the file represents a rule.
|
||||
|
||||
The following configuration file defines a single rule:
|
||||
|
||||
$ cat blkdebug.conf
|
||||
[inject-error]
|
||||
event = "read_aio"
|
||||
errno = "28"
|
||||
|
||||
This rule fails all aio read requests with ENOSPC (28). Note that the errno
|
||||
value depends on the host. On Linux, see
|
||||
/usr/include/asm-generic/errno-base.h for errno values.
|
||||
|
||||
Invoke QEMU as follows:
|
||||
|
||||
$ qemu-system-x86_64
|
||||
-drive if=none,cache=none,file=blkdebug:blkdebug.conf:test.img,id=drive0 \
|
||||
-device virtio-blk-pci,drive=drive0,id=virtio-blk-pci0
|
||||
|
||||
Rules support the following attributes:
|
||||
|
||||
event - which type of operation to match (e.g. read_aio, write_aio,
|
||||
flush_to_os, flush_to_disk). See the "Events" section for
|
||||
information on events.
|
||||
|
||||
state - (optional) the engine must be in this state number in order for this
|
||||
rule to match. See the "State transitions" section for information
|
||||
on states.
|
||||
|
||||
errno - the numeric errno value to return when a request matches this rule.
|
||||
The errno values depend on the host since the numeric values are not
|
||||
standardized in the POSIX specification.
|
||||
|
||||
sector - (optional) a sector number that the request must overlap in order to
|
||||
match this rule
|
||||
|
||||
once - (optional, default "off") only execute this action on the first
|
||||
matching request
|
||||
|
||||
immediately - (optional, default "off") return a NULL BlockAIOCB
|
||||
pointer and fail without an errno instead. This
|
||||
exercises the code path where BlockAIOCB fails and the
|
||||
caller's BlockCompletionFunc is not invoked.
|
||||
|
||||
Events
|
||||
------
|
||||
Block drivers provide information about the type of I/O request they are about
|
||||
to make so rules can match specific types of requests. For example, the qcow2
|
||||
block driver tells blkdebug when it accesses the L1 table so rules can match
|
||||
only L1 table accesses and not other metadata or guest data requests.
|
||||
|
||||
The core events are:
|
||||
|
||||
read_aio - guest data read
|
||||
|
||||
write_aio - guest data write
|
||||
|
||||
flush_to_os - write out unwritten block driver state (e.g. cached metadata)
|
||||
|
||||
flush_to_disk - flush the host block device's disk cache
|
||||
|
||||
See qapi/block-core.json:BlkdebugEvent for the full list of events.
|
||||
You may need to grep block driver source code to understand the
|
||||
meaning of specific events.
|
||||
|
||||
State transitions
|
||||
-----------------
|
||||
There are cases where more power is needed to match a particular I/O request in
|
||||
a longer sequence of requests. For example:
|
||||
|
||||
write_aio
|
||||
flush_to_disk
|
||||
write_aio
|
||||
|
||||
How do we match the 2nd write_aio but not the first? This is where state
|
||||
transitions come in.
|
||||
|
||||
The error injection engine has an integer called the "state" that always starts
|
||||
initialized to 1. The state integer is internal to blkdebug and cannot be
|
||||
observed from outside but rules can interact with it for powerful matching
|
||||
behavior.
|
||||
|
||||
Rules can be conditional on the current state and they can transition to a new
|
||||
state.
|
||||
|
||||
When a rule's "state" attribute is non-zero then the current state must equal
|
||||
the attribute in order for the rule to match.
|
||||
|
||||
For example, to match the 2nd write_aio:
|
||||
|
||||
[set-state]
|
||||
event = "write_aio"
|
||||
state = "1"
|
||||
new_state = "2"
|
||||
|
||||
[inject-error]
|
||||
event = "write_aio"
|
||||
state = "2"
|
||||
errno = "5"
|
||||
|
||||
The first write_aio request matches the set-state rule and transitions from
|
||||
state 1 to state 2. Once state 2 has been entered, the set-state rule no
|
||||
longer matches since it requires state 1. But the inject-error rule now
|
||||
matches the next write_aio request and injects EIO (5).
|
||||
|
||||
State transition rules support the following attributes:
|
||||
|
||||
event - which type of operation to match (e.g. read_aio, write_aio,
|
||||
flush_to_os, flush_to_disk). See the "Events" section for
|
||||
information on events.
|
||||
|
||||
state - (optional) the engine must be in this state number in order for this
|
||||
rule to match
|
||||
|
||||
new_state - transition to this state number
|
||||
|
||||
Suspend and resume
|
||||
------------------
|
||||
Exercising code paths in block drivers may require specific ordering amongst
|
||||
concurrent requests. The "breakpoint" feature allows requests to be halted on
|
||||
a blkdebug event and resumed later. This makes it possible to achieve
|
||||
deterministic ordering when multiple requests are in flight.
|
||||
|
||||
Breakpoints on blkdebug events are associated with a user-defined "tag" string.
|
||||
This tag serves as an identifier by which the request can be resumed at a later
|
||||
point.
|
||||
|
||||
See the qemu-io(1) break, resume, remove_break, and wait_break commands for
|
||||
details.
|
@ -358,6 +358,12 @@ humans (for instance in debugging), use ``clock_display_freq()``,
|
||||
which returns a prettified string-representation, e.g. "33.3 MHz".
|
||||
The caller must free the string with g_free() after use.
|
||||
|
||||
It's also possible to retrieve the clock period from a QTest by
|
||||
accessing QOM property ``qtest-clock-period`` using a QMP command.
|
||||
This property is only present when the device is being run under
|
||||
the ``qtest`` accelerator; it is not available when QEMU is
|
||||
being run normally.
|
||||
|
||||
Calculating expiry deadlines
|
||||
----------------------------
|
||||
|
||||
|
@ -9,6 +9,7 @@ generated from in-code annotations to function prototypes.
|
||||
|
||||
bitops
|
||||
loads-stores
|
||||
lockcnt
|
||||
memory
|
||||
modules
|
||||
pci
|
||||
|
@ -8,6 +8,7 @@ Details about QEMU's various subsystems including how to add features to them.
|
||||
|
||||
qom
|
||||
atomics
|
||||
rcu
|
||||
block-coroutine-wrapper
|
||||
clocks
|
||||
ebpf_rss
|
||||
@ -21,3 +22,4 @@ Details about QEMU's various subsystems including how to add features to them.
|
||||
writing-monitor-commands
|
||||
virtio-backends
|
||||
crypto
|
||||
multiple-iothreads
|
||||
|
@ -1,9 +1,9 @@
|
||||
DOCUMENTATION FOR LOCKED COUNTERS (aka QemuLockCnt)
|
||||
===================================================
|
||||
Locked Counters (aka ``QemuLockCnt``)
|
||||
=====================================
|
||||
|
||||
QEMU often uses reference counts to track data structures that are being
|
||||
accessed and should not be freed. For example, a loop that invoke
|
||||
callbacks like this is not safe:
|
||||
callbacks like this is not safe::
|
||||
|
||||
QLIST_FOREACH_SAFE(ioh, &io_handlers, next, pioh) {
|
||||
if (ioh->revents & G_IO_OUT) {
|
||||
@ -11,11 +11,11 @@ callbacks like this is not safe:
|
||||
}
|
||||
}
|
||||
|
||||
QLIST_FOREACH_SAFE protects against deletion of the current node (ioh)
|
||||
by stashing away its "next" pointer. However, ioh->fd_write could
|
||||
``QLIST_FOREACH_SAFE`` protects against deletion of the current node (``ioh``)
|
||||
by stashing away its ``next`` pointer. However, ``ioh->fd_write`` could
|
||||
actually delete the next node from the list. The simplest way to
|
||||
avoid this is to mark the node as deleted, and remove it from the
|
||||
list in the above loop:
|
||||
list in the above loop::
|
||||
|
||||
QLIST_FOREACH_SAFE(ioh, &io_handlers, next, pioh) {
|
||||
if (ioh->deleted) {
|
||||
@ -29,7 +29,7 @@ list in the above loop:
|
||||
}
|
||||
|
||||
If however this loop must also be reentrant, i.e. it is possible that
|
||||
ioh->fd_write invokes the loop again, some kind of counting is needed:
|
||||
``ioh->fd_write`` invokes the loop again, some kind of counting is needed::
|
||||
|
||||
walking_handlers++;
|
||||
QLIST_FOREACH_SAFE(ioh, &io_handlers, next, pioh) {
|
||||
@ -46,8 +46,8 @@ ioh->fd_write invokes the loop again, some kind of counting is needed:
|
||||
}
|
||||
walking_handlers--;
|
||||
|
||||
One may think of using the RCU primitives, rcu_read_lock() and
|
||||
rcu_read_unlock(); effectively, the RCU nesting count would take
|
||||
One may think of using the RCU primitives, ``rcu_read_lock()`` and
|
||||
``rcu_read_unlock()``; effectively, the RCU nesting count would take
|
||||
the place of the walking_handlers global variable. Indeed,
|
||||
reference counting and RCU have similar purposes, but their usage in
|
||||
general is complementary:
|
||||
@ -70,14 +70,14 @@ general is complementary:
|
||||
this can improve performance, but also delay reclamation undesirably.
|
||||
With reference counting, reclamation is deterministic.
|
||||
|
||||
This file documents QemuLockCnt, an abstraction for using reference
|
||||
This file documents ``QemuLockCnt``, an abstraction for using reference
|
||||
counting in code that has to be both thread-safe and reentrant.
|
||||
|
||||
|
||||
QemuLockCnt concepts
|
||||
--------------------
|
||||
``QemuLockCnt`` concepts
|
||||
------------------------
|
||||
|
||||
A QemuLockCnt comprises both a counter and a mutex; it has primitives
|
||||
A ``QemuLockCnt`` comprises both a counter and a mutex; it has primitives
|
||||
to increment and decrement the counter, and to take and release the
|
||||
mutex. The counter notes how many visits to the data structures are
|
||||
taking place (the visits could be from different threads, or there could
|
||||
@ -95,13 +95,14 @@ not just frees, though there could be cases where this is not necessary.
|
||||
|
||||
Reads, instead, can be done without taking the mutex, as long as the
|
||||
readers and writers use the same macros that are used for RCU, for
|
||||
example qatomic_rcu_read, qatomic_rcu_set, QLIST_FOREACH_RCU, etc. This is
|
||||
because the reads are done outside a lock and a set or QLIST_INSERT_HEAD
|
||||
example ``qatomic_rcu_read``, ``qatomic_rcu_set``, ``QLIST_FOREACH_RCU``,
|
||||
etc. This is because the reads are done outside a lock and a set
|
||||
or ``QLIST_INSERT_HEAD``
|
||||
can happen concurrently with the read. The RCU API ensures that the
|
||||
processor and the compiler see all required memory barriers.
|
||||
|
||||
This could be implemented simply by protecting the counter with the
|
||||
mutex, for example:
|
||||
mutex, for example::
|
||||
|
||||
// (1)
|
||||
qemu_mutex_lock(&walking_handlers_mutex);
|
||||
@ -125,33 +126,33 @@ mutex, for example:
|
||||
Here, no frees can happen in the code represented by the ellipsis.
|
||||
If another thread is executing critical section (2), that part of
|
||||
the code cannot be entered, because the thread will not be able
|
||||
to increment the walking_handlers variable. And of course
|
||||
to increment the ``walking_handlers`` variable. And of course
|
||||
during the visit any other thread will see a nonzero value for
|
||||
walking_handlers, as in the single-threaded code.
|
||||
``walking_handlers``, as in the single-threaded code.
|
||||
|
||||
Note that it is possible for multiple concurrent accesses to delay
|
||||
the cleanup arbitrarily; in other words, for the walking_handlers
|
||||
the cleanup arbitrarily; in other words, for the ``walking_handlers``
|
||||
counter to never become zero. For this reason, this technique is
|
||||
more easily applicable if concurrent access to the structure is rare.
|
||||
|
||||
However, critical sections are easy to forget since you have to do
|
||||
them for each modification of the counter. QemuLockCnt ensures that
|
||||
them for each modification of the counter. ``QemuLockCnt`` ensures that
|
||||
all modifications of the counter take the lock appropriately, and it
|
||||
can also be more efficient in two ways:
|
||||
|
||||
- it avoids taking the lock for many operations (for example
|
||||
incrementing the counter while it is non-zero);
|
||||
|
||||
- on some platforms, one can implement QemuLockCnt to hold the lock
|
||||
- on some platforms, one can implement ``QemuLockCnt`` to hold the lock
|
||||
and the mutex in a single word, making the fast path no more expensive
|
||||
than simply managing a counter using atomic operations (see
|
||||
docs/devel/atomics.rst). This can be very helpful if concurrent access to
|
||||
:doc:`atomics`). This can be very helpful if concurrent access to
|
||||
the data structure is expected to be rare.
|
||||
|
||||
|
||||
Using the same mutex for frees and writes can still incur some small
|
||||
inefficiencies; for example, a visit can never start if the counter is
|
||||
zero and the mutex is taken---even if the mutex is taken by a write,
|
||||
zero and the mutex is taken -- even if the mutex is taken by a write,
|
||||
which in principle need not block a visit of the data structure.
|
||||
However, these are usually not a problem if any of the following
|
||||
assumptions are valid:
|
||||
@ -163,27 +164,27 @@ assumptions are valid:
|
||||
- writes are frequent, but this kind of write (e.g. appending to a
|
||||
list) has a very small critical section.
|
||||
|
||||
For example, QEMU uses QemuLockCnt to manage an AioContext's list of
|
||||
For example, QEMU uses ``QemuLockCnt`` to manage an ``AioContext``'s list of
|
||||
bottom halves and file descriptor handlers. Modifications to the list
|
||||
of file descriptor handlers are rare. Creation of a new bottom half is
|
||||
frequent and can happen on a fast path; however: 1) it is almost never
|
||||
concurrent with a visit to the list of bottom halves; 2) it only has
|
||||
three instructions in the critical path, two assignments and a smp_wmb().
|
||||
three instructions in the critical path, two assignments and a ``smp_wmb()``.
|
||||
|
||||
|
||||
QemuLockCnt API
|
||||
---------------
|
||||
``QemuLockCnt`` API
|
||||
-------------------
|
||||
|
||||
The QemuLockCnt API is described in include/qemu/thread.h.
|
||||
.. kernel-doc:: include/qemu/lockcnt.h
|
||||
|
||||
|
||||
QemuLockCnt usage
|
||||
-----------------
|
||||
``QemuLockCnt`` usage
|
||||
---------------------
|
||||
|
||||
This section explains the typical usage patterns for QemuLockCnt functions.
|
||||
This section explains the typical usage patterns for ``QemuLockCnt`` functions.
|
||||
|
||||
Setting a variable to a non-NULL value can be done between
|
||||
qemu_lockcnt_lock and qemu_lockcnt_unlock:
|
||||
``qemu_lockcnt_lock`` and ``qemu_lockcnt_unlock``::
|
||||
|
||||
qemu_lockcnt_lock(&xyz_lockcnt);
|
||||
if (!xyz) {
|
||||
@ -193,8 +194,8 @@ qemu_lockcnt_lock and qemu_lockcnt_unlock:
|
||||
}
|
||||
qemu_lockcnt_unlock(&xyz_lockcnt);
|
||||
|
||||
Accessing the value can be done between qemu_lockcnt_inc and
|
||||
qemu_lockcnt_dec:
|
||||
Accessing the value can be done between ``qemu_lockcnt_inc`` and
|
||||
``qemu_lockcnt_dec``::
|
||||
|
||||
qemu_lockcnt_inc(&xyz_lockcnt);
|
||||
if (xyz) {
|
||||
@ -204,11 +205,11 @@ qemu_lockcnt_dec:
|
||||
}
|
||||
qemu_lockcnt_dec(&xyz_lockcnt);
|
||||
|
||||
Freeing the object can similarly use qemu_lockcnt_lock and
|
||||
qemu_lockcnt_unlock, but you also need to ensure that the count
|
||||
is zero (i.e. there is no concurrent visit). Because qemu_lockcnt_inc
|
||||
takes the QemuLockCnt's lock, the count cannot become non-zero while
|
||||
the object is being freed. Freeing an object looks like this:
|
||||
Freeing the object can similarly use ``qemu_lockcnt_lock`` and
|
||||
``qemu_lockcnt_unlock``, but you also need to ensure that the count
|
||||
is zero (i.e. there is no concurrent visit). Because ``qemu_lockcnt_inc``
|
||||
takes the ``QemuLockCnt``'s lock, the count cannot become non-zero while
|
||||
the object is being freed. Freeing an object looks like this::
|
||||
|
||||
qemu_lockcnt_lock(&xyz_lockcnt);
|
||||
if (!qemu_lockcnt_count(&xyz_lockcnt)) {
|
||||
@ -218,7 +219,7 @@ the object is being freed. Freeing an object looks like this:
|
||||
qemu_lockcnt_unlock(&xyz_lockcnt);
|
||||
|
||||
If an object has to be freed right after a visit, you can combine
|
||||
the decrement, the locking and the check on count as follows:
|
||||
the decrement, the locking and the check on count as follows::
|
||||
|
||||
qemu_lockcnt_inc(&xyz_lockcnt);
|
||||
if (xyz) {
|
||||
@ -232,7 +233,7 @@ the decrement, the locking and the check on count as follows:
|
||||
qemu_lockcnt_unlock(&xyz_lockcnt);
|
||||
}
|
||||
|
||||
QemuLockCnt can also be used to access a list as follows:
|
||||
``QemuLockCnt`` can also be used to access a list as follows::
|
||||
|
||||
qemu_lockcnt_inc(&io_handlers_lockcnt);
|
||||
QLIST_FOREACH_RCU(ioh, &io_handlers, pioh) {
|
||||
@ -252,10 +253,10 @@ QemuLockCnt can also be used to access a list as follows:
|
||||
}
|
||||
|
||||
Again, the RCU primitives are used because new items can be added to the
|
||||
list during the walk. QLIST_FOREACH_RCU ensures that the processor and
|
||||
list during the walk. ``QLIST_FOREACH_RCU`` ensures that the processor and
|
||||
the compiler see the appropriate memory barriers.
|
||||
|
||||
An alternative pattern uses qemu_lockcnt_dec_if_lock:
|
||||
An alternative pattern uses ``qemu_lockcnt_dec_if_lock``::
|
||||
|
||||
qemu_lockcnt_inc(&io_handlers_lockcnt);
|
||||
QLIST_FOREACH_SAFE_RCU(ioh, &io_handlers, next, pioh) {
|
||||
@ -273,5 +274,5 @@ An alternative pattern uses qemu_lockcnt_dec_if_lock:
|
||||
}
|
||||
qemu_lockcnt_dec(&io_handlers_lockcnt);
|
||||
|
||||
Here you can use qemu_lockcnt_dec instead of qemu_lockcnt_dec_and_lock,
|
||||
Here you can use ``qemu_lockcnt_dec`` instead of ``qemu_lockcnt_dec_and_lock``,
|
||||
because there is no special task to do if the count goes from 1 to 0.
|
139
docs/devel/multiple-iothreads.rst
Normal file
139
docs/devel/multiple-iothreads.rst
Normal file
@ -0,0 +1,139 @@
|
||||
Using Multiple ``IOThread``\ s
|
||||
==============================
|
||||
|
||||
..
|
||||
Copyright (c) 2014-2017 Red Hat Inc.
|
||||
|
||||
This work is licensed under the terms of the GNU GPL, version 2 or later. See
|
||||
the COPYING file in the top-level directory.
|
||||
|
||||
|
||||
This document explains the ``IOThread`` feature and how to write code that runs
|
||||
outside the BQL.
|
||||
|
||||
The main loop and ``IOThread``\ s
|
||||
---------------------------------
|
||||
QEMU is an event-driven program that can do several things at once using an
|
||||
event loop. The VNC server and the QMP monitor are both processed from the
|
||||
same event loop, which monitors their file descriptors until they become
|
||||
readable and then invokes a callback.
|
||||
|
||||
The default event loop is called the main loop (see ``main-loop.c``). It is
|
||||
possible to create additional event loop threads using
|
||||
``-object iothread,id=my-iothread``.
|
||||
|
||||
Side note: The main loop and ``IOThread`` are both event loops but their code is
|
||||
not shared completely. Sometimes it is useful to remember that although they
|
||||
are conceptually similar they are currently not interchangeable.
|
||||
|
||||
Why ``IOThread``\ s are useful
|
||||
------------------------------
|
||||
``IOThread``\ s allow the user to control the placement of work. The main loop is a
|
||||
scalability bottleneck on hosts with many CPUs. Work can be spread across
|
||||
several ``IOThread``\ s instead of just one main loop. When set up correctly this
|
||||
can improve I/O latency and reduce jitter seen by the guest.
|
||||
|
||||
The main loop is also deeply associated with the BQL, which is a
|
||||
scalability bottleneck in itself. vCPU threads and the main loop use the BQL
|
||||
to serialize execution of QEMU code. This mutex is necessary because a lot of
|
||||
QEMU's code historically was not thread-safe.
|
||||
|
||||
The fact that all I/O processing is done in a single main loop and that the
|
||||
BQL is contended by all vCPU threads and the main loop explain
|
||||
why it is desirable to place work into ``IOThread``\ s.
|
||||
|
||||
The experimental ``virtio-blk`` data-plane implementation has been benchmarked and
|
||||
shows these effects:
|
||||
ftp://public.dhe.ibm.com/linux/pdfs/KVM_Virtualized_IO_Performance_Paper.pdf
|
||||
|
||||
.. _how-to-program:
|
||||
|
||||
How to program for ``IOThread``\ s
|
||||
----------------------------------
|
||||
The main difference between legacy code and new code that can run in an
|
||||
``IOThread`` is dealing explicitly with the event loop object, ``AioContext``
|
||||
(see ``include/block/aio.h``). Code that only works in the main loop
|
||||
implicitly uses the main loop's ``AioContext``. Code that supports running
|
||||
in ``IOThread``\ s must be aware of its ``AioContext``.
|
||||
|
||||
AioContext supports the following services:
|
||||
* File descriptor monitoring (read/write/error on POSIX hosts)
|
||||
* Event notifiers (inter-thread signalling)
|
||||
* Timers
|
||||
* Bottom Halves (BH) deferred callbacks
|
||||
|
||||
There are several old APIs that use the main loop AioContext:
|
||||
* LEGACY ``qemu_aio_set_fd_handler()`` - monitor a file descriptor
|
||||
* LEGACY ``qemu_aio_set_event_notifier()`` - monitor an event notifier
|
||||
* LEGACY ``timer_new_ms()`` - create a timer
|
||||
* LEGACY ``qemu_bh_new()`` - create a BH
|
||||
* LEGACY ``qemu_bh_new_guarded()`` - create a BH with a device re-entrancy guard
|
||||
* LEGACY ``qemu_aio_wait()`` - run an event loop iteration
|
||||
|
||||
Since they implicitly work on the main loop they cannot be used in code that
|
||||
runs in an ``IOThread``. They might cause a crash or deadlock if called from an
|
||||
``IOThread`` since the BQL is not held.
|
||||
|
||||
Instead, use the ``AioContext`` functions directly (see ``include/block/aio.h``):
|
||||
* ``aio_set_fd_handler()`` - monitor a file descriptor
|
||||
* ``aio_set_event_notifier()`` - monitor an event notifier
|
||||
* ``aio_timer_new()`` - create a timer
|
||||
* ``aio_bh_new()`` - create a BH
|
||||
* ``aio_bh_new_guarded()`` - create a BH with a device re-entrancy guard
|
||||
* ``aio_poll()`` - run an event loop iteration
|
||||
|
||||
The ``qemu_bh_new_guarded``/``aio_bh_new_guarded`` APIs accept a
|
||||
``MemReentrancyGuard``
|
||||
argument, which is used to check for and prevent re-entrancy problems. For
|
||||
BHs associated with devices, the reentrancy-guard is contained in the
|
||||
corresponding ``DeviceState`` and named ``mem_reentrancy_guard``.
|
||||
|
||||
The ``AioContext`` can be obtained from the ``IOThread`` using
|
||||
``iothread_get_aio_context()`` or for the main loop using
|
||||
``qemu_get_aio_context()``. Code that takes an ``AioContext`` argument
|
||||
works both in ``IOThread``\ s or the main loop, depending on which ``AioContext``
|
||||
instance the caller passes in.
|
||||
|
||||
How to synchronize with an ``IOThread``
|
||||
---------------------------------------
|
||||
Variables that can be accessed by multiple threads require some form of
|
||||
synchronization such as ``qemu_mutex_lock()``, ``rcu_read_lock()``, etc.
|
||||
|
||||
``AioContext`` functions like ``aio_set_fd_handler()``,
|
||||
``aio_set_event_notifier()``, ``aio_bh_new()``, and ``aio_timer_new()``
|
||||
are thread-safe. They can be used to trigger activity in an ``IOThread``.
|
||||
|
||||
Side note: the best way to schedule a function call across threads is to call
|
||||
``aio_bh_schedule_oneshot()``.
|
||||
|
||||
The main loop thread can wait synchronously for a condition using
|
||||
``AIO_WAIT_WHILE()``.
|
||||
|
||||
``AioContext`` and the block layer
|
||||
----------------------------------
|
||||
The ``AioContext`` originates from the QEMU block layer, even though nowadays
|
||||
``AioContext`` is a generic event loop that can be used by any QEMU subsystem.
|
||||
|
||||
The block layer has support for ``AioContext`` integrated. Each
|
||||
``BlockDriverState`` is associated with an ``AioContext`` using
|
||||
``bdrv_try_change_aio_context()`` and ``bdrv_get_aio_context()``.
|
||||
This allows block layer code to process I/O inside the
|
||||
right ``AioContext``. Other subsystems may wish to follow a similar approach.
|
||||
|
||||
Block layer code must therefore expect to run in an ``IOThread`` and avoid using
|
||||
old APIs that implicitly use the main loop. See
|
||||
`How to program for IOThreads`_ for information on how to do that.
|
||||
|
||||
Code running in the monitor typically needs to ensure that past
|
||||
requests from the guest are completed. When a block device is running
|
||||
in an ``IOThread``, the ``IOThread`` can also process requests from the guest
|
||||
(via ioeventfd). To achieve both objects, wrap the code between
|
||||
``bdrv_drained_begin()`` and ``bdrv_drained_end()``, thus creating a "drained
|
||||
section".
|
||||
|
||||
Long-running jobs (usually in the form of coroutines) are often scheduled in
|
||||
the ``BlockDriverState``'s ``AioContext``. The functions
|
||||
``bdrv_add``/``remove_aio_context_notifier``, or alternatively
|
||||
``blk_add``/``remove_aio_context_notifier`` if you use ``BlockBackends``,
|
||||
can be used to get a notification whenever ``bdrv_try_change_aio_context()``
|
||||
moves a ``BlockDriverState`` to a different ``AioContext``.
|
@ -1,130 +0,0 @@
|
||||
Copyright (c) 2014-2017 Red Hat Inc.
|
||||
|
||||
This work is licensed under the terms of the GNU GPL, version 2 or later. See
|
||||
the COPYING file in the top-level directory.
|
||||
|
||||
|
||||
This document explains the IOThread feature and how to write code that runs
|
||||
outside the BQL.
|
||||
|
||||
The main loop and IOThreads
|
||||
---------------------------
|
||||
QEMU is an event-driven program that can do several things at once using an
|
||||
event loop. The VNC server and the QMP monitor are both processed from the
|
||||
same event loop, which monitors their file descriptors until they become
|
||||
readable and then invokes a callback.
|
||||
|
||||
The default event loop is called the main loop (see main-loop.c). It is
|
||||
possible to create additional event loop threads using -object
|
||||
iothread,id=my-iothread.
|
||||
|
||||
Side note: The main loop and IOThread are both event loops but their code is
|
||||
not shared completely. Sometimes it is useful to remember that although they
|
||||
are conceptually similar they are currently not interchangeable.
|
||||
|
||||
Why IOThreads are useful
|
||||
------------------------
|
||||
IOThreads allow the user to control the placement of work. The main loop is a
|
||||
scalability bottleneck on hosts with many CPUs. Work can be spread across
|
||||
several IOThreads instead of just one main loop. When set up correctly this
|
||||
can improve I/O latency and reduce jitter seen by the guest.
|
||||
|
||||
The main loop is also deeply associated with the BQL, which is a
|
||||
scalability bottleneck in itself. vCPU threads and the main loop use the BQL
|
||||
to serialize execution of QEMU code. This mutex is necessary because a lot of
|
||||
QEMU's code historically was not thread-safe.
|
||||
|
||||
The fact that all I/O processing is done in a single main loop and that the
|
||||
BQL is contended by all vCPU threads and the main loop explain
|
||||
why it is desirable to place work into IOThreads.
|
||||
|
||||
The experimental virtio-blk data-plane implementation has been benchmarked and
|
||||
shows these effects:
|
||||
ftp://public.dhe.ibm.com/linux/pdfs/KVM_Virtualized_IO_Performance_Paper.pdf
|
||||
|
||||
How to program for IOThreads
|
||||
----------------------------
|
||||
The main difference between legacy code and new code that can run in an
|
||||
IOThread is dealing explicitly with the event loop object, AioContext
|
||||
(see include/block/aio.h). Code that only works in the main loop
|
||||
implicitly uses the main loop's AioContext. Code that supports running
|
||||
in IOThreads must be aware of its AioContext.
|
||||
|
||||
AioContext supports the following services:
|
||||
* File descriptor monitoring (read/write/error on POSIX hosts)
|
||||
* Event notifiers (inter-thread signalling)
|
||||
* Timers
|
||||
* Bottom Halves (BH) deferred callbacks
|
||||
|
||||
There are several old APIs that use the main loop AioContext:
|
||||
* LEGACY qemu_aio_set_fd_handler() - monitor a file descriptor
|
||||
* LEGACY qemu_aio_set_event_notifier() - monitor an event notifier
|
||||
* LEGACY timer_new_ms() - create a timer
|
||||
* LEGACY qemu_bh_new() - create a BH
|
||||
* LEGACY qemu_bh_new_guarded() - create a BH with a device re-entrancy guard
|
||||
* LEGACY qemu_aio_wait() - run an event loop iteration
|
||||
|
||||
Since they implicitly work on the main loop they cannot be used in code that
|
||||
runs in an IOThread. They might cause a crash or deadlock if called from an
|
||||
IOThread since the BQL is not held.
|
||||
|
||||
Instead, use the AioContext functions directly (see include/block/aio.h):
|
||||
* aio_set_fd_handler() - monitor a file descriptor
|
||||
* aio_set_event_notifier() - monitor an event notifier
|
||||
* aio_timer_new() - create a timer
|
||||
* aio_bh_new() - create a BH
|
||||
* aio_bh_new_guarded() - create a BH with a device re-entrancy guard
|
||||
* aio_poll() - run an event loop iteration
|
||||
|
||||
The qemu_bh_new_guarded/aio_bh_new_guarded APIs accept a "MemReentrancyGuard"
|
||||
argument, which is used to check for and prevent re-entrancy problems. For
|
||||
BHs associated with devices, the reentrancy-guard is contained in the
|
||||
corresponding DeviceState and named "mem_reentrancy_guard".
|
||||
|
||||
The AioContext can be obtained from the IOThread using
|
||||
iothread_get_aio_context() or for the main loop using qemu_get_aio_context().
|
||||
Code that takes an AioContext argument works both in IOThreads or the main
|
||||
loop, depending on which AioContext instance the caller passes in.
|
||||
|
||||
How to synchronize with an IOThread
|
||||
-----------------------------------
|
||||
Variables that can be accessed by multiple threads require some form of
|
||||
synchronization such as qemu_mutex_lock(), rcu_read_lock(), etc.
|
||||
|
||||
AioContext functions like aio_set_fd_handler(), aio_set_event_notifier(),
|
||||
aio_bh_new(), and aio_timer_new() are thread-safe. They can be used to trigger
|
||||
activity in an IOThread.
|
||||
|
||||
Side note: the best way to schedule a function call across threads is to call
|
||||
aio_bh_schedule_oneshot().
|
||||
|
||||
The main loop thread can wait synchronously for a condition using
|
||||
AIO_WAIT_WHILE().
|
||||
|
||||
AioContext and the block layer
|
||||
------------------------------
|
||||
The AioContext originates from the QEMU block layer, even though nowadays
|
||||
AioContext is a generic event loop that can be used by any QEMU subsystem.
|
||||
|
||||
The block layer has support for AioContext integrated. Each BlockDriverState
|
||||
is associated with an AioContext using bdrv_try_change_aio_context() and
|
||||
bdrv_get_aio_context(). This allows block layer code to process I/O inside the
|
||||
right AioContext. Other subsystems may wish to follow a similar approach.
|
||||
|
||||
Block layer code must therefore expect to run in an IOThread and avoid using
|
||||
old APIs that implicitly use the main loop. See the "How to program for
|
||||
IOThreads" above for information on how to do that.
|
||||
|
||||
Code running in the monitor typically needs to ensure that past
|
||||
requests from the guest are completed. When a block device is running
|
||||
in an IOThread, the IOThread can also process requests from the guest
|
||||
(via ioeventfd). To achieve both objects, wrap the code between
|
||||
bdrv_drained_begin() and bdrv_drained_end(), thus creating a "drained
|
||||
section".
|
||||
|
||||
Long-running jobs (usually in the form of coroutines) are often scheduled in
|
||||
the BlockDriverState's AioContext. The functions
|
||||
bdrv_add/remove_aio_context_notifier, or alternatively
|
||||
blk_add/remove_aio_context_notifier if you use BlockBackends, can be used to
|
||||
get a notification whenever bdrv_try_change_aio_context() moves a
|
||||
BlockDriverState to a different AioContext.
|
@ -20,7 +20,7 @@ for the execution of all *currently running* critical sections before
|
||||
proceeding, or before asynchronously executing a callback.
|
||||
|
||||
The key point here is that only the currently running critical sections
|
||||
are waited for; critical sections that are started _after_ the beginning
|
||||
are waited for; critical sections that are started **after** the beginning
|
||||
of the wait do not extend the wait, despite running concurrently with
|
||||
the updater. This is the reason why RCU is more scalable than,
|
||||
for example, reader-writer locks. It is so much more scalable that
|
||||
@ -37,7 +37,7 @@ do not matter; as soon as all previous critical sections have finished,
|
||||
there cannot be any readers who hold references to the data structure,
|
||||
and these can now be safely reclaimed (e.g., freed or unref'ed).
|
||||
|
||||
Here is a picture:
|
||||
Here is a picture::
|
||||
|
||||
thread 1 thread 2 thread 3
|
||||
------------------- ------------------------ -------------------
|
||||
@ -58,43 +58,38 @@ that critical section.
|
||||
|
||||
|
||||
RCU API
|
||||
=======
|
||||
-------
|
||||
|
||||
The core RCU API is small:
|
||||
|
||||
void rcu_read_lock(void);
|
||||
|
||||
``void rcu_read_lock(void);``
|
||||
Used by a reader to inform the reclaimer that the reader is
|
||||
entering an RCU read-side critical section.
|
||||
|
||||
void rcu_read_unlock(void);
|
||||
|
||||
``void rcu_read_unlock(void);``
|
||||
Used by a reader to inform the reclaimer that the reader is
|
||||
exiting an RCU read-side critical section. Note that RCU
|
||||
read-side critical sections may be nested and/or overlapping.
|
||||
|
||||
void synchronize_rcu(void);
|
||||
|
||||
``void synchronize_rcu(void);``
|
||||
Blocks until all pre-existing RCU read-side critical sections
|
||||
on all threads have completed. This marks the end of the removal
|
||||
phase and the beginning of reclamation phase.
|
||||
|
||||
Note that it would be valid for another update to come while
|
||||
synchronize_rcu is running. Because of this, it is better that
|
||||
``synchronize_rcu`` is running. Because of this, it is better that
|
||||
the updater releases any locks it may hold before calling
|
||||
synchronize_rcu. If this is not possible (for example, because
|
||||
the updater is protected by the BQL), you can use call_rcu.
|
||||
``synchronize_rcu``. If this is not possible (for example, because
|
||||
the updater is protected by the BQL), you can use ``call_rcu``.
|
||||
|
||||
void call_rcu1(struct rcu_head * head,
|
||||
void (*func)(struct rcu_head *head));
|
||||
|
||||
This function invokes func(head) after all pre-existing RCU
|
||||
``void call_rcu1(struct rcu_head * head, void (*func)(struct rcu_head *head));``
|
||||
This function invokes ``func(head)`` after all pre-existing RCU
|
||||
read-side critical sections on all threads have completed. This
|
||||
marks the end of the removal phase, with func taking care
|
||||
asynchronously of the reclamation phase.
|
||||
|
||||
The foo struct needs to have an rcu_head structure added,
|
||||
perhaps as follows:
|
||||
The ``foo`` struct needs to have an ``rcu_head`` structure added,
|
||||
perhaps as follows::
|
||||
|
||||
struct foo {
|
||||
struct rcu_head rcu;
|
||||
@ -103,8 +98,8 @@ The core RCU API is small:
|
||||
long c;
|
||||
};
|
||||
|
||||
so that the reclaimer function can fetch the struct foo address
|
||||
and free it:
|
||||
so that the reclaimer function can fetch the ``struct foo`` address
|
||||
and free it::
|
||||
|
||||
call_rcu1(&foo.rcu, foo_reclaim);
|
||||
|
||||
@ -114,29 +109,27 @@ The core RCU API is small:
|
||||
g_free(fp);
|
||||
}
|
||||
|
||||
For the common case where the rcu_head member is the first of the
|
||||
struct, you can use the following macro.
|
||||
``call_rcu1`` is typically used via either the ``call_rcu`` or
|
||||
``g_free_rcu`` macros, which handle the common case where the
|
||||
``rcu_head`` member is the first of the struct.
|
||||
|
||||
void call_rcu(T *p,
|
||||
void (*func)(T *p),
|
||||
field-name);
|
||||
void g_free_rcu(T *p,
|
||||
field-name);
|
||||
``void call_rcu(T *p, void (*func)(T *p), field-name);``
|
||||
If the ``struct rcu_head`` is the first field in the struct, you can
|
||||
use this macro instead of ``call_rcu1``.
|
||||
|
||||
call_rcu1 is typically used through these macro, in the common case
|
||||
where the "struct rcu_head" is the first field in the struct. If
|
||||
the callback function is g_free, in particular, g_free_rcu can be
|
||||
used. In the above case, one could have written simply:
|
||||
``void g_free_rcu(T *p, field-name);``
|
||||
This is a special-case version of ``call_rcu`` where the callback
|
||||
function is ``g_free``.
|
||||
In the example given in ``call_rcu1``, one could have written simply::
|
||||
|
||||
g_free_rcu(&foo, rcu);
|
||||
|
||||
typeof(*p) qatomic_rcu_read(p);
|
||||
``typeof(*p) qatomic_rcu_read(p);``
|
||||
``qatomic_rcu_read()`` is similar to ``qatomic_load_acquire()``, but
|
||||
it makes some assumptions on the code that calls it. This allows a
|
||||
more optimized implementation.
|
||||
|
||||
qatomic_rcu_read() is similar to qatomic_load_acquire(), but it makes
|
||||
some assumptions on the code that calls it. This allows a more
|
||||
optimized implementation.
|
||||
|
||||
qatomic_rcu_read assumes that whenever a single RCU critical
|
||||
``qatomic_rcu_read`` assumes that whenever a single RCU critical
|
||||
section reads multiple shared data, these reads are either
|
||||
data-dependent or need no ordering. This is almost always the
|
||||
case when using RCU, because read-side critical sections typically
|
||||
@ -144,7 +137,7 @@ The core RCU API is small:
|
||||
every update) until reaching a data structure of interest,
|
||||
and then read from there.
|
||||
|
||||
RCU read-side critical sections must use qatomic_rcu_read() to
|
||||
RCU read-side critical sections must use ``qatomic_rcu_read()`` to
|
||||
read data, unless concurrent writes are prevented by another
|
||||
synchronization mechanism.
|
||||
|
||||
@ -152,18 +145,17 @@ The core RCU API is small:
|
||||
data structure in a single direction, opposite to the direction
|
||||
in which the updater initializes it.
|
||||
|
||||
void qatomic_rcu_set(p, typeof(*p) v);
|
||||
``void qatomic_rcu_set(p, typeof(*p) v);``
|
||||
``qatomic_rcu_set()`` is similar to ``qatomic_store_release()``,
|
||||
though it also makes assumptions on the code that calls it in
|
||||
order to allow a more optimized implementation.
|
||||
|
||||
qatomic_rcu_set() is similar to qatomic_store_release(), though it also
|
||||
makes assumptions on the code that calls it in order to allow a more
|
||||
optimized implementation.
|
||||
|
||||
In particular, qatomic_rcu_set() suffices for synchronization
|
||||
In particular, ``qatomic_rcu_set()`` suffices for synchronization
|
||||
with readers, if the updater never mutates a field within a
|
||||
data item that is already accessible to readers. This is the
|
||||
case when initializing a new copy of the RCU-protected data
|
||||
structure; just ensure that initialization of *p is carried out
|
||||
before qatomic_rcu_set() makes the data item visible to readers.
|
||||
structure; just ensure that initialization of ``*p`` is carried out
|
||||
before ``qatomic_rcu_set()`` makes the data item visible to readers.
|
||||
If this rule is observed, writes will happen in the opposite
|
||||
order as reads in the RCU read-side critical sections (or if
|
||||
there is just one update), and there will be no need for other
|
||||
@ -171,58 +163,54 @@ The core RCU API is small:
|
||||
|
||||
The following APIs must be used before RCU is used in a thread:
|
||||
|
||||
void rcu_register_thread(void);
|
||||
|
||||
``void rcu_register_thread(void);``
|
||||
Mark a thread as taking part in the RCU mechanism. Such a thread
|
||||
will have to report quiescent points regularly, either manually
|
||||
or through the QemuCond/QemuSemaphore/QemuEvent APIs.
|
||||
|
||||
void rcu_unregister_thread(void);
|
||||
or through the ``QemuCond``/``QemuSemaphore``/``QemuEvent`` APIs.
|
||||
|
||||
``void rcu_unregister_thread(void);``
|
||||
Mark a thread as not taking part anymore in the RCU mechanism.
|
||||
It is not a problem if such a thread reports quiescent points,
|
||||
either manually or by using the QemuCond/QemuSemaphore/QemuEvent
|
||||
APIs.
|
||||
either manually or by using the
|
||||
``QemuCond``/``QemuSemaphore``/``QemuEvent`` APIs.
|
||||
|
||||
Note that these APIs are relatively heavyweight, and should _not_ be
|
||||
Note that these APIs are relatively heavyweight, and should **not** be
|
||||
nested.
|
||||
|
||||
Convenience macros
|
||||
==================
|
||||
------------------
|
||||
|
||||
Two macros are provided that automatically release the read lock at the
|
||||
end of the scope.
|
||||
|
||||
RCU_READ_LOCK_GUARD()
|
||||
|
||||
``RCU_READ_LOCK_GUARD()``
|
||||
Takes the lock and will release it at the end of the block it's
|
||||
used in.
|
||||
|
||||
WITH_RCU_READ_LOCK_GUARD() { code }
|
||||
|
||||
``WITH_RCU_READ_LOCK_GUARD() { code }``
|
||||
Is used at the head of a block to protect the code within the block.
|
||||
|
||||
Note that 'goto'ing out of the guarded block will also drop the lock.
|
||||
Note that a ``goto`` out of the guarded block will also drop the lock.
|
||||
|
||||
DIFFERENCES WITH LINUX
|
||||
======================
|
||||
Differences with Linux
|
||||
----------------------
|
||||
|
||||
- Waiting on a mutex is possible, though discouraged, within an RCU critical
|
||||
section. This is because spinlocks are rarely (if ever) used in userspace
|
||||
programming; not allowing this would prevent upgrading an RCU read-side
|
||||
critical section to become an updater.
|
||||
|
||||
- qatomic_rcu_read and qatomic_rcu_set replace rcu_dereference and
|
||||
rcu_assign_pointer. They take a _pointer_ to the variable being accessed.
|
||||
- ``qatomic_rcu_read`` and ``qatomic_rcu_set`` replace ``rcu_dereference`` and
|
||||
``rcu_assign_pointer``. They take a **pointer** to the variable being accessed.
|
||||
|
||||
- call_rcu is a macro that has an extra argument (the name of the first
|
||||
field in the struct, which must be a struct rcu_head), and expects the
|
||||
- ``call_rcu`` is a macro that has an extra argument (the name of the first
|
||||
field in the struct, which must be a struct ``rcu_head``), and expects the
|
||||
type of the callback's argument to be the type of the first argument.
|
||||
call_rcu1 is the same as Linux's call_rcu.
|
||||
``call_rcu1`` is the same as Linux's ``call_rcu``.
|
||||
|
||||
|
||||
RCU PATTERNS
|
||||
============
|
||||
RCU Patterns
|
||||
------------
|
||||
|
||||
Many patterns using read-writer locks translate directly to RCU, with
|
||||
the advantages of higher scalability and deadlock immunity.
|
||||
@ -243,28 +231,28 @@ Here are some frequently-used RCU idioms that are worth noting.
|
||||
|
||||
|
||||
RCU list processing
|
||||
-------------------
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TBD (not yet used in QEMU)
|
||||
|
||||
|
||||
RCU reference counting
|
||||
----------------------
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Because grace periods are not allowed to complete while there is an RCU
|
||||
read-side critical section in progress, the RCU read-side primitives
|
||||
may be used as a restricted reference-counting mechanism. For example,
|
||||
consider the following code fragment:
|
||||
consider the following code fragment::
|
||||
|
||||
rcu_read_lock();
|
||||
p = qatomic_rcu_read(&foo);
|
||||
/* do something with p. */
|
||||
rcu_read_unlock();
|
||||
|
||||
The RCU read-side critical section ensures that the value of "p" remains
|
||||
valid until after the rcu_read_unlock(). In some sense, it is acquiring
|
||||
a reference to p that is later released when the critical section ends.
|
||||
The write side looks simply like this (with appropriate locking):
|
||||
The RCU read-side critical section ensures that the value of ``p`` remains
|
||||
valid until after the ``rcu_read_unlock()``. In some sense, it is acquiring
|
||||
a reference to ``p`` that is later released when the critical section ends.
|
||||
The write side looks simply like this (with appropriate locking)::
|
||||
|
||||
qemu_mutex_lock(&foo_mutex);
|
||||
old = foo;
|
||||
@ -274,7 +262,7 @@ The write side looks simply like this (with appropriate locking):
|
||||
free(old);
|
||||
|
||||
If the processing cannot be done purely within the critical section, it
|
||||
is possible to combine this idiom with a "real" reference count:
|
||||
is possible to combine this idiom with a "real" reference count::
|
||||
|
||||
rcu_read_lock();
|
||||
p = qatomic_rcu_read(&foo);
|
||||
@ -283,7 +271,7 @@ is possible to combine this idiom with a "real" reference count:
|
||||
/* do something with p. */
|
||||
foo_unref(p);
|
||||
|
||||
The write side can be like this:
|
||||
The write side can be like this::
|
||||
|
||||
qemu_mutex_lock(&foo_mutex);
|
||||
old = foo;
|
||||
@ -292,7 +280,7 @@ The write side can be like this:
|
||||
synchronize_rcu();
|
||||
foo_unref(old);
|
||||
|
||||
or with call_rcu:
|
||||
or with ``call_rcu``::
|
||||
|
||||
qemu_mutex_lock(&foo_mutex);
|
||||
old = foo;
|
||||
@ -301,10 +289,10 @@ or with call_rcu:
|
||||
call_rcu(foo_unref, old, rcu);
|
||||
|
||||
In both cases, the write side only performs removal. Reclamation
|
||||
happens when the last reference to a "foo" object is dropped.
|
||||
Using synchronize_rcu() is undesirably expensive, because the
|
||||
happens when the last reference to a ``foo`` object is dropped.
|
||||
Using ``synchronize_rcu()`` is undesirably expensive, because the
|
||||
last reference may be dropped on the read side. Hence you can
|
||||
use call_rcu() instead:
|
||||
use ``call_rcu()`` instead::
|
||||
|
||||
foo_unref(struct foo *p) {
|
||||
if (qatomic_fetch_dec(&p->refcount) == 1) {
|
||||
@ -314,7 +302,7 @@ use call_rcu() instead:
|
||||
|
||||
|
||||
Note that the same idioms would be possible with reader/writer
|
||||
locks:
|
||||
locks::
|
||||
|
||||
read_lock(&foo_rwlock); write_mutex_lock(&foo_rwlock);
|
||||
p = foo; p = foo;
|
||||
@ -334,15 +322,15 @@ locks:
|
||||
foo_unref(p);
|
||||
read_unlock(&foo_rwlock);
|
||||
|
||||
foo_unref could use a mechanism such as bottom halves to move deallocation
|
||||
``foo_unref`` could use a mechanism such as bottom halves to move deallocation
|
||||
out of the write-side critical section.
|
||||
|
||||
|
||||
RCU resizable arrays
|
||||
--------------------
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Resizable arrays can be used with RCU. The expensive RCU synchronization
|
||||
(or call_rcu) only needs to take place when the array is resized.
|
||||
(or ``call_rcu``) only needs to take place when the array is resized.
|
||||
The two items to take care of are:
|
||||
|
||||
- ensuring that the old version of the array is available between removal
|
||||
@ -351,10 +339,10 @@ The two items to take care of are:
|
||||
- avoiding mismatches in the read side between the array data and the
|
||||
array size.
|
||||
|
||||
The first problem is avoided simply by not using realloc. Instead,
|
||||
The first problem is avoided simply by not using ``realloc``. Instead,
|
||||
each resize will allocate a new array and copy the old data into it.
|
||||
The second problem would arise if the size and the data pointers were
|
||||
two members of a larger struct:
|
||||
two members of a larger struct::
|
||||
|
||||
struct mystuff {
|
||||
...
|
||||
@ -364,7 +352,7 @@ two members of a larger struct:
|
||||
...
|
||||
};
|
||||
|
||||
Instead, we store the size of the array with the array itself:
|
||||
Instead, we store the size of the array with the array itself::
|
||||
|
||||
struct arr {
|
||||
int size;
|
||||
@ -400,7 +388,7 @@ Instead, we store the size of the array with the array itself:
|
||||
}
|
||||
|
||||
|
||||
SOURCES
|
||||
=======
|
||||
References
|
||||
----------
|
||||
|
||||
* Documentation/RCU/ from the Linux kernel
|
||||
* The `Linux kernel RCU documentation <https://docs.kernel.org/RCU/>`__
|
177
docs/devel/testing/blkdebug.rst
Normal file
177
docs/devel/testing/blkdebug.rst
Normal file
@ -0,0 +1,177 @@
|
||||
Block I/O error injection using ``blkdebug``
|
||||
============================================
|
||||
|
||||
..
|
||||
Copyright (C) 2014-2015 Red Hat Inc
|
||||
|
||||
This work is licensed under the terms of the GNU GPL, version 2 or later. See
|
||||
the COPYING file in the top-level directory.
|
||||
|
||||
The ``blkdebug`` block driver is a rule-based error injection engine. It can be
|
||||
used to exercise error code paths in block drivers including ``ENOSPC`` (out of
|
||||
space) and ``EIO``.
|
||||
|
||||
This document gives an overview of the features available in ``blkdebug``.
|
||||
|
||||
Background
|
||||
----------
|
||||
Block drivers have many error code paths that handle I/O errors. Image formats
|
||||
are especially complex since metadata I/O errors during cluster allocation or
|
||||
while updating tables happen halfway through request processing and require
|
||||
discipline to keep image files consistent.
|
||||
|
||||
Error injection allows test cases to trigger I/O errors at specific points.
|
||||
This way, all error paths can be tested to make sure they are correct.
|
||||
|
||||
Rules
|
||||
-----
|
||||
The ``blkdebug`` block driver takes a list of "rules" that tell the error injection
|
||||
engine when to fail an I/O request.
|
||||
|
||||
Each I/O request is evaluated against the rules. If a rule matches the request
|
||||
then its "action" is executed.
|
||||
|
||||
Rules can be placed in a configuration file; the configuration file
|
||||
follows the same .ini-like format used by QEMU's ``-readconfig`` option, and
|
||||
each section of the file represents a rule.
|
||||
|
||||
The following configuration file defines a single rule::
|
||||
|
||||
$ cat blkdebug.conf
|
||||
[inject-error]
|
||||
event = "read_aio"
|
||||
errno = "28"
|
||||
|
||||
This rule fails all aio read requests with ``ENOSPC`` (28). Note that the errno
|
||||
value depends on the host. On Linux, see
|
||||
``/usr/include/asm-generic/errno-base.h`` for errno values.
|
||||
|
||||
Invoke QEMU as follows::
|
||||
|
||||
$ qemu-system-x86_64
|
||||
-drive if=none,cache=none,file=blkdebug:blkdebug.conf:test.img,id=drive0 \
|
||||
-device virtio-blk-pci,drive=drive0,id=virtio-blk-pci0
|
||||
|
||||
Rules support the following attributes:
|
||||
|
||||
``event``
|
||||
which type of operation to match (e.g. ``read_aio``, ``write_aio``,
|
||||
``flush_to_os``, ``flush_to_disk``). See `Events`_ for
|
||||
information on events.
|
||||
|
||||
``state``
|
||||
(optional) the engine must be in this state number in order for this
|
||||
rule to match. See `State transitions`_ for information
|
||||
on states.
|
||||
|
||||
``errno``
|
||||
the numeric errno value to return when a request matches this rule.
|
||||
The errno values depend on the host since the numeric values are not
|
||||
standardized in the POSIX specification.
|
||||
|
||||
``sector``
|
||||
(optional) a sector number that the request must overlap in order to
|
||||
match this rule
|
||||
|
||||
``once``
|
||||
(optional, default ``off``) only execute this action on the first
|
||||
matching request
|
||||
|
||||
``immediately``
|
||||
(optional, default ``off``) return a NULL ``BlockAIOCB``
|
||||
pointer and fail without an errno instead. This
|
||||
exercises the code path where ``BlockAIOCB`` fails and the
|
||||
caller's ``BlockCompletionFunc`` is not invoked.
|
||||
|
||||
Events
|
||||
------
|
||||
Block drivers provide information about the type of I/O request they are about
|
||||
to make so rules can match specific types of requests. For example, the ``qcow2``
|
||||
block driver tells ``blkdebug`` when it accesses the L1 table so rules can match
|
||||
only L1 table accesses and not other metadata or guest data requests.
|
||||
|
||||
The core events are:
|
||||
|
||||
``read_aio``
|
||||
guest data read
|
||||
|
||||
``write_aio``
|
||||
guest data write
|
||||
|
||||
``flush_to_os``
|
||||
write out unwritten block driver state (e.g. cached metadata)
|
||||
|
||||
``flush_to_disk``
|
||||
flush the host block device's disk cache
|
||||
|
||||
See ``qapi/block-core.json:BlkdebugEvent`` for the full list of events.
|
||||
You may need to grep block driver source code to understand the
|
||||
meaning of specific events.
|
||||
|
||||
State transitions
|
||||
-----------------
|
||||
There are cases where more power is needed to match a particular I/O request in
|
||||
a longer sequence of requests. For example::
|
||||
|
||||
write_aio
|
||||
flush_to_disk
|
||||
write_aio
|
||||
|
||||
How do we match the 2nd ``write_aio`` but not the first? This is where state
|
||||
transitions come in.
|
||||
|
||||
The error injection engine has an integer called the "state" that always starts
|
||||
initialized to 1. The state integer is internal to ``blkdebug`` and cannot be
|
||||
observed from outside but rules can interact with it for powerful matching
|
||||
behavior.
|
||||
|
||||
Rules can be conditional on the current state and they can transition to a new
|
||||
state.
|
||||
|
||||
When a rule's "state" attribute is non-zero then the current state must equal
|
||||
the attribute in order for the rule to match.
|
||||
|
||||
For example, to match the 2nd write_aio::
|
||||
|
||||
[set-state]
|
||||
event = "write_aio"
|
||||
state = "1"
|
||||
new_state = "2"
|
||||
|
||||
[inject-error]
|
||||
event = "write_aio"
|
||||
state = "2"
|
||||
errno = "5"
|
||||
|
||||
The first ``write_aio`` request matches the ``set-state`` rule and transitions from
|
||||
state 1 to state 2. Once state 2 has been entered, the ``set-state`` rule no
|
||||
longer matches since it requires state 1. But the ``inject-error`` rule now
|
||||
matches the next ``write_aio`` request and injects ``EIO`` (5).
|
||||
|
||||
State transition rules support the following attributes:
|
||||
|
||||
``event``
|
||||
which type of operation to match (e.g. ``read_aio``, ``write_aio``,
|
||||
``flush_to_os`, ``flush_to_disk``). See `Events`_ for
|
||||
information on events.
|
||||
|
||||
``state``
|
||||
(optional) the engine must be in this state number in order for this
|
||||
rule to match
|
||||
|
||||
``new_state``
|
||||
transition to this state number
|
||||
|
||||
Suspend and resume
|
||||
------------------
|
||||
Exercising code paths in block drivers may require specific ordering amongst
|
||||
concurrent requests. The "breakpoint" feature allows requests to be halted on
|
||||
a ``blkdebug`` event and resumed later. This makes it possible to achieve
|
||||
deterministic ordering when multiple requests are in flight.
|
||||
|
||||
Breakpoints on ``blkdebug`` events are associated with a user-defined ``tag`` string.
|
||||
This tag serves as an identifier by which the request can be resumed at a later
|
||||
point.
|
||||
|
||||
See the ``qemu-io(1)`` ``break``, ``resume``, ``remove_break``, and ``wait_break``
|
||||
commands for details.
|
@ -1,8 +1,10 @@
|
||||
= Block driver correctness testing with blkverify =
|
||||
Block driver correctness testing with ``blkverify``
|
||||
===================================================
|
||||
|
||||
== Introduction ==
|
||||
Introduction
|
||||
------------
|
||||
|
||||
This document describes how to use the blkverify protocol to test that a block
|
||||
This document describes how to use the ``blkverify`` protocol to test that a block
|
||||
driver is operating correctly.
|
||||
|
||||
It is difficult to test and debug block drivers against real guests. Often
|
||||
@ -11,12 +13,13 @@ of the executable. Other times obscure errors are raised by a program inside
|
||||
the guest. These issues are extremely hard to trace back to bugs in the block
|
||||
driver.
|
||||
|
||||
Blkverify solves this problem by catching data corruption inside QEMU the first
|
||||
``blkverify`` solves this problem by catching data corruption inside QEMU the first
|
||||
time bad data is read and reporting the disk sector that is corrupted.
|
||||
|
||||
== How it works ==
|
||||
How it works
|
||||
------------
|
||||
|
||||
The blkverify protocol has two child block devices, the "test" device and the
|
||||
The ``blkverify`` protocol has two child block devices, the "test" device and the
|
||||
"raw" device. Read/write operations are mirrored to both devices so their
|
||||
state should always be in sync.
|
||||
|
||||
@ -25,13 +28,14 @@ contents to the "test" image. The idea is that the "raw" device will handle
|
||||
read/write operations correctly and not corrupt data. It can be used as a
|
||||
reference for comparison against the "test" device.
|
||||
|
||||
After a mirrored read operation completes, blkverify will compare the data and
|
||||
After a mirrored read operation completes, ``blkverify`` will compare the data and
|
||||
raise an error if it is not identical. This makes it possible to catch the
|
||||
first instance where corrupt data is read.
|
||||
|
||||
== Example ==
|
||||
Example
|
||||
-------
|
||||
|
||||
Imagine raw.img has 0xcd repeated throughout its first sector:
|
||||
Imagine raw.img has 0xcd repeated throughout its first sector::
|
||||
|
||||
$ ./qemu-io -c 'read -v 0 512' raw.img
|
||||
00000000: cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd ................
|
||||
@ -42,7 +46,7 @@ Imagine raw.img has 0xcd repeated throughout its first sector:
|
||||
read 512/512 bytes at offset 0
|
||||
512.000000 bytes, 1 ops; 0.0000 sec (97.656 MiB/sec and 200000.0000 ops/sec)
|
||||
|
||||
And test.img is corrupt, its first sector is zeroed when it shouldn't be:
|
||||
And test.img is corrupt, its first sector is zeroed when it shouldn't be::
|
||||
|
||||
$ ./qemu-io -c 'read -v 0 512' test.img
|
||||
00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
@ -53,17 +57,17 @@ And test.img is corrupt, its first sector is zeroed when it shouldn't be:
|
||||
read 512/512 bytes at offset 0
|
||||
512.000000 bytes, 1 ops; 0.0000 sec (81.380 MiB/sec and 166666.6667 ops/sec)
|
||||
|
||||
This error is caught by blkverify:
|
||||
This error is caught by ``blkverify``::
|
||||
|
||||
$ ./qemu-io -c 'read 0 512' blkverify:a.img:b.img
|
||||
blkverify: read sector_num=0 nb_sectors=4 contents mismatch in sector 0
|
||||
|
||||
A more realistic scenario is verifying the installation of a guest OS:
|
||||
A more realistic scenario is verifying the installation of a guest OS::
|
||||
|
||||
$ ./qemu-img create raw.img 16G
|
||||
$ ./qemu-img create -f qcow2 test.qcow2 16G
|
||||
$ ./qemu-system-x86_64 -cdrom debian.iso \
|
||||
-drive file=blkverify:raw.img:test.qcow2
|
||||
|
||||
If the installation is aborted when blkverify detects corruption, use qemu-io
|
||||
If the installation is aborted when ``blkverify`` detects corruption, use ``qemu-io``
|
||||
to explore the contents of the disk image at the sector in question.
|
@ -14,3 +14,5 @@ testing infrastructure.
|
||||
acpi-bits
|
||||
ci
|
||||
fuzzing
|
||||
blkdebug
|
||||
blkverify
|
||||
|
@ -15,4 +15,5 @@ Emulated devices:
|
||||
- USB controller
|
||||
- SATA controller
|
||||
- TWI (I2C) controller
|
||||
- SPI controller
|
||||
- Watchdog timer
|
||||
|
@ -36,6 +36,7 @@ Supported devices
|
||||
* SPI controller
|
||||
* System configuration (SYSCFG)
|
||||
* Timer controller (TIMER)
|
||||
* Reset and Clock Controller (RCC) (STM32F4 only, reset and enable only)
|
||||
|
||||
Missing devices
|
||||
---------------
|
||||
@ -53,7 +54,7 @@ Missing devices
|
||||
* Power supply configuration (PWR)
|
||||
* Random Number Generator (RNG)
|
||||
* Real-Time Clock (RTC) controller
|
||||
* Reset and Clock Controller (RCC)
|
||||
* Reset and Clock Controller (RCC) (other features than reset and enable)
|
||||
* Secure Digital Input/Output (SDIO) interface
|
||||
* USB OTG
|
||||
* Watchdog controller (IWDG, WWDG)
|
||||
|
@ -27,7 +27,6 @@ source nvme/Kconfig
|
||||
source nvram/Kconfig
|
||||
source pci-bridge/Kconfig
|
||||
source pci-host/Kconfig
|
||||
source pcmcia/Kconfig
|
||||
source pci/Kconfig
|
||||
source remote/Kconfig
|
||||
source rtc/Kconfig
|
||||
|
@ -1,5 +1,2 @@
|
||||
config STM32F2XX_ADC
|
||||
bool
|
||||
|
||||
config MAX111X
|
||||
bool
|
||||
|
236
hw/adc/max111x.c
236
hw/adc/max111x.c
@ -1,236 +0,0 @@
|
||||
/*
|
||||
* Maxim MAX1110/1111 ADC chip emulation.
|
||||
*
|
||||
* Copyright (c) 2006 Openedhand Ltd.
|
||||
* Written by Andrzej Zaborowski <balrog@zabor.org>
|
||||
*
|
||||
* This code is licensed under the GNU GPLv2.
|
||||
*
|
||||
* Contributions after 2012-01-13 are licensed under the terms of the
|
||||
* GNU GPL, version 2 or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/adc/max111x.h"
|
||||
#include "hw/irq.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qemu/module.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
|
||||
/* Control-byte bitfields */
|
||||
#define CB_PD0 (1 << 0)
|
||||
#define CB_PD1 (1 << 1)
|
||||
#define CB_SGL (1 << 2)
|
||||
#define CB_UNI (1 << 3)
|
||||
#define CB_SEL0 (1 << 4)
|
||||
#define CB_SEL1 (1 << 5)
|
||||
#define CB_SEL2 (1 << 6)
|
||||
#define CB_START (1 << 7)
|
||||
|
||||
#define CHANNEL_NUM(v, b0, b1, b2) \
|
||||
((((v) >> (2 + (b0))) & 4) | \
|
||||
(((v) >> (3 + (b1))) & 2) | \
|
||||
(((v) >> (4 + (b2))) & 1))
|
||||
|
||||
static uint32_t max111x_read(MAX111xState *s)
|
||||
{
|
||||
if (!s->tb1)
|
||||
return 0;
|
||||
|
||||
switch (s->cycle ++) {
|
||||
case 1:
|
||||
return s->rb2;
|
||||
case 2:
|
||||
return s->rb3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Interpret a control-byte */
|
||||
static void max111x_write(MAX111xState *s, uint32_t value)
|
||||
{
|
||||
int measure, chan;
|
||||
|
||||
/* Ignore the value if START bit is zero */
|
||||
if (!(value & CB_START))
|
||||
return;
|
||||
|
||||
s->cycle = 0;
|
||||
|
||||
if (!(value & CB_PD1)) {
|
||||
s->tb1 = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
s->tb1 = value;
|
||||
|
||||
if (s->inputs == 8)
|
||||
chan = CHANNEL_NUM(value, 1, 0, 2);
|
||||
else
|
||||
chan = CHANNEL_NUM(value & ~CB_SEL0, 0, 1, 2);
|
||||
|
||||
if (value & CB_SGL)
|
||||
measure = s->input[chan] - s->com;
|
||||
else
|
||||
measure = s->input[chan] - s->input[chan ^ 1];
|
||||
|
||||
if (!(value & CB_UNI))
|
||||
measure ^= 0x80;
|
||||
|
||||
s->rb2 = (measure >> 2) & 0x3f;
|
||||
s->rb3 = (measure << 6) & 0xc0;
|
||||
|
||||
/* FIXME: When should the IRQ be lowered? */
|
||||
qemu_irq_raise(s->interrupt);
|
||||
}
|
||||
|
||||
static uint32_t max111x_transfer(SSIPeripheral *dev, uint32_t value)
|
||||
{
|
||||
MAX111xState *s = MAX_111X(dev);
|
||||
max111x_write(s, value);
|
||||
return max111x_read(s);
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_max111x = {
|
||||
.name = "max111x",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_SSI_PERIPHERAL(parent_obj, MAX111xState),
|
||||
VMSTATE_UINT8(tb1, MAX111xState),
|
||||
VMSTATE_UINT8(rb2, MAX111xState),
|
||||
VMSTATE_UINT8(rb3, MAX111xState),
|
||||
VMSTATE_INT32_EQUAL(inputs, MAX111xState, NULL),
|
||||
VMSTATE_INT32(com, MAX111xState),
|
||||
VMSTATE_ARRAY_INT32_UNSAFE(input, MAX111xState, inputs,
|
||||
vmstate_info_uint8, uint8_t),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void max111x_input_set(void *opaque, int line, int value)
|
||||
{
|
||||
MAX111xState *s = MAX_111X(opaque);
|
||||
|
||||
assert(line >= 0 && line < s->inputs);
|
||||
s->input[line] = value;
|
||||
}
|
||||
|
||||
static int max111x_init(SSIPeripheral *d, int inputs)
|
||||
{
|
||||
DeviceState *dev = DEVICE(d);
|
||||
MAX111xState *s = MAX_111X(dev);
|
||||
|
||||
qdev_init_gpio_out(dev, &s->interrupt, 1);
|
||||
qdev_init_gpio_in(dev, max111x_input_set, inputs);
|
||||
|
||||
s->inputs = inputs;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void max1110_realize(SSIPeripheral *dev, Error **errp)
|
||||
{
|
||||
max111x_init(dev, 8);
|
||||
}
|
||||
|
||||
static void max1111_realize(SSIPeripheral *dev, Error **errp)
|
||||
{
|
||||
max111x_init(dev, 4);
|
||||
}
|
||||
|
||||
static void max111x_reset(DeviceState *dev)
|
||||
{
|
||||
MAX111xState *s = MAX_111X(dev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < s->inputs; i++) {
|
||||
s->input[i] = s->reset_input[i];
|
||||
}
|
||||
s->com = 0;
|
||||
s->tb1 = 0;
|
||||
s->rb2 = 0;
|
||||
s->rb3 = 0;
|
||||
s->cycle = 0;
|
||||
}
|
||||
|
||||
static Property max1110_properties[] = {
|
||||
/* Reset values for ADC inputs */
|
||||
DEFINE_PROP_UINT8("input0", MAX111xState, reset_input[0], 0xf0),
|
||||
DEFINE_PROP_UINT8("input1", MAX111xState, reset_input[1], 0xe0),
|
||||
DEFINE_PROP_UINT8("input2", MAX111xState, reset_input[2], 0xd0),
|
||||
DEFINE_PROP_UINT8("input3", MAX111xState, reset_input[3], 0xc0),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static Property max1111_properties[] = {
|
||||
/* Reset values for ADC inputs */
|
||||
DEFINE_PROP_UINT8("input0", MAX111xState, reset_input[0], 0xf0),
|
||||
DEFINE_PROP_UINT8("input1", MAX111xState, reset_input[1], 0xe0),
|
||||
DEFINE_PROP_UINT8("input2", MAX111xState, reset_input[2], 0xd0),
|
||||
DEFINE_PROP_UINT8("input3", MAX111xState, reset_input[3], 0xc0),
|
||||
DEFINE_PROP_UINT8("input4", MAX111xState, reset_input[4], 0xb0),
|
||||
DEFINE_PROP_UINT8("input5", MAX111xState, reset_input[5], 0xa0),
|
||||
DEFINE_PROP_UINT8("input6", MAX111xState, reset_input[6], 0x90),
|
||||
DEFINE_PROP_UINT8("input7", MAX111xState, reset_input[7], 0x80),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void max111x_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass);
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
k->transfer = max111x_transfer;
|
||||
device_class_set_legacy_reset(dc, max111x_reset);
|
||||
dc->vmsd = &vmstate_max111x;
|
||||
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
|
||||
}
|
||||
|
||||
static const TypeInfo max111x_info = {
|
||||
.name = TYPE_MAX_111X,
|
||||
.parent = TYPE_SSI_PERIPHERAL,
|
||||
.instance_size = sizeof(MAX111xState),
|
||||
.class_init = max111x_class_init,
|
||||
.abstract = true,
|
||||
};
|
||||
|
||||
static void max1110_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass);
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
k->realize = max1110_realize;
|
||||
device_class_set_props(dc, max1110_properties);
|
||||
}
|
||||
|
||||
static const TypeInfo max1110_info = {
|
||||
.name = TYPE_MAX_1110,
|
||||
.parent = TYPE_MAX_111X,
|
||||
.class_init = max1110_class_init,
|
||||
};
|
||||
|
||||
static void max1111_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass);
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
k->realize = max1111_realize;
|
||||
device_class_set_props(dc, max1111_properties);
|
||||
}
|
||||
|
||||
static const TypeInfo max1111_info = {
|
||||
.name = TYPE_MAX_1111,
|
||||
.parent = TYPE_MAX_111X,
|
||||
.class_init = max1111_class_init,
|
||||
};
|
||||
|
||||
static void max111x_register_types(void)
|
||||
{
|
||||
type_register_static(&max111x_info);
|
||||
type_register_static(&max1110_info);
|
||||
type_register_static(&max1111_info);
|
||||
}
|
||||
|
||||
type_init(max111x_register_types)
|
@ -2,4 +2,3 @@ system_ss.add(when: 'CONFIG_STM32F2XX_ADC', if_true: files('stm32f2xx_adc.c'))
|
||||
system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_adc.c'))
|
||||
system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_adc.c'))
|
||||
system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq-xadc.c'))
|
||||
system_ss.add(when: 'CONFIG_MAX111X', if_true: files('max111x.c'))
|
||||
|
@ -150,7 +150,6 @@ config OMAP
|
||||
bool
|
||||
select FRAMEBUFFER
|
||||
select I2C
|
||||
select ECC
|
||||
select NAND
|
||||
select PFLASH_CFI01
|
||||
select SD
|
||||
@ -328,6 +327,7 @@ config ALLWINNER_A10
|
||||
select ALLWINNER_WDT
|
||||
select ALLWINNER_EMAC
|
||||
select ALLWINNER_I2C
|
||||
select ALLWINNER_A10_SPI
|
||||
select AXP2XX_PMU
|
||||
select SERIAL_MM
|
||||
select UNIMP
|
||||
@ -397,6 +397,7 @@ config STM32F405_SOC
|
||||
bool
|
||||
select ARM_V7M
|
||||
select OR_IRQ
|
||||
select STM32_RCC
|
||||
select STM32F4XX_SYSCFG
|
||||
select STM32F4XX_EXTI
|
||||
|
||||
|
@ -35,6 +35,7 @@
|
||||
#define AW_A10_PIC_REG_BASE 0x01c20400
|
||||
#define AW_A10_PIT_REG_BASE 0x01c20c00
|
||||
#define AW_A10_UART0_REG_BASE 0x01c28000
|
||||
#define AW_A10_SPI0_BASE 0x01c05000
|
||||
#define AW_A10_EMAC_BASE 0x01c0b000
|
||||
#define AW_A10_EHCI_BASE 0x01c14000
|
||||
#define AW_A10_OHCI_BASE 0x01c14400
|
||||
@ -80,6 +81,8 @@ static void aw_a10_init(Object *obj)
|
||||
|
||||
object_initialize_child(obj, "i2c0", &s->i2c0, TYPE_AW_I2C);
|
||||
|
||||
object_initialize_child(obj, "spi0", &s->spi0, TYPE_AW_A10_SPI);
|
||||
|
||||
for (size_t i = 0; i < AW_A10_NUM_USB; i++) {
|
||||
object_initialize_child(obj, "ehci[*]", &s->ehci[i],
|
||||
TYPE_PLATFORM_EHCI);
|
||||
@ -195,6 +198,11 @@ static void aw_a10_realize(DeviceState *dev, Error **errp)
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->i2c0), 0, AW_A10_I2C0_BASE);
|
||||
sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c0), 0, qdev_get_gpio_in(dev, 7));
|
||||
|
||||
/* SPI */
|
||||
sysbus_realize(SYS_BUS_DEVICE(&s->spi0), &error_fatal);
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->spi0), 0, AW_A10_SPI0_BASE);
|
||||
sysbus_connect_irq(SYS_BUS_DEVICE(&s->spi0), 0, qdev_get_gpio_in(dev, 10));
|
||||
|
||||
/* WDT */
|
||||
sysbus_realize(SYS_BUS_DEVICE(&s->wdt), &error_fatal);
|
||||
sysbus_mmio_map_overlap(SYS_BUS_DEVICE(&s->wdt), 0, AW_A10_WDT_BASE, 1);
|
||||
|
@ -2170,29 +2170,27 @@ struct omap_uwire_s {
|
||||
uint16_t rxbuf;
|
||||
uint16_t control;
|
||||
uint16_t setup[5];
|
||||
|
||||
uWireSlave *chip[4];
|
||||
};
|
||||
|
||||
static void omap_uwire_transfer_start(struct omap_uwire_s *s)
|
||||
{
|
||||
int chipselect = (s->control >> 10) & 3; /* INDEX */
|
||||
uWireSlave *slave = s->chip[chipselect];
|
||||
|
||||
if ((s->control >> 5) & 0x1f) { /* NB_BITS_WR */
|
||||
if (s->control & (1 << 12)) /* CS_CMD */
|
||||
if (slave && slave->send)
|
||||
slave->send(slave->opaque,
|
||||
s->txbuf >> (16 - ((s->control >> 5) & 0x1f)));
|
||||
if (s->control & (1 << 12)) { /* CS_CMD */
|
||||
qemu_log_mask(LOG_UNIMP, "uWireSlave TX CS:%d data:0x%04x\n",
|
||||
chipselect,
|
||||
s->txbuf >> (16 - ((s->control >> 5) & 0x1f)));
|
||||
}
|
||||
s->control &= ~(1 << 14); /* CSRB */
|
||||
/* TODO: depending on s->setup[4] bits [1:0] assert an IRQ or
|
||||
* a DRQ. When is the level IRQ supposed to be reset? */
|
||||
}
|
||||
|
||||
if ((s->control >> 0) & 0x1f) { /* NB_BITS_RD */
|
||||
if (s->control & (1 << 12)) /* CS_CMD */
|
||||
if (slave && slave->receive)
|
||||
s->rxbuf = slave->receive(slave->opaque);
|
||||
if (s->control & (1 << 12)) { /* CS_CMD */
|
||||
qemu_log_mask(LOG_UNIMP, "uWireSlave RX CS:%d\n", chipselect);
|
||||
}
|
||||
s->control |= 1 << 15; /* RDRB */
|
||||
/* TODO: depending on s->setup[4] bits [1:0] assert an IRQ or
|
||||
* a DRQ. When is the level IRQ supposed to be reset? */
|
||||
@ -2321,17 +2319,6 @@ static struct omap_uwire_s *omap_uwire_init(MemoryRegion *system_memory,
|
||||
return s;
|
||||
}
|
||||
|
||||
void omap_uwire_attach(struct omap_uwire_s *s,
|
||||
uWireSlave *slave, int chipselect)
|
||||
{
|
||||
if (chipselect < 0 || chipselect > 3) {
|
||||
error_report("%s: Bad chipselect %i", __func__, chipselect);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
s->chip[chipselect] = slave;
|
||||
}
|
||||
|
||||
/* Pseudonoise Pulse-Width Light Modulator */
|
||||
struct omap_pwl_s {
|
||||
MemoryRegion iomem;
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "hw/qdev-clock.h"
|
||||
#include "hw/misc/unimp.h"
|
||||
|
||||
#define RCC_ADDR 0x40023800
|
||||
#define SYSCFG_ADD 0x40013800
|
||||
static const uint32_t usart_addr[] = { 0x40011000, 0x40004400, 0x40004800,
|
||||
0x40004C00, 0x40005000, 0x40011400,
|
||||
@ -59,6 +60,8 @@ static void stm32f405_soc_initfn(Object *obj)
|
||||
|
||||
object_initialize_child(obj, "armv7m", &s->armv7m, TYPE_ARMV7M);
|
||||
|
||||
object_initialize_child(obj, "rcc", &s->rcc, TYPE_STM32_RCC);
|
||||
|
||||
object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_STM32F4XX_SYSCFG);
|
||||
|
||||
for (i = 0; i < STM_NUM_USARTS; i++) {
|
||||
@ -160,6 +163,14 @@ static void stm32f405_soc_realize(DeviceState *dev_soc, Error **errp)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Reset and clock controller */
|
||||
dev = DEVICE(&s->rcc);
|
||||
if (!sysbus_realize(SYS_BUS_DEVICE(&s->rcc), errp)) {
|
||||
return;
|
||||
}
|
||||
busdev = SYS_BUS_DEVICE(dev);
|
||||
sysbus_mmio_map(busdev, 0, RCC_ADDR);
|
||||
|
||||
/* System configuration controller */
|
||||
dev = DEVICE(&s->syscfg);
|
||||
if (!sysbus_realize(SYS_BUS_DEVICE(&s->syscfg), errp)) {
|
||||
@ -276,7 +287,6 @@ static void stm32f405_soc_realize(DeviceState *dev_soc, Error **errp)
|
||||
create_unimplemented_device("GPIOH", 0x40021C00, 0x400);
|
||||
create_unimplemented_device("GPIOI", 0x40022000, 0x400);
|
||||
create_unimplemented_device("CRC", 0x40023000, 0x400);
|
||||
create_unimplemented_device("RCC", 0x40023800, 0x400);
|
||||
create_unimplemented_device("Flash Int", 0x40023C00, 0x400);
|
||||
create_unimplemented_device("BKPSRAM", 0x40024000, 0x400);
|
||||
create_unimplemented_device("DMA1", 0x40026000, 0x400);
|
||||
|
@ -236,6 +236,8 @@ static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
|
||||
|
||||
/* System configuration controller */
|
||||
busdev = SYS_BUS_DEVICE(&s->syscfg);
|
||||
qdev_connect_clock_in(DEVICE(&s->syscfg), "clk",
|
||||
qdev_get_clock_out(DEVICE(&(s->rcc)), "syscfg-out"));
|
||||
if (!sysbus_realize(busdev, errp)) {
|
||||
return;
|
||||
}
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "hw/net/cadence_gem.h"
|
||||
#include "hw/cpu/a9mpcore.h"
|
||||
#include "hw/qdev-clock.h"
|
||||
#include "hw/misc/unimp.h"
|
||||
#include "sysemu/reset.h"
|
||||
#include "qom/object.h"
|
||||
#include "exec/tswap.h"
|
||||
@ -373,6 +374,75 @@ static void zynq_init(MachineState *machine)
|
||||
sysbus_connect_irq(busdev, 0, pic[40 - IRQ_OFFSET]);
|
||||
sysbus_mmio_map(busdev, 0, 0xF8007000);
|
||||
|
||||
/*
|
||||
* Refer to the ug585-Zynq-7000-TRM manual B.3 (Module Summary) and
|
||||
* the zynq-7000.dtsi. Add placeholders for unimplemented devices.
|
||||
*/
|
||||
create_unimplemented_device("zynq.i2c0", 0xE0004000, 4 * KiB);
|
||||
create_unimplemented_device("zynq.i2c1", 0xE0005000, 4 * KiB);
|
||||
create_unimplemented_device("zynq.can0", 0xE0008000, 4 * KiB);
|
||||
create_unimplemented_device("zynq.can1", 0xE0009000, 4 * KiB);
|
||||
create_unimplemented_device("zynq.gpio", 0xE000A000, 4 * KiB);
|
||||
create_unimplemented_device("zynq.smcc", 0xE000E000, 4 * KiB);
|
||||
|
||||
/* Direct Memory Access Controller, PL330, Non-Secure Mode */
|
||||
create_unimplemented_device("zynq.dma_ns", 0xF8004000, 4 * KiB);
|
||||
|
||||
/* System Watchdog Timer Registers */
|
||||
create_unimplemented_device("zynq.swdt", 0xF8005000, 4 * KiB);
|
||||
|
||||
/* DDR memory controller */
|
||||
create_unimplemented_device("zynq.ddrc", 0xF8006000, 4 * KiB);
|
||||
|
||||
/* AXI_HP Interface (AFI) */
|
||||
create_unimplemented_device("zynq.axi_hp0", 0xF8008000, 0x28);
|
||||
create_unimplemented_device("zynq.axi_hp1", 0xF8009000, 0x28);
|
||||
create_unimplemented_device("zynq.axi_hp2", 0xF800A000, 0x28);
|
||||
create_unimplemented_device("zynq.axi_hp3", 0xF800B000, 0x28);
|
||||
|
||||
create_unimplemented_device("zynq.efuse", 0xF800d000, 0x20);
|
||||
|
||||
/* Embedded Trace Buffer */
|
||||
create_unimplemented_device("zynq.etb", 0xF8801000, 4 * KiB);
|
||||
|
||||
/* Cross Trigger Interface, ETB and TPIU */
|
||||
create_unimplemented_device("zynq.cti_etb_tpiu", 0xF8802000, 4 * KiB);
|
||||
|
||||
/* Trace Port Interface Unit */
|
||||
create_unimplemented_device("zynq.tpiu", 0xF8803000, 4 * KiB);
|
||||
|
||||
/* CoreSight Trace Funnel */
|
||||
create_unimplemented_device("zynq.funnel", 0xF8804000, 4 * KiB);
|
||||
|
||||
/* Instrumentation Trace Macrocell */
|
||||
create_unimplemented_device("zynq.itm", 0xF8805000, 4 * KiB);
|
||||
|
||||
/* Cross Trigger Interface, FTM */
|
||||
create_unimplemented_device("zynq.cti_ftm", 0xF8809000, 4 * KiB);
|
||||
|
||||
/* Fabric Trace Macrocell */
|
||||
create_unimplemented_device("zynq.ftm", 0xF880B000, 4 * KiB);
|
||||
|
||||
/* Cortex A9 Performance Monitoring Unit, CPU */
|
||||
create_unimplemented_device("cortex-a9.pmu0", 0xF8891000, 4 * KiB);
|
||||
create_unimplemented_device("cortex-a9.pmu1", 0xF8893000, 4 * KiB);
|
||||
|
||||
/* Cross Trigger Interface, CPU */
|
||||
create_unimplemented_device("zynq.cpu_cti0", 0xF8898000, 4 * KiB);
|
||||
create_unimplemented_device("zynq.cpu_cti1", 0xF8899000, 4 * KiB);
|
||||
|
||||
/* CoreSight PTM-A9, CPU */
|
||||
create_unimplemented_device("cortex-a9.ptm0", 0xF889c000, 4 * KiB);
|
||||
create_unimplemented_device("cortex-a9.ptm1", 0xF889d000, 4 * KiB);
|
||||
|
||||
/* AMBA NIC301 TrustZone */
|
||||
create_unimplemented_device("zynq.trustZone", 0xF8900000, 0x20);
|
||||
|
||||
/* AMBA Network Interconnect Advanced Quality of Service (QoS-301) */
|
||||
create_unimplemented_device("zynq.qos301_cpu", 0xF8946000, 0x130);
|
||||
create_unimplemented_device("zynq.qos301_dmac", 0xF8947000, 0x130);
|
||||
create_unimplemented_device("zynq.qos301_iou", 0xF8948000, 0x130);
|
||||
|
||||
zynq_binfo.ram_size = machine->ram_size;
|
||||
zynq_binfo.board_id = 0xd32;
|
||||
zynq_binfo.loader_start = 0;
|
||||
|
@ -22,9 +22,6 @@ config PFLASH_CFI01
|
||||
config PFLASH_CFI02
|
||||
bool
|
||||
|
||||
config ECC
|
||||
bool
|
||||
|
||||
config VIRTIO_BLK
|
||||
bool
|
||||
default y
|
||||
|
@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Calculate Error-correcting Codes. Used by NAND Flash controllers
|
||||
* (not by NAND chips).
|
||||
*
|
||||
* Copyright (c) 2006 Openedhand Ltd.
|
||||
* Written by Andrzej Zaborowski <balrog@zabor.org>
|
||||
*
|
||||
* This code is licensed under the GNU GPL v2.
|
||||
*
|
||||
* Contributions after 2012-01-13 are licensed under the terms of the
|
||||
* GNU GPL, version 2 or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "hw/block/flash.h"
|
||||
|
||||
/*
|
||||
* Pre-calculated 256-way 1 byte column parity. Table borrowed from Linux.
|
||||
*/
|
||||
static const uint8_t nand_ecc_precalc_table[] = {
|
||||
0x00, 0x55, 0x56, 0x03, 0x59, 0x0c, 0x0f, 0x5a,
|
||||
0x5a, 0x0f, 0x0c, 0x59, 0x03, 0x56, 0x55, 0x00,
|
||||
0x65, 0x30, 0x33, 0x66, 0x3c, 0x69, 0x6a, 0x3f,
|
||||
0x3f, 0x6a, 0x69, 0x3c, 0x66, 0x33, 0x30, 0x65,
|
||||
0x66, 0x33, 0x30, 0x65, 0x3f, 0x6a, 0x69, 0x3c,
|
||||
0x3c, 0x69, 0x6a, 0x3f, 0x65, 0x30, 0x33, 0x66,
|
||||
0x03, 0x56, 0x55, 0x00, 0x5a, 0x0f, 0x0c, 0x59,
|
||||
0x59, 0x0c, 0x0f, 0x5a, 0x00, 0x55, 0x56, 0x03,
|
||||
0x69, 0x3c, 0x3f, 0x6a, 0x30, 0x65, 0x66, 0x33,
|
||||
0x33, 0x66, 0x65, 0x30, 0x6a, 0x3f, 0x3c, 0x69,
|
||||
0x0c, 0x59, 0x5a, 0x0f, 0x55, 0x00, 0x03, 0x56,
|
||||
0x56, 0x03, 0x00, 0x55, 0x0f, 0x5a, 0x59, 0x0c,
|
||||
0x0f, 0x5a, 0x59, 0x0c, 0x56, 0x03, 0x00, 0x55,
|
||||
0x55, 0x00, 0x03, 0x56, 0x0c, 0x59, 0x5a, 0x0f,
|
||||
0x6a, 0x3f, 0x3c, 0x69, 0x33, 0x66, 0x65, 0x30,
|
||||
0x30, 0x65, 0x66, 0x33, 0x69, 0x3c, 0x3f, 0x6a,
|
||||
0x6a, 0x3f, 0x3c, 0x69, 0x33, 0x66, 0x65, 0x30,
|
||||
0x30, 0x65, 0x66, 0x33, 0x69, 0x3c, 0x3f, 0x6a,
|
||||
0x0f, 0x5a, 0x59, 0x0c, 0x56, 0x03, 0x00, 0x55,
|
||||
0x55, 0x00, 0x03, 0x56, 0x0c, 0x59, 0x5a, 0x0f,
|
||||
0x0c, 0x59, 0x5a, 0x0f, 0x55, 0x00, 0x03, 0x56,
|
||||
0x56, 0x03, 0x00, 0x55, 0x0f, 0x5a, 0x59, 0x0c,
|
||||
0x69, 0x3c, 0x3f, 0x6a, 0x30, 0x65, 0x66, 0x33,
|
||||
0x33, 0x66, 0x65, 0x30, 0x6a, 0x3f, 0x3c, 0x69,
|
||||
0x03, 0x56, 0x55, 0x00, 0x5a, 0x0f, 0x0c, 0x59,
|
||||
0x59, 0x0c, 0x0f, 0x5a, 0x00, 0x55, 0x56, 0x03,
|
||||
0x66, 0x33, 0x30, 0x65, 0x3f, 0x6a, 0x69, 0x3c,
|
||||
0x3c, 0x69, 0x6a, 0x3f, 0x65, 0x30, 0x33, 0x66,
|
||||
0x65, 0x30, 0x33, 0x66, 0x3c, 0x69, 0x6a, 0x3f,
|
||||
0x3f, 0x6a, 0x69, 0x3c, 0x66, 0x33, 0x30, 0x65,
|
||||
0x00, 0x55, 0x56, 0x03, 0x59, 0x0c, 0x0f, 0x5a,
|
||||
0x5a, 0x0f, 0x0c, 0x59, 0x03, 0x56, 0x55, 0x00,
|
||||
};
|
||||
|
||||
/* Update ECC parity count. */
|
||||
uint8_t ecc_digest(ECCState *s, uint8_t sample)
|
||||
{
|
||||
uint8_t idx = nand_ecc_precalc_table[sample];
|
||||
|
||||
s->cp ^= idx & 0x3f;
|
||||
if (idx & 0x40) {
|
||||
s->lp[0] ^= ~s->count;
|
||||
s->lp[1] ^= s->count;
|
||||
}
|
||||
s->count ++;
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
/* Reinitialise the counters. */
|
||||
void ecc_reset(ECCState *s)
|
||||
{
|
||||
s->lp[0] = 0x0000;
|
||||
s->lp[1] = 0x0000;
|
||||
s->cp = 0x00;
|
||||
s->count = 0;
|
||||
}
|
||||
|
||||
/* Save/restore */
|
||||
const VMStateDescription vmstate_ecc_state = {
|
||||
.name = "ecc-state",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT8(cp, ECCState),
|
||||
VMSTATE_UINT16_ARRAY(lp, ECCState, 2),
|
||||
VMSTATE_UINT16(count, ECCState),
|
||||
VMSTATE_END_OF_LIST(),
|
||||
},
|
||||
};
|
@ -3,7 +3,6 @@ system_ss.add(files(
|
||||
'cdrom.c',
|
||||
'hd-geometry.c'
|
||||
))
|
||||
system_ss.add(when: 'CONFIG_ECC', if_true: files('ecc.c'))
|
||||
system_ss.add(when: 'CONFIG_FDC', if_true: files('fdc.c'))
|
||||
system_ss.add(when: 'CONFIG_FDC_ISA', if_true: files('fdc-isa.c'))
|
||||
system_ss.add(when: 'CONFIG_FDC_SYSBUS', if_true: files('fdc-sysbus.c'))
|
||||
|
@ -90,10 +90,10 @@ DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Chardev *chr)
|
||||
#define CR_UARTEN (1 << 0)
|
||||
|
||||
/* Integer Baud Rate Divider, UARTIBRD */
|
||||
#define IBRD_MASK 0x3f
|
||||
#define IBRD_MASK 0xffff
|
||||
|
||||
/* Fractional Baud Rate Divider, UARTFBRD */
|
||||
#define FBRD_MASK 0xffff
|
||||
#define FBRD_MASK 0x3f
|
||||
|
||||
static const unsigned char pl011_id_arm[8] =
|
||||
{ 0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
|
||||
|
@ -13,6 +13,8 @@
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qapi/visitor.h"
|
||||
#include "sysemu/qtest.h"
|
||||
#include "hw/clock.h"
|
||||
#include "trace.h"
|
||||
|
||||
@ -158,6 +160,15 @@ bool clock_set_mul_div(Clock *clk, uint32_t multiplier, uint32_t divider)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void clock_period_prop_get(Object *obj, Visitor *v, const char *name,
|
||||
void *opaque, Error **errp)
|
||||
{
|
||||
Clock *clk = CLOCK(obj);
|
||||
uint64_t period = clock_get(clk);
|
||||
visit_type_uint64(v, name, &period, errp);
|
||||
}
|
||||
|
||||
|
||||
static void clock_initfn(Object *obj)
|
||||
{
|
||||
Clock *clk = CLOCK(obj);
|
||||
@ -166,6 +177,11 @@ static void clock_initfn(Object *obj)
|
||||
clk->divider = 1;
|
||||
|
||||
QLIST_INIT(&clk->children);
|
||||
|
||||
if (qtest_enabled()) {
|
||||
object_property_add(obj, "qtest-clock-period", "uint64",
|
||||
clock_period_prop_get, NULL, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void clock_finalizefn(Object *obj)
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "sysemu/hw_accel.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qemu/lockcnt.h"
|
||||
#include "exec/log.h"
|
||||
#include "exec/gdbstub.h"
|
||||
#include "sysemu/tcg.h"
|
||||
|
@ -1,7 +1,3 @@
|
||||
config MAX7310
|
||||
bool
|
||||
depends on I2C
|
||||
|
||||
config PL061
|
||||
bool
|
||||
|
||||
|
@ -1,217 +0,0 @@
|
||||
/*
|
||||
* MAX7310 8-port GPIO expansion chip.
|
||||
*
|
||||
* Copyright (c) 2006 Openedhand Ltd.
|
||||
* Written by Andrzej Zaborowski <balrog@zabor.org>
|
||||
*
|
||||
* This file is licensed under GNU GPL.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/i2c/i2c.h"
|
||||
#include "hw/irq.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define TYPE_MAX7310 "max7310"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(MAX7310State, MAX7310)
|
||||
|
||||
struct MAX7310State {
|
||||
I2CSlave parent_obj;
|
||||
|
||||
int i2c_command_byte;
|
||||
int len;
|
||||
|
||||
uint8_t level;
|
||||
uint8_t direction;
|
||||
uint8_t polarity;
|
||||
uint8_t status;
|
||||
uint8_t command;
|
||||
qemu_irq handler[8];
|
||||
qemu_irq *gpio_in;
|
||||
};
|
||||
|
||||
static void max7310_reset(DeviceState *dev)
|
||||
{
|
||||
MAX7310State *s = MAX7310(dev);
|
||||
|
||||
s->level &= s->direction;
|
||||
s->direction = 0xff;
|
||||
s->polarity = 0xf0;
|
||||
s->status = 0x01;
|
||||
s->command = 0x00;
|
||||
}
|
||||
|
||||
static uint8_t max7310_rx(I2CSlave *i2c)
|
||||
{
|
||||
MAX7310State *s = MAX7310(i2c);
|
||||
|
||||
switch (s->command) {
|
||||
case 0x00: /* Input port */
|
||||
return s->level ^ s->polarity;
|
||||
|
||||
case 0x01: /* Output port */
|
||||
return s->level & ~s->direction;
|
||||
|
||||
case 0x02: /* Polarity inversion */
|
||||
return s->polarity;
|
||||
|
||||
case 0x03: /* Configuration */
|
||||
return s->direction;
|
||||
|
||||
case 0x04: /* Timeout */
|
||||
return s->status;
|
||||
|
||||
case 0xff: /* Reserved */
|
||||
return 0xff;
|
||||
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP, "%s: Unsupported register 0x02%" PRIx8 "\n",
|
||||
__func__, s->command);
|
||||
break;
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
static int max7310_tx(I2CSlave *i2c, uint8_t data)
|
||||
{
|
||||
MAX7310State *s = MAX7310(i2c);
|
||||
uint8_t diff;
|
||||
int line;
|
||||
|
||||
if (s->len ++ > 1) {
|
||||
#ifdef VERBOSE
|
||||
printf("%s: message too long (%i bytes)\n", __func__, s->len);
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (s->i2c_command_byte) {
|
||||
s->command = data;
|
||||
s->i2c_command_byte = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (s->command) {
|
||||
case 0x01: /* Output port */
|
||||
for (diff = (data ^ s->level) & ~s->direction; diff;
|
||||
diff &= ~(1 << line)) {
|
||||
line = ctz32(diff);
|
||||
if (s->handler[line])
|
||||
qemu_set_irq(s->handler[line], (data >> line) & 1);
|
||||
}
|
||||
s->level = (s->level & s->direction) | (data & ~s->direction);
|
||||
break;
|
||||
|
||||
case 0x02: /* Polarity inversion */
|
||||
s->polarity = data;
|
||||
break;
|
||||
|
||||
case 0x03: /* Configuration */
|
||||
s->level &= ~(s->direction ^ data);
|
||||
s->direction = data;
|
||||
break;
|
||||
|
||||
case 0x04: /* Timeout */
|
||||
s->status = data;
|
||||
break;
|
||||
|
||||
case 0x00: /* Input port - ignore writes */
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP, "%s: Unsupported register 0x02%" PRIx8 "\n",
|
||||
__func__, s->command);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max7310_event(I2CSlave *i2c, enum i2c_event event)
|
||||
{
|
||||
MAX7310State *s = MAX7310(i2c);
|
||||
s->len = 0;
|
||||
|
||||
switch (event) {
|
||||
case I2C_START_SEND:
|
||||
s->i2c_command_byte = 1;
|
||||
break;
|
||||
case I2C_FINISH:
|
||||
#ifdef VERBOSE
|
||||
if (s->len == 1)
|
||||
printf("%s: message too short (%i bytes)\n", __func__, s->len);
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_max7310 = {
|
||||
.name = "max7310",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_INT32(i2c_command_byte, MAX7310State),
|
||||
VMSTATE_INT32(len, MAX7310State),
|
||||
VMSTATE_UINT8(level, MAX7310State),
|
||||
VMSTATE_UINT8(direction, MAX7310State),
|
||||
VMSTATE_UINT8(polarity, MAX7310State),
|
||||
VMSTATE_UINT8(status, MAX7310State),
|
||||
VMSTATE_UINT8(command, MAX7310State),
|
||||
VMSTATE_I2C_SLAVE(parent_obj, MAX7310State),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void max7310_gpio_set(void *opaque, int line, int level)
|
||||
{
|
||||
MAX7310State *s = (MAX7310State *) opaque;
|
||||
assert(line >= 0 && line < ARRAY_SIZE(s->handler));
|
||||
|
||||
if (level)
|
||||
s->level |= s->direction & (1 << line);
|
||||
else
|
||||
s->level &= ~(s->direction & (1 << line));
|
||||
}
|
||||
|
||||
/* MAX7310 is SMBus-compatible (can be used with only SMBus protocols),
|
||||
* but also accepts sequences that are not SMBus so return an I2C device. */
|
||||
static void max7310_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
MAX7310State *s = MAX7310(dev);
|
||||
|
||||
qdev_init_gpio_in(dev, max7310_gpio_set, ARRAY_SIZE(s->handler));
|
||||
qdev_init_gpio_out(dev, s->handler, ARRAY_SIZE(s->handler));
|
||||
}
|
||||
|
||||
static void max7310_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
|
||||
|
||||
dc->realize = max7310_realize;
|
||||
k->event = max7310_event;
|
||||
k->recv = max7310_rx;
|
||||
k->send = max7310_tx;
|
||||
device_class_set_legacy_reset(dc, max7310_reset);
|
||||
dc->vmsd = &vmstate_max7310;
|
||||
}
|
||||
|
||||
static const TypeInfo max7310_info = {
|
||||
.name = TYPE_MAX7310,
|
||||
.parent = TYPE_I2C_SLAVE,
|
||||
.instance_size = sizeof(MAX7310State),
|
||||
.class_init = max7310_class_init,
|
||||
};
|
||||
|
||||
static void max7310_register_types(void)
|
||||
{
|
||||
type_register_static(&max7310_info);
|
||||
}
|
||||
|
||||
type_init(max7310_register_types)
|
@ -1,7 +1,6 @@
|
||||
system_ss.add(when: 'CONFIG_GPIO_KEY', if_true: files('gpio_key.c'))
|
||||
system_ss.add(when: 'CONFIG_GPIO_MPC8XXX', if_true: files('mpc8xxx.c'))
|
||||
system_ss.add(when: 'CONFIG_GPIO_PWR', if_true: files('gpio_pwr.c'))
|
||||
system_ss.add(when: 'CONFIG_MAX7310', if_true: files('max7310.c'))
|
||||
system_ss.add(when: 'CONFIG_PCA9552', if_true: files('pca9552.c'))
|
||||
system_ss.add(when: 'CONFIG_PCA9554', if_true: files('pca9554.c'))
|
||||
system_ss.add(when: 'CONFIG_PL061', if_true: files('pl061.c'))
|
||||
|
@ -43,12 +43,6 @@ config IDE_VIA
|
||||
bool
|
||||
select IDE_PCI
|
||||
|
||||
config MICRODRIVE
|
||||
bool
|
||||
select IDE_BUS
|
||||
select IDE_DEV
|
||||
depends on PCMCIA
|
||||
|
||||
config AHCI
|
||||
bool
|
||||
select IDE_BUS
|
||||
|
@ -13,4 +13,3 @@ system_ss.add(when: 'CONFIG_IDE_PCI', if_true: files('pci.c'))
|
||||
system_ss.add(when: 'CONFIG_IDE_PIIX', if_true: files('piix.c', 'ioport.c'))
|
||||
system_ss.add(when: 'CONFIG_IDE_SII3112', if_true: files('sii3112.c'))
|
||||
system_ss.add(when: 'CONFIG_IDE_VIA', if_true: files('via.c'))
|
||||
system_ss.add(when: 'CONFIG_MICRODRIVE', if_true: files('microdrive.c'))
|
||||
|
@ -1,644 +0,0 @@
|
||||
/*
|
||||
* QEMU IDE Emulation: microdrive (CF / PCMCIA)
|
||||
*
|
||||
* Copyright (c) 2003 Fabrice Bellard
|
||||
* Copyright (c) 2006 Openedhand Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/pcmcia.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/module.h"
|
||||
#include "sysemu/dma.h"
|
||||
#include "hw/irq.h"
|
||||
|
||||
#include "qom/object.h"
|
||||
#include "ide-internal.h"
|
||||
|
||||
#define TYPE_MICRODRIVE "microdrive"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(MicroDriveState, MICRODRIVE)
|
||||
|
||||
/***********************************************************/
|
||||
/* CF-ATA Microdrive */
|
||||
|
||||
#define METADATA_SIZE 0x20
|
||||
|
||||
/* DSCM-1XXXX Microdrive hard disk with CF+ II / PCMCIA interface. */
|
||||
|
||||
struct MicroDriveState {
|
||||
/*< private >*/
|
||||
PCMCIACardState parent_obj;
|
||||
/*< public >*/
|
||||
|
||||
IDEBus bus;
|
||||
uint32_t attr_base;
|
||||
uint32_t io_base;
|
||||
|
||||
/* Card state */
|
||||
uint8_t opt;
|
||||
uint8_t stat;
|
||||
uint8_t pins;
|
||||
|
||||
uint8_t ctrl;
|
||||
uint16_t io;
|
||||
uint8_t cycle;
|
||||
};
|
||||
|
||||
/* Register bitfields */
|
||||
enum md_opt {
|
||||
OPT_MODE_MMAP = 0,
|
||||
OPT_MODE_IOMAP16 = 1,
|
||||
OPT_MODE_IOMAP1 = 2,
|
||||
OPT_MODE_IOMAP2 = 3,
|
||||
OPT_MODE = 0x3f,
|
||||
OPT_LEVIREQ = 0x40,
|
||||
OPT_SRESET = 0x80,
|
||||
};
|
||||
enum md_cstat {
|
||||
STAT_INT = 0x02,
|
||||
STAT_PWRDWN = 0x04,
|
||||
STAT_XE = 0x10,
|
||||
STAT_IOIS8 = 0x20,
|
||||
STAT_SIGCHG = 0x40,
|
||||
STAT_CHANGED = 0x80,
|
||||
};
|
||||
enum md_pins {
|
||||
PINS_MRDY = 0x02,
|
||||
PINS_CRDY = 0x20,
|
||||
};
|
||||
enum md_ctrl {
|
||||
CTRL_IEN = 0x02,
|
||||
CTRL_SRST = 0x04,
|
||||
};
|
||||
|
||||
static inline void md_interrupt_update(MicroDriveState *s)
|
||||
{
|
||||
PCMCIACardState *card = PCMCIA_CARD(s);
|
||||
|
||||
if (card->slot == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
qemu_set_irq(card->slot->irq,
|
||||
!(s->stat & STAT_INT) && /* Inverted */
|
||||
!(s->ctrl & (CTRL_IEN | CTRL_SRST)) &&
|
||||
!(s->opt & OPT_SRESET));
|
||||
}
|
||||
|
||||
static void md_set_irq(void *opaque, int irq, int level)
|
||||
{
|
||||
MicroDriveState *s = opaque;
|
||||
|
||||
if (level) {
|
||||
s->stat |= STAT_INT;
|
||||
} else {
|
||||
s->stat &= ~STAT_INT;
|
||||
}
|
||||
|
||||
md_interrupt_update(s);
|
||||
}
|
||||
|
||||
static void md_reset(DeviceState *dev)
|
||||
{
|
||||
MicroDriveState *s = MICRODRIVE(dev);
|
||||
|
||||
s->opt = OPT_MODE_MMAP;
|
||||
s->stat = 0;
|
||||
s->pins = 0;
|
||||
s->cycle = 0;
|
||||
s->ctrl = 0;
|
||||
ide_bus_reset(&s->bus);
|
||||
}
|
||||
|
||||
static uint8_t md_attr_read(PCMCIACardState *card, uint32_t at)
|
||||
{
|
||||
MicroDriveState *s = MICRODRIVE(card);
|
||||
PCMCIACardClass *pcc = PCMCIA_CARD_GET_CLASS(card);
|
||||
|
||||
if (at < s->attr_base) {
|
||||
if (at < pcc->cis_len) {
|
||||
return pcc->cis[at];
|
||||
} else {
|
||||
return 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
at -= s->attr_base;
|
||||
|
||||
switch (at) {
|
||||
case 0x00: /* Configuration Option Register */
|
||||
return s->opt;
|
||||
case 0x02: /* Card Configuration Status Register */
|
||||
if (s->ctrl & CTRL_IEN) {
|
||||
return s->stat & ~STAT_INT;
|
||||
} else {
|
||||
return s->stat;
|
||||
}
|
||||
case 0x04: /* Pin Replacement Register */
|
||||
return (s->pins & PINS_CRDY) | 0x0c;
|
||||
case 0x06: /* Socket and Copy Register */
|
||||
return 0x00;
|
||||
#ifdef VERBOSE
|
||||
default:
|
||||
printf("%s: Bad attribute space register %02x\n", __func__, at);
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void md_attr_write(PCMCIACardState *card, uint32_t at, uint8_t value)
|
||||
{
|
||||
MicroDriveState *s = MICRODRIVE(card);
|
||||
|
||||
at -= s->attr_base;
|
||||
|
||||
switch (at) {
|
||||
case 0x00: /* Configuration Option Register */
|
||||
s->opt = value & 0xcf;
|
||||
if (value & OPT_SRESET) {
|
||||
device_cold_reset(DEVICE(s));
|
||||
}
|
||||
md_interrupt_update(s);
|
||||
break;
|
||||
case 0x02: /* Card Configuration Status Register */
|
||||
if ((s->stat ^ value) & STAT_PWRDWN) {
|
||||
s->pins |= PINS_CRDY;
|
||||
}
|
||||
s->stat &= 0x82;
|
||||
s->stat |= value & 0x74;
|
||||
md_interrupt_update(s);
|
||||
/* Word 170 in Identify Device must be equal to STAT_XE */
|
||||
break;
|
||||
case 0x04: /* Pin Replacement Register */
|
||||
s->pins &= PINS_CRDY;
|
||||
s->pins |= value & PINS_MRDY;
|
||||
break;
|
||||
case 0x06: /* Socket and Copy Register */
|
||||
break;
|
||||
default:
|
||||
printf("%s: Bad attribute space register %02x\n", __func__, at);
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t md_common_read(PCMCIACardState *card, uint32_t at)
|
||||
{
|
||||
MicroDriveState *s = MICRODRIVE(card);
|
||||
IDEState *ifs;
|
||||
uint16_t ret;
|
||||
at -= s->io_base;
|
||||
|
||||
switch (s->opt & OPT_MODE) {
|
||||
case OPT_MODE_MMAP:
|
||||
if ((at & ~0x3ff) == 0x400) {
|
||||
at = 0;
|
||||
}
|
||||
break;
|
||||
case OPT_MODE_IOMAP16:
|
||||
at &= 0xf;
|
||||
break;
|
||||
case OPT_MODE_IOMAP1:
|
||||
if ((at & ~0xf) == 0x3f0) {
|
||||
at -= 0x3e8;
|
||||
} else if ((at & ~0xf) == 0x1f0) {
|
||||
at -= 0x1f0;
|
||||
}
|
||||
break;
|
||||
case OPT_MODE_IOMAP2:
|
||||
if ((at & ~0xf) == 0x370) {
|
||||
at -= 0x368;
|
||||
} else if ((at & ~0xf) == 0x170) {
|
||||
at -= 0x170;
|
||||
}
|
||||
}
|
||||
|
||||
switch (at) {
|
||||
case 0x0: /* Even RD Data */
|
||||
case 0x8:
|
||||
return ide_data_readw(&s->bus, 0);
|
||||
|
||||
/* TODO: 8-bit accesses */
|
||||
if (s->cycle) {
|
||||
ret = s->io >> 8;
|
||||
} else {
|
||||
s->io = ide_data_readw(&s->bus, 0);
|
||||
ret = s->io & 0xff;
|
||||
}
|
||||
s->cycle = !s->cycle;
|
||||
return ret;
|
||||
case 0x9: /* Odd RD Data */
|
||||
return s->io >> 8;
|
||||
case 0xd: /* Error */
|
||||
return ide_ioport_read(&s->bus, 0x1);
|
||||
case 0xe: /* Alternate Status */
|
||||
ifs = ide_bus_active_if(&s->bus);
|
||||
if (ifs->blk) {
|
||||
return ifs->status;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
case 0xf: /* Device Address */
|
||||
ifs = ide_bus_active_if(&s->bus);
|
||||
return 0xc2 | ((~ifs->select << 2) & 0x3c);
|
||||
default:
|
||||
return ide_ioport_read(&s->bus, at);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void md_common_write(PCMCIACardState *card, uint32_t at, uint16_t value)
|
||||
{
|
||||
MicroDriveState *s = MICRODRIVE(card);
|
||||
at -= s->io_base;
|
||||
|
||||
switch (s->opt & OPT_MODE) {
|
||||
case OPT_MODE_MMAP:
|
||||
if ((at & ~0x3ff) == 0x400) {
|
||||
at = 0;
|
||||
}
|
||||
break;
|
||||
case OPT_MODE_IOMAP16:
|
||||
at &= 0xf;
|
||||
break;
|
||||
case OPT_MODE_IOMAP1:
|
||||
if ((at & ~0xf) == 0x3f0) {
|
||||
at -= 0x3e8;
|
||||
} else if ((at & ~0xf) == 0x1f0) {
|
||||
at -= 0x1f0;
|
||||
}
|
||||
break;
|
||||
case OPT_MODE_IOMAP2:
|
||||
if ((at & ~0xf) == 0x370) {
|
||||
at -= 0x368;
|
||||
} else if ((at & ~0xf) == 0x170) {
|
||||
at -= 0x170;
|
||||
}
|
||||
}
|
||||
|
||||
switch (at) {
|
||||
case 0x0: /* Even WR Data */
|
||||
case 0x8:
|
||||
ide_data_writew(&s->bus, 0, value);
|
||||
break;
|
||||
|
||||
/* TODO: 8-bit accesses */
|
||||
if (s->cycle) {
|
||||
ide_data_writew(&s->bus, 0, s->io | (value << 8));
|
||||
} else {
|
||||
s->io = value & 0xff;
|
||||
}
|
||||
s->cycle = !s->cycle;
|
||||
break;
|
||||
case 0x9:
|
||||
s->io = value & 0xff;
|
||||
s->cycle = !s->cycle;
|
||||
break;
|
||||
case 0xd: /* Features */
|
||||
ide_ioport_write(&s->bus, 0x1, value);
|
||||
break;
|
||||
case 0xe: /* Device Control */
|
||||
s->ctrl = value;
|
||||
if (value & CTRL_SRST) {
|
||||
device_cold_reset(DEVICE(s));
|
||||
}
|
||||
md_interrupt_update(s);
|
||||
break;
|
||||
default:
|
||||
if (s->stat & STAT_PWRDWN) {
|
||||
s->pins |= PINS_CRDY;
|
||||
s->stat &= ~STAT_PWRDWN;
|
||||
}
|
||||
ide_ioport_write(&s->bus, at, value);
|
||||
}
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_microdrive = {
|
||||
.name = "microdrive",
|
||||
.version_id = 3,
|
||||
.minimum_version_id = 0,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT8(opt, MicroDriveState),
|
||||
VMSTATE_UINT8(stat, MicroDriveState),
|
||||
VMSTATE_UINT8(pins, MicroDriveState),
|
||||
VMSTATE_UINT8(ctrl, MicroDriveState),
|
||||
VMSTATE_UINT16(io, MicroDriveState),
|
||||
VMSTATE_UINT8(cycle, MicroDriveState),
|
||||
VMSTATE_IDE_BUS(bus, MicroDriveState),
|
||||
VMSTATE_IDE_DRIVES(bus.ifs, MicroDriveState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const uint8_t dscm1xxxx_cis[0x14a] = {
|
||||
[0x000] = CISTPL_DEVICE, /* 5V Device Information */
|
||||
[0x002] = 0x03, /* Tuple length = 4 bytes */
|
||||
[0x004] = 0xdb, /* ID: DTYPE_FUNCSPEC, non WP, DSPEED_150NS */
|
||||
[0x006] = 0x01, /* Size = 2K bytes */
|
||||
[0x008] = CISTPL_ENDMARK,
|
||||
|
||||
[0x00a] = CISTPL_DEVICE_OC, /* Additional Device Information */
|
||||
[0x00c] = 0x04, /* Tuple length = 4 byest */
|
||||
[0x00e] = 0x03, /* Conditions: Ext = 0, Vcc 3.3V, MWAIT = 1 */
|
||||
[0x010] = 0xdb, /* ID: DTYPE_FUNCSPEC, non WP, DSPEED_150NS */
|
||||
[0x012] = 0x01, /* Size = 2K bytes */
|
||||
[0x014] = CISTPL_ENDMARK,
|
||||
|
||||
[0x016] = CISTPL_JEDEC_C, /* JEDEC ID */
|
||||
[0x018] = 0x02, /* Tuple length = 2 bytes */
|
||||
[0x01a] = 0xdf, /* PC Card ATA with no Vpp required */
|
||||
[0x01c] = 0x01,
|
||||
|
||||
[0x01e] = CISTPL_MANFID, /* Manufacture ID */
|
||||
[0x020] = 0x04, /* Tuple length = 4 bytes */
|
||||
[0x022] = 0xa4, /* TPLMID_MANF = 00a4 (IBM) */
|
||||
[0x024] = 0x00,
|
||||
[0x026] = 0x00, /* PLMID_CARD = 0000 */
|
||||
[0x028] = 0x00,
|
||||
|
||||
[0x02a] = CISTPL_VERS_1, /* Level 1 Version */
|
||||
[0x02c] = 0x12, /* Tuple length = 23 bytes */
|
||||
[0x02e] = 0x04, /* Major Version = JEIDA 4.2 / PCMCIA 2.1 */
|
||||
[0x030] = 0x01, /* Minor Version = 1 */
|
||||
[0x032] = 'I',
|
||||
[0x034] = 'B',
|
||||
[0x036] = 'M',
|
||||
[0x038] = 0x00,
|
||||
[0x03a] = 'm',
|
||||
[0x03c] = 'i',
|
||||
[0x03e] = 'c',
|
||||
[0x040] = 'r',
|
||||
[0x042] = 'o',
|
||||
[0x044] = 'd',
|
||||
[0x046] = 'r',
|
||||
[0x048] = 'i',
|
||||
[0x04a] = 'v',
|
||||
[0x04c] = 'e',
|
||||
[0x04e] = 0x00,
|
||||
[0x050] = CISTPL_ENDMARK,
|
||||
|
||||
[0x052] = CISTPL_FUNCID, /* Function ID */
|
||||
[0x054] = 0x02, /* Tuple length = 2 bytes */
|
||||
[0x056] = 0x04, /* TPLFID_FUNCTION = Fixed Disk */
|
||||
[0x058] = 0x01, /* TPLFID_SYSINIT: POST = 1, ROM = 0 */
|
||||
|
||||
[0x05a] = CISTPL_FUNCE, /* Function Extension */
|
||||
[0x05c] = 0x02, /* Tuple length = 2 bytes */
|
||||
[0x05e] = 0x01, /* TPLFE_TYPE = Disk Device Interface */
|
||||
[0x060] = 0x01, /* TPLFE_DATA = PC Card ATA Interface */
|
||||
|
||||
[0x062] = CISTPL_FUNCE, /* Function Extension */
|
||||
[0x064] = 0x03, /* Tuple length = 3 bytes */
|
||||
[0x066] = 0x02, /* TPLFE_TYPE = Basic PC Card ATA Interface */
|
||||
[0x068] = 0x08, /* TPLFE_DATA: Rotating, Unique, Single */
|
||||
[0x06a] = 0x0f, /* TPLFE_DATA: Sleep, Standby, Idle, Auto */
|
||||
|
||||
[0x06c] = CISTPL_CONFIG, /* Configuration */
|
||||
[0x06e] = 0x05, /* Tuple length = 5 bytes */
|
||||
[0x070] = 0x01, /* TPCC_RASZ = 2 bytes, TPCC_RMSZ = 1 byte */
|
||||
[0x072] = 0x07, /* TPCC_LAST = 7 */
|
||||
[0x074] = 0x00, /* TPCC_RADR = 0200 */
|
||||
[0x076] = 0x02,
|
||||
[0x078] = 0x0f, /* TPCC_RMSK = 200, 202, 204, 206 */
|
||||
|
||||
[0x07a] = CISTPL_CFTABLE_ENTRY, /* 16-bit PC Card Configuration */
|
||||
[0x07c] = 0x0b, /* Tuple length = 11 bytes */
|
||||
[0x07e] = 0xc0, /* TPCE_INDX = Memory Mode, Default, Iface */
|
||||
[0x080] = 0xc0, /* TPCE_IF = Memory, no BVDs, no WP, READY */
|
||||
[0x082] = 0xa1, /* TPCE_FS = Vcc only, no I/O, Memory, Misc */
|
||||
[0x084] = 0x27, /* NomV = 1, MinV = 1, MaxV = 1, Peakl = 1 */
|
||||
[0x086] = 0x55, /* NomV: 5.0 V */
|
||||
[0x088] = 0x4d, /* MinV: 4.5 V */
|
||||
[0x08a] = 0x5d, /* MaxV: 5.5 V */
|
||||
[0x08c] = 0x4e, /* Peakl: 450 mA */
|
||||
[0x08e] = 0x08, /* TPCE_MS = 1 window, 1 byte, Host address */
|
||||
[0x090] = 0x00, /* Window descriptor: Window length = 0 */
|
||||
[0x092] = 0x20, /* TPCE_MI: support power down mode, RW */
|
||||
|
||||
[0x094] = CISTPL_CFTABLE_ENTRY, /* 16-bit PC Card Configuration */
|
||||
[0x096] = 0x06, /* Tuple length = 6 bytes */
|
||||
[0x098] = 0x00, /* TPCE_INDX = Memory Mode, no Default */
|
||||
[0x09a] = 0x01, /* TPCE_FS = Vcc only, no I/O, no Memory */
|
||||
[0x09c] = 0x21, /* NomV = 1, MinV = 0, MaxV = 0, Peakl = 1 */
|
||||
[0x09e] = 0xb5, /* NomV: 3.3 V */
|
||||
[0x0a0] = 0x1e,
|
||||
[0x0a2] = 0x3e, /* Peakl: 350 mA */
|
||||
|
||||
[0x0a4] = CISTPL_CFTABLE_ENTRY, /* 16-bit PC Card Configuration */
|
||||
[0x0a6] = 0x0d, /* Tuple length = 13 bytes */
|
||||
[0x0a8] = 0xc1, /* TPCE_INDX = I/O and Memory Mode, Default */
|
||||
[0x0aa] = 0x41, /* TPCE_IF = I/O and Memory, no BVD, no WP */
|
||||
[0x0ac] = 0x99, /* TPCE_FS = Vcc only, I/O, Interrupt, Misc */
|
||||
[0x0ae] = 0x27, /* NomV = 1, MinV = 1, MaxV = 1, Peakl = 1 */
|
||||
[0x0b0] = 0x55, /* NomV: 5.0 V */
|
||||
[0x0b2] = 0x4d, /* MinV: 4.5 V */
|
||||
[0x0b4] = 0x5d, /* MaxV: 5.5 V */
|
||||
[0x0b6] = 0x4e, /* Peakl: 450 mA */
|
||||
[0x0b8] = 0x64, /* TPCE_IO = 16-byte boundary, 16/8 accesses */
|
||||
[0x0ba] = 0xf0, /* TPCE_IR = MASK, Level, Pulse, Share */
|
||||
[0x0bc] = 0xff, /* IRQ0..IRQ7 supported */
|
||||
[0x0be] = 0xff, /* IRQ8..IRQ15 supported */
|
||||
[0x0c0] = 0x20, /* TPCE_MI = support power down mode */
|
||||
|
||||
[0x0c2] = CISTPL_CFTABLE_ENTRY, /* 16-bit PC Card Configuration */
|
||||
[0x0c4] = 0x06, /* Tuple length = 6 bytes */
|
||||
[0x0c6] = 0x01, /* TPCE_INDX = I/O and Memory Mode */
|
||||
[0x0c8] = 0x01, /* TPCE_FS = Vcc only, no I/O, no Memory */
|
||||
[0x0ca] = 0x21, /* NomV = 1, MinV = 0, MaxV = 0, Peakl = 1 */
|
||||
[0x0cc] = 0xb5, /* NomV: 3.3 V */
|
||||
[0x0ce] = 0x1e,
|
||||
[0x0d0] = 0x3e, /* Peakl: 350 mA */
|
||||
|
||||
[0x0d2] = CISTPL_CFTABLE_ENTRY, /* 16-bit PC Card Configuration */
|
||||
[0x0d4] = 0x12, /* Tuple length = 18 bytes */
|
||||
[0x0d6] = 0xc2, /* TPCE_INDX = I/O Primary Mode */
|
||||
[0x0d8] = 0x41, /* TPCE_IF = I/O and Memory, no BVD, no WP */
|
||||
[0x0da] = 0x99, /* TPCE_FS = Vcc only, I/O, Interrupt, Misc */
|
||||
[0x0dc] = 0x27, /* NomV = 1, MinV = 1, MaxV = 1, Peakl = 1 */
|
||||
[0x0de] = 0x55, /* NomV: 5.0 V */
|
||||
[0x0e0] = 0x4d, /* MinV: 4.5 V */
|
||||
[0x0e2] = 0x5d, /* MaxV: 5.5 V */
|
||||
[0x0e4] = 0x4e, /* Peakl: 450 mA */
|
||||
[0x0e6] = 0xea, /* TPCE_IO = 1K boundary, 16/8 access, Range */
|
||||
[0x0e8] = 0x61, /* Range: 2 fields, 2 bytes addr, 1 byte len */
|
||||
[0x0ea] = 0xf0, /* Field 1 address = 0x01f0 */
|
||||
[0x0ec] = 0x01,
|
||||
[0x0ee] = 0x07, /* Address block length = 8 */
|
||||
[0x0f0] = 0xf6, /* Field 2 address = 0x03f6 */
|
||||
[0x0f2] = 0x03,
|
||||
[0x0f4] = 0x01, /* Address block length = 2 */
|
||||
[0x0f6] = 0xee, /* TPCE_IR = IRQ E, Level, Pulse, Share */
|
||||
[0x0f8] = 0x20, /* TPCE_MI = support power down mode */
|
||||
|
||||
[0x0fa] = CISTPL_CFTABLE_ENTRY, /* 16-bit PC Card Configuration */
|
||||
[0x0fc] = 0x06, /* Tuple length = 6 bytes */
|
||||
[0x0fe] = 0x02, /* TPCE_INDX = I/O Primary Mode, no Default */
|
||||
[0x100] = 0x01, /* TPCE_FS = Vcc only, no I/O, no Memory */
|
||||
[0x102] = 0x21, /* NomV = 1, MinV = 0, MaxV = 0, Peakl = 1 */
|
||||
[0x104] = 0xb5, /* NomV: 3.3 V */
|
||||
[0x106] = 0x1e,
|
||||
[0x108] = 0x3e, /* Peakl: 350 mA */
|
||||
|
||||
[0x10a] = CISTPL_CFTABLE_ENTRY, /* 16-bit PC Card Configuration */
|
||||
[0x10c] = 0x12, /* Tuple length = 18 bytes */
|
||||
[0x10e] = 0xc3, /* TPCE_INDX = I/O Secondary Mode, Default */
|
||||
[0x110] = 0x41, /* TPCE_IF = I/O and Memory, no BVD, no WP */
|
||||
[0x112] = 0x99, /* TPCE_FS = Vcc only, I/O, Interrupt, Misc */
|
||||
[0x114] = 0x27, /* NomV = 1, MinV = 1, MaxV = 1, Peakl = 1 */
|
||||
[0x116] = 0x55, /* NomV: 5.0 V */
|
||||
[0x118] = 0x4d, /* MinV: 4.5 V */
|
||||
[0x11a] = 0x5d, /* MaxV: 5.5 V */
|
||||
[0x11c] = 0x4e, /* Peakl: 450 mA */
|
||||
[0x11e] = 0xea, /* TPCE_IO = 1K boundary, 16/8 access, Range */
|
||||
[0x120] = 0x61, /* Range: 2 fields, 2 byte addr, 1 byte len */
|
||||
[0x122] = 0x70, /* Field 1 address = 0x0170 */
|
||||
[0x124] = 0x01,
|
||||
[0x126] = 0x07, /* Address block length = 8 */
|
||||
[0x128] = 0x76, /* Field 2 address = 0x0376 */
|
||||
[0x12a] = 0x03,
|
||||
[0x12c] = 0x01, /* Address block length = 2 */
|
||||
[0x12e] = 0xee, /* TPCE_IR = IRQ E, Level, Pulse, Share */
|
||||
[0x130] = 0x20, /* TPCE_MI = support power down mode */
|
||||
|
||||
[0x132] = CISTPL_CFTABLE_ENTRY, /* 16-bit PC Card Configuration */
|
||||
[0x134] = 0x06, /* Tuple length = 6 bytes */
|
||||
[0x136] = 0x03, /* TPCE_INDX = I/O Secondary Mode */
|
||||
[0x138] = 0x01, /* TPCE_FS = Vcc only, no I/O, no Memory */
|
||||
[0x13a] = 0x21, /* NomV = 1, MinV = 0, MaxV = 0, Peakl = 1 */
|
||||
[0x13c] = 0xb5, /* NomV: 3.3 V */
|
||||
[0x13e] = 0x1e,
|
||||
[0x140] = 0x3e, /* Peakl: 350 mA */
|
||||
|
||||
[0x142] = CISTPL_NO_LINK, /* No Link */
|
||||
[0x144] = 0x00, /* Tuple length = 0 bytes */
|
||||
|
||||
[0x146] = CISTPL_END, /* Tuple End */
|
||||
};
|
||||
|
||||
#define TYPE_DSCM1XXXX "dscm1xxxx"
|
||||
|
||||
static int dscm1xxxx_attach(PCMCIACardState *card)
|
||||
{
|
||||
MicroDriveState *md = MICRODRIVE(card);
|
||||
PCMCIACardClass *pcc = PCMCIA_CARD_GET_CLASS(card);
|
||||
|
||||
md->attr_base = pcc->cis[0x74] | (pcc->cis[0x76] << 8);
|
||||
md->io_base = 0x0;
|
||||
|
||||
device_cold_reset(DEVICE(md));
|
||||
md_interrupt_update(md);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dscm1xxxx_detach(PCMCIACardState *card)
|
||||
{
|
||||
MicroDriveState *md = MICRODRIVE(card);
|
||||
|
||||
device_cold_reset(DEVICE(md));
|
||||
return 0;
|
||||
}
|
||||
|
||||
PCMCIACardState *dscm1xxxx_init(DriveInfo *dinfo)
|
||||
{
|
||||
MicroDriveState *md;
|
||||
|
||||
md = MICRODRIVE(object_new(TYPE_DSCM1XXXX));
|
||||
qdev_realize(DEVICE(md), NULL, &error_fatal);
|
||||
|
||||
if (dinfo != NULL) {
|
||||
ide_bus_create_drive(&md->bus, 0, dinfo);
|
||||
}
|
||||
md->bus.ifs[0].drive_kind = IDE_CFATA;
|
||||
md->bus.ifs[0].mdata_size = METADATA_SIZE;
|
||||
md->bus.ifs[0].mdata_storage = g_malloc0(METADATA_SIZE);
|
||||
|
||||
return PCMCIA_CARD(md);
|
||||
}
|
||||
|
||||
static void dscm1xxxx_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
PCMCIACardClass *pcc = PCMCIA_CARD_CLASS(oc);
|
||||
DeviceClass *dc = DEVICE_CLASS(oc);
|
||||
|
||||
pcc->cis = dscm1xxxx_cis;
|
||||
pcc->cis_len = sizeof(dscm1xxxx_cis);
|
||||
|
||||
pcc->attach = dscm1xxxx_attach;
|
||||
pcc->detach = dscm1xxxx_detach;
|
||||
/* Reason: Needs to be wired-up in code, see dscm1xxxx_init() */
|
||||
dc->user_creatable = false;
|
||||
}
|
||||
|
||||
static const TypeInfo dscm1xxxx_type_info = {
|
||||
.name = TYPE_DSCM1XXXX,
|
||||
.parent = TYPE_MICRODRIVE,
|
||||
.class_init = dscm1xxxx_class_init,
|
||||
};
|
||||
|
||||
static void microdrive_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
MicroDriveState *md = MICRODRIVE(dev);
|
||||
|
||||
ide_bus_init_output_irq(&md->bus, qemu_allocate_irq(md_set_irq, md, 0));
|
||||
}
|
||||
|
||||
static void microdrive_init(Object *obj)
|
||||
{
|
||||
MicroDriveState *md = MICRODRIVE(obj);
|
||||
|
||||
ide_bus_init(&md->bus, sizeof(md->bus), DEVICE(obj), 0, 1);
|
||||
}
|
||||
|
||||
static void microdrive_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(oc);
|
||||
PCMCIACardClass *pcc = PCMCIA_CARD_CLASS(oc);
|
||||
|
||||
pcc->attr_read = md_attr_read;
|
||||
pcc->attr_write = md_attr_write;
|
||||
pcc->common_read = md_common_read;
|
||||
pcc->common_write = md_common_write;
|
||||
pcc->io_read = md_common_read;
|
||||
pcc->io_write = md_common_write;
|
||||
|
||||
dc->realize = microdrive_realize;
|
||||
device_class_set_legacy_reset(dc, md_reset);
|
||||
dc->vmsd = &vmstate_microdrive;
|
||||
}
|
||||
|
||||
static const TypeInfo microdrive_type_info = {
|
||||
.name = TYPE_MICRODRIVE,
|
||||
.parent = TYPE_PCMCIA_CARD,
|
||||
.instance_size = sizeof(MicroDriveState),
|
||||
.instance_init = microdrive_init,
|
||||
.abstract = true,
|
||||
.class_init = microdrive_class_init,
|
||||
};
|
||||
|
||||
static void microdrive_register_types(void)
|
||||
{
|
||||
type_register_static(µdrive_type_info);
|
||||
type_register_static(&dscm1xxxx_type_info);
|
||||
}
|
||||
|
||||
type_init(microdrive_register_types)
|
@ -781,7 +781,7 @@ static void icv_activate_irq(GICv3CPUState *cs, int idx, int grp)
|
||||
if (nmi) {
|
||||
cs->ich_apr[grp][regno] |= ICV_AP1R_EL1_NMI;
|
||||
} else {
|
||||
cs->ich_apr[grp][regno] |= (1 << regbit);
|
||||
cs->ich_apr[grp][regno] |= (1U << regbit);
|
||||
}
|
||||
}
|
||||
|
||||
@ -793,7 +793,7 @@ static void icv_activate_vlpi(GICv3CPUState *cs)
|
||||
int regno = aprbit / 32;
|
||||
int regbit = aprbit % 32;
|
||||
|
||||
cs->ich_apr[cs->hppvlpi.grp][regno] |= (1 << regbit);
|
||||
cs->ich_apr[cs->hppvlpi.grp][regno] |= (1U << regbit);
|
||||
gicv3_redist_vlpi_pending(cs, cs->hppvlpi.irq, 0);
|
||||
}
|
||||
|
||||
@ -1170,7 +1170,7 @@ static void icc_activate_irq(GICv3CPUState *cs, int irq)
|
||||
if (nmi) {
|
||||
cs->icc_apr[cs->hppi.grp][regno] |= ICC_AP1R_EL1_NMI;
|
||||
} else {
|
||||
cs->icc_apr[cs->hppi.grp][regno] |= (1 << regbit);
|
||||
cs->icc_apr[cs->hppi.grp][regno] |= (1U << regbit);
|
||||
}
|
||||
|
||||
if (irq < GIC_INTERNAL) {
|
||||
|
@ -392,22 +392,15 @@ static void omap_intc_class_init(ObjectClass *klass, void *data)
|
||||
}
|
||||
|
||||
static const TypeInfo omap_intc_info = {
|
||||
.name = "omap-intc",
|
||||
.parent = TYPE_OMAP_INTC,
|
||||
.name = TYPE_OMAP_INTC,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(OMAPIntcState),
|
||||
.instance_init = omap_intc_init,
|
||||
.class_init = omap_intc_class_init,
|
||||
};
|
||||
|
||||
static const TypeInfo omap_intc_type_info = {
|
||||
.name = TYPE_OMAP_INTC,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(OMAPIntcState),
|
||||
.abstract = true,
|
||||
};
|
||||
|
||||
static void omap_intc_register_types(void)
|
||||
{
|
||||
type_register_static(&omap_intc_type_info);
|
||||
type_register_static(&omap_intc_info);
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,6 @@ subdir('nvram')
|
||||
subdir('pci')
|
||||
subdir('pci-bridge')
|
||||
subdir('pci-host')
|
||||
subdir('pcmcia')
|
||||
subdir('rtc')
|
||||
subdir('scsi')
|
||||
subdir('sd')
|
||||
|
@ -74,7 +74,6 @@ config IVSHMEM_DEVICE
|
||||
|
||||
config ECCMEMCTL
|
||||
bool
|
||||
select ECC
|
||||
|
||||
config IMX
|
||||
bool
|
||||
@ -82,6 +81,9 @@ config IMX
|
||||
select SSI
|
||||
select USB_EHCI_SYSBUS
|
||||
|
||||
config STM32_RCC
|
||||
bool
|
||||
|
||||
config STM32F2XX_SYSCFG
|
||||
bool
|
||||
|
||||
|
@ -100,6 +100,7 @@ system_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files(
|
||||
system_ss.add(when: 'CONFIG_XLNX_VERSAL_TRNG', if_true: files(
|
||||
'xlnx-versal-trng.c',
|
||||
))
|
||||
system_ss.add(when: 'CONFIG_STM32_RCC', if_true: files('stm32_rcc.c'))
|
||||
system_ss.add(when: 'CONFIG_STM32F2XX_SYSCFG', if_true: files('stm32f2xx_syscfg.c'))
|
||||
system_ss.add(when: 'CONFIG_STM32F4XX_SYSCFG', if_true: files('stm32f4xx_syscfg.c'))
|
||||
system_ss.add(when: 'CONFIG_STM32F4XX_EXTI', if_true: files('stm32f4xx_exti.c'))
|
||||
|
162
hw/misc/stm32_rcc.c
Normal file
162
hw/misc/stm32_rcc.c
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* STM32 RCC (only reset and enable registers are implemented)
|
||||
*
|
||||
* Copyright (c) 2024 Román Cárdenas <rcardenas.rod@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/log.h"
|
||||
#include "trace.h"
|
||||
#include "hw/irq.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "hw/misc/stm32_rcc.h"
|
||||
|
||||
static void stm32_rcc_reset(DeviceState *dev)
|
||||
{
|
||||
STM32RccState *s = STM32_RCC(dev);
|
||||
|
||||
for (int i = 0; i < STM32_RCC_NREGS; i++) {
|
||||
s->regs[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t stm32_rcc_read(void *opaque, hwaddr addr, unsigned int size)
|
||||
{
|
||||
STM32RccState *s = STM32_RCC(opaque);
|
||||
|
||||
uint32_t value = 0;
|
||||
if (addr > STM32_RCC_DCKCFGR2) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%"HWADDR_PRIx"\n",
|
||||
__func__, addr);
|
||||
} else {
|
||||
value = s->regs[addr >> 2];
|
||||
}
|
||||
trace_stm32_rcc_read(addr, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static void stm32_rcc_write(void *opaque, hwaddr addr,
|
||||
uint64_t val64, unsigned int size)
|
||||
{
|
||||
STM32RccState *s = STM32_RCC(opaque);
|
||||
uint32_t value = val64;
|
||||
uint32_t prev_value, new_value, irq_offset;
|
||||
|
||||
trace_stm32_rcc_write(value, addr);
|
||||
|
||||
if (addr > STM32_RCC_DCKCFGR2) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%"HWADDR_PRIx"\n",
|
||||
__func__, addr);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (addr) {
|
||||
case STM32_RCC_AHB1_RSTR...STM32_RCC_APB2_RSTR:
|
||||
prev_value = s->regs[addr / 4];
|
||||
s->regs[addr / 4] = value;
|
||||
|
||||
irq_offset = ((addr - STM32_RCC_AHB1_RSTR) / 4) * 32;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
new_value = extract32(value, i, 1);
|
||||
if (extract32(prev_value, i, 1) && !new_value) {
|
||||
trace_stm32_rcc_pulse_reset(irq_offset + i, new_value);
|
||||
qemu_set_irq(s->reset_irq[irq_offset + i], new_value);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case STM32_RCC_AHB1_ENR...STM32_RCC_APB2_ENR:
|
||||
prev_value = s->regs[addr / 4];
|
||||
s->regs[addr / 4] = value;
|
||||
|
||||
irq_offset = ((addr - STM32_RCC_AHB1_ENR) / 4) * 32;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
new_value = extract32(value, i, 1);
|
||||
if (!extract32(prev_value, i, 1) && new_value) {
|
||||
trace_stm32_rcc_pulse_enable(irq_offset + i, new_value);
|
||||
qemu_set_irq(s->enable_irq[irq_offset + i], new_value);
|
||||
}
|
||||
}
|
||||
return;
|
||||
default:
|
||||
qemu_log_mask(
|
||||
LOG_UNIMP,
|
||||
"%s: The RCC peripheral only supports enable and reset in QEMU\n",
|
||||
__func__
|
||||
);
|
||||
s->regs[addr >> 2] = value;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps stm32_rcc_ops = {
|
||||
.read = stm32_rcc_read,
|
||||
.write = stm32_rcc_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
};
|
||||
|
||||
static void stm32_rcc_init(Object *obj)
|
||||
{
|
||||
STM32RccState *s = STM32_RCC(obj);
|
||||
|
||||
memory_region_init_io(&s->mmio, obj, &stm32_rcc_ops, s,
|
||||
TYPE_STM32_RCC, STM32_RCC_PERIPHERAL_SIZE);
|
||||
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
|
||||
|
||||
qdev_init_gpio_out(DEVICE(obj), s->reset_irq, STM32_RCC_NIRQS);
|
||||
qdev_init_gpio_out(DEVICE(obj), s->enable_irq, STM32_RCC_NIRQS);
|
||||
|
||||
for (int i = 0; i < STM32_RCC_NIRQS; i++) {
|
||||
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->reset_irq[i]);
|
||||
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->enable_irq[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_stm32_rcc = {
|
||||
.name = TYPE_STM32_RCC,
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT32_ARRAY(regs, STM32RccState, STM32_RCC_NREGS),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void stm32_rcc_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->vmsd = &vmstate_stm32_rcc;
|
||||
device_class_set_legacy_reset(dc, stm32_rcc_reset);
|
||||
}
|
||||
|
||||
static const TypeInfo stm32_rcc_info = {
|
||||
.name = TYPE_STM32_RCC,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(STM32RccState),
|
||||
.instance_init = stm32_rcc_init,
|
||||
.class_init = stm32_rcc_class_init,
|
||||
};
|
||||
|
||||
static void stm32_rcc_register_types(void)
|
||||
{
|
||||
type_register_static(&stm32_rcc_info);
|
||||
}
|
||||
|
||||
type_init(stm32_rcc_register_types)
|
@ -26,6 +26,9 @@
|
||||
#include "trace.h"
|
||||
#include "hw/irq.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "hw/clock.h"
|
||||
#include "hw/qdev-clock.h"
|
||||
#include "qapi/error.h"
|
||||
#include "hw/misc/stm32l4x5_syscfg.h"
|
||||
#include "hw/gpio/stm32l4x5_gpio.h"
|
||||
|
||||
@ -225,12 +228,22 @@ static void stm32l4x5_syscfg_init(Object *obj)
|
||||
qdev_init_gpio_in(DEVICE(obj), stm32l4x5_syscfg_set_irq,
|
||||
GPIO_NUM_PINS * NUM_GPIOS);
|
||||
qdev_init_gpio_out(DEVICE(obj), s->gpio_out, GPIO_NUM_PINS);
|
||||
s->clk = qdev_init_clock_in(DEVICE(s), "clk", NULL, s, 0);
|
||||
}
|
||||
|
||||
static void stm32l4x5_syscfg_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
Stm32l4x5SyscfgState *s = STM32L4X5_SYSCFG(dev);
|
||||
if (!clock_has_source(s->clk)) {
|
||||
error_setg(errp, "SYSCFG: clk input must be connected");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_stm32l4x5_syscfg = {
|
||||
.name = TYPE_STM32L4X5_SYSCFG,
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.version_id = 2,
|
||||
.minimum_version_id = 2,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32(memrmp, Stm32l4x5SyscfgState),
|
||||
VMSTATE_UINT32(cfgr1, Stm32l4x5SyscfgState),
|
||||
@ -241,6 +254,7 @@ static const VMStateDescription vmstate_stm32l4x5_syscfg = {
|
||||
VMSTATE_UINT32(swpr, Stm32l4x5SyscfgState),
|
||||
VMSTATE_UINT32(skr, Stm32l4x5SyscfgState),
|
||||
VMSTATE_UINT32(swpr2, Stm32l4x5SyscfgState),
|
||||
VMSTATE_CLOCK(clk, Stm32l4x5SyscfgState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
@ -251,6 +265,7 @@ static void stm32l4x5_syscfg_class_init(ObjectClass *klass, void *data)
|
||||
ResettableClass *rc = RESETTABLE_CLASS(klass);
|
||||
|
||||
dc->vmsd = &vmstate_stm32l4x5_syscfg;
|
||||
dc->realize = stm32l4x5_syscfg_realize;
|
||||
rc->phases.hold = stm32l4x5_syscfg_hold_reset;
|
||||
}
|
||||
|
||||
|
@ -156,6 +156,12 @@ npcm7xx_pwm_write(const char *id, uint64_t offset, uint32_t value) "%s offset: 0
|
||||
npcm7xx_pwm_update_freq(const char *id, uint8_t index, uint32_t old_value, uint32_t new_value) "%s pwm[%u] Update Freq: old_freq: %u, new_freq: %u"
|
||||
npcm7xx_pwm_update_duty(const char *id, uint8_t index, uint32_t old_value, uint32_t new_value) "%s pwm[%u] Update Duty: old_duty: %u, new_duty: %u"
|
||||
|
||||
# stm32_rcc.c
|
||||
stm32_rcc_read(uint64_t addr, uint64_t data) "reg read: addr: 0x%" PRIx64 " val: 0x%" PRIx64 ""
|
||||
stm32_rcc_write(uint64_t addr, uint64_t data) "reg write: addr: 0x%" PRIx64 " val: 0x%" PRIx64 ""
|
||||
stm32_rcc_pulse_enable(int line, int level) "Enable: %d to %d"
|
||||
stm32_rcc_pulse_reset(int line, int level) "Reset: %d to %d"
|
||||
|
||||
# stm32f4xx_syscfg.c
|
||||
stm32f4xx_syscfg_set_irq(int gpio, int line, int level) "Interrupt: GPIO: %d, Line: %d; Level: %d"
|
||||
stm32f4xx_pulse_exti(int irq) "Pulse EXTI: %d"
|
||||
|
@ -1,2 +0,0 @@
|
||||
config PCMCIA
|
||||
bool
|
@ -1 +0,0 @@
|
||||
system_ss.add(when: 'CONFIG_PCMCIA', if_true: files('pcmcia.c'))
|
@ -1,24 +0,0 @@
|
||||
/*
|
||||
* PCMCIA emulation
|
||||
*
|
||||
* Copyright 2013 SUSE LINUX Products GmbH
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/module.h"
|
||||
#include "hw/pcmcia.h"
|
||||
|
||||
static const TypeInfo pcmcia_card_type_info = {
|
||||
.name = TYPE_PCMCIA_CARD,
|
||||
.parent = TYPE_DEVICE,
|
||||
.instance_size = sizeof(PCMCIACardState),
|
||||
.abstract = true,
|
||||
.class_size = sizeof(PCMCIACardClass),
|
||||
};
|
||||
|
||||
static void pcmcia_register_types(void)
|
||||
{
|
||||
type_register_static(&pcmcia_card_type_info);
|
||||
}
|
||||
|
||||
type_init(pcmcia_register_types)
|
@ -28,3 +28,7 @@ config BCM2835_SPI
|
||||
config PNV_SPI
|
||||
bool
|
||||
select SSI
|
||||
|
||||
config ALLWINNER_A10_SPI
|
||||
bool
|
||||
select SSI
|
||||
|
561
hw/ssi/allwinner-a10-spi.c
Normal file
561
hw/ssi/allwinner-a10-spi.c
Normal file
@ -0,0 +1,561 @@
|
||||
/*
|
||||
* Allwinner SPI Bus Serial Interface Emulation
|
||||
*
|
||||
* Copyright (C) 2024 Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/irq.h"
|
||||
#include "hw/ssi/allwinner-a10-spi.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/module.h"
|
||||
#include "trace.h"
|
||||
|
||||
/* Allwinner SPI memory map */
|
||||
#define SPI_RXDATA_REG 0x00 /* receive data register */
|
||||
#define SPI_TXDATA_REG 0x04 /* transmit data register */
|
||||
#define SPI_CTL_REG 0x08 /* control register */
|
||||
#define SPI_INTCTL_REG 0x0c /* interrupt control register */
|
||||
#define SPI_INT_STA_REG 0x10 /* interrupt status register */
|
||||
#define SPI_DMACTL_REG 0x14 /* DMA control register */
|
||||
#define SPI_WAIT_REG 0x18 /* wait clock counter register */
|
||||
#define SPI_CCTL_REG 0x1c /* clock rate control register */
|
||||
#define SPI_BC_REG 0x20 /* burst control register */
|
||||
#define SPI_TC_REG 0x24 /* transmit counter register */
|
||||
#define SPI_FIFO_STA_REG 0x28 /* FIFO status register */
|
||||
|
||||
/* Data register */
|
||||
#define SPI_DATA_RESET 0
|
||||
|
||||
/* Control register */
|
||||
#define SPI_CTL_SDC (1 << 19)
|
||||
#define SPI_CTL_TP_EN (1 << 18)
|
||||
#define SPI_CTL_SS_LEVEL (1 << 17)
|
||||
#define SPI_CTL_SS_CTRL (1 << 16)
|
||||
#define SPI_CTL_DHB (1 << 15)
|
||||
#define SPI_CTL_DDB (1 << 14)
|
||||
#define SPI_CTL_SS (3 << 12)
|
||||
#define SPI_CTL_SS_SHIFT 12
|
||||
#define SPI_CTL_RPSM (1 << 11)
|
||||
#define SPI_CTL_XCH (1 << 10)
|
||||
#define SPI_CTL_RF_RST (1 << 9)
|
||||
#define SPI_CTL_TF_RST (1 << 8)
|
||||
#define SPI_CTL_SSCTL (1 << 7)
|
||||
#define SPI_CTL_LMTF (1 << 6)
|
||||
#define SPI_CTL_DMAMC (1 << 5)
|
||||
#define SPI_CTL_SSPOL (1 << 4)
|
||||
#define SPI_CTL_POL (1 << 3)
|
||||
#define SPI_CTL_PHA (1 << 2)
|
||||
#define SPI_CTL_MODE (1 << 1)
|
||||
#define SPI_CTL_EN (1 << 0)
|
||||
#define SPI_CTL_MASK 0xFFFFFu
|
||||
#define SPI_CTL_RESET 0x0002001Cu
|
||||
|
||||
/* Interrupt control register */
|
||||
#define SPI_INTCTL_SS_INT_EN (1 << 17)
|
||||
#define SPI_INTCTL_TX_INT_EN (1 << 16)
|
||||
#define SPI_INTCTL_TF_UR_INT_EN (1 << 14)
|
||||
#define SPI_INTCTL_TF_OF_INT_EN (1 << 13)
|
||||
#define SPI_INTCTL_TF_E34_INT_EN (1 << 12)
|
||||
#define SPI_INTCTL_TF_E14_INT_EN (1 << 11)
|
||||
#define SPI_INTCTL_TF_FL_INT_EN (1 << 10)
|
||||
#define SPI_INTCTL_TF_HALF_EMP_INT_EN (1 << 9)
|
||||
#define SPI_INTCTL_TF_EMP_INT_EN (1 << 8)
|
||||
#define SPI_INTCTL_RF_UR_INT_EN (1 << 6)
|
||||
#define SPI_INTCTL_RF_OF_INT_EN (1 << 5)
|
||||
#define SPI_INTCTL_RF_E34_INT_EN (1 << 4)
|
||||
#define SPI_INTCTL_RF_E14_INT_EN (1 << 3)
|
||||
#define SPI_INTCTL_RF_FU_INT_EN (1 << 2)
|
||||
#define SPI_INTCTL_RF_HALF_FU_INT_EN (1 << 1)
|
||||
#define SPI_INTCTL_RF_RDY_INT_EN (1 << 0)
|
||||
#define SPI_INTCTL_MASK 0x37F7Fu
|
||||
#define SPI_INTCTL_RESET 0
|
||||
|
||||
/* Interrupt status register */
|
||||
#define SPI_INT_STA_INT_CBF (1 << 31)
|
||||
#define SPI_INT_STA_SSI (1 << 17)
|
||||
#define SPI_INT_STA_TC (1 << 16)
|
||||
#define SPI_INT_STA_TU (1 << 14)
|
||||
#define SPI_INT_STA_TO (1 << 13)
|
||||
#define SPI_INT_STA_TE34 (1 << 12)
|
||||
#define SPI_INT_STA_TE14 (1 << 11)
|
||||
#define SPI_INT_STA_TF (1 << 10)
|
||||
#define SPI_INT_STA_THE (1 << 9)
|
||||
#define SPI_INT_STA_TE (1 << 8)
|
||||
#define SPI_INT_STA_RU (1 << 6)
|
||||
#define SPI_INT_STA_RO (1 << 5)
|
||||
#define SPI_INT_STA_RF34 (1 << 4)
|
||||
#define SPI_INT_STA_RF14 (1 << 3)
|
||||
#define SPI_INT_STA_RF (1 << 2)
|
||||
#define SPI_INT_STA_RHF (1 << 1)
|
||||
#define SPI_INT_STA_RR (1 << 0)
|
||||
#define SPI_INT_STA_MASK 0x80037F7Fu
|
||||
#define SPI_INT_STA_RESET 0x00001B00u
|
||||
|
||||
/* DMA control register - not implemented */
|
||||
#define SPI_DMACTL_RESET 0
|
||||
|
||||
/* Wait clock register */
|
||||
#define SPI_WAIT_REG_WCC_MASK 0xFFFFu
|
||||
#define SPI_WAIT_RESET 0
|
||||
|
||||
/* Clock control register - not implemented */
|
||||
#define SPI_CCTL_RESET 2
|
||||
|
||||
/* Burst count register */
|
||||
#define SPI_BC_BC_MASK 0xFFFFFFu
|
||||
#define SPI_BC_RESET 0
|
||||
|
||||
/* Transmi counter register */
|
||||
#define SPI_TC_WTC_MASK 0xFFFFFFu
|
||||
#define SPI_TC_RESET 0
|
||||
|
||||
/* FIFO status register */
|
||||
#define SPI_FIFO_STA_CNT_MASK 0x7F
|
||||
#define SPI_FIFO_STA_TF_CNT_SHIFT 16
|
||||
#define SPI_FIFO_STA_RF_CNT_SHIFT 0
|
||||
#define SPI_FIFO_STA_RESET 0
|
||||
|
||||
#define REG_INDEX(offset) (offset / sizeof(uint32_t))
|
||||
|
||||
|
||||
static const char *allwinner_a10_spi_get_regname(unsigned offset)
|
||||
{
|
||||
switch (offset) {
|
||||
case SPI_RXDATA_REG:
|
||||
return "RXDATA";
|
||||
case SPI_TXDATA_REG:
|
||||
return "TXDATA";
|
||||
case SPI_CTL_REG:
|
||||
return "CTL";
|
||||
case SPI_INTCTL_REG:
|
||||
return "INTCTL";
|
||||
case SPI_INT_STA_REG:
|
||||
return "INT_STA";
|
||||
case SPI_DMACTL_REG:
|
||||
return "DMACTL";
|
||||
case SPI_WAIT_REG:
|
||||
return "WAIT";
|
||||
case SPI_CCTL_REG:
|
||||
return "CCTL";
|
||||
case SPI_BC_REG:
|
||||
return "BC";
|
||||
case SPI_TC_REG:
|
||||
return "TC";
|
||||
case SPI_FIFO_STA_REG:
|
||||
return "FIFO_STA";
|
||||
default:
|
||||
return "[?]";
|
||||
}
|
||||
}
|
||||
|
||||
static bool allwinner_a10_spi_is_enabled(AWA10SPIState *s)
|
||||
{
|
||||
return s->regs[REG_INDEX(SPI_CTL_REG)] & SPI_CTL_EN;
|
||||
}
|
||||
|
||||
static void allwinner_a10_spi_txfifo_reset(AWA10SPIState *s)
|
||||
{
|
||||
fifo8_reset(&s->tx_fifo);
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] |= (SPI_INT_STA_TE | SPI_INT_STA_TE14 |
|
||||
SPI_INT_STA_THE | SPI_INT_STA_TE34);
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] &= ~(SPI_INT_STA_TU | SPI_INT_STA_TO);
|
||||
}
|
||||
|
||||
static void allwinner_a10_spi_rxfifo_reset(AWA10SPIState *s)
|
||||
{
|
||||
fifo8_reset(&s->rx_fifo);
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] &=
|
||||
~(SPI_INT_STA_RU | SPI_INT_STA_RO | SPI_INT_STA_RF | SPI_INT_STA_RR |
|
||||
SPI_INT_STA_RHF | SPI_INT_STA_RF14 | SPI_INT_STA_RF34);
|
||||
}
|
||||
|
||||
static uint8_t allwinner_a10_spi_selected_channel(AWA10SPIState *s)
|
||||
{
|
||||
return (s->regs[REG_INDEX(SPI_CTL_REG)] & SPI_CTL_SS) >> SPI_CTL_SS_SHIFT;
|
||||
}
|
||||
|
||||
static void allwinner_a10_spi_reset_hold(Object *obj, ResetType type)
|
||||
{
|
||||
AWA10SPIState *s = AW_A10_SPI(obj);
|
||||
|
||||
s->regs[REG_INDEX(SPI_RXDATA_REG)] = SPI_DATA_RESET;
|
||||
s->regs[REG_INDEX(SPI_TXDATA_REG)] = SPI_DATA_RESET;
|
||||
s->regs[REG_INDEX(SPI_CTL_REG)] = SPI_CTL_RESET;
|
||||
s->regs[REG_INDEX(SPI_INTCTL_REG)] = SPI_INTCTL_RESET;
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] = SPI_INT_STA_RESET;
|
||||
s->regs[REG_INDEX(SPI_DMACTL_REG)] = SPI_DMACTL_RESET;
|
||||
s->regs[REG_INDEX(SPI_WAIT_REG)] = SPI_WAIT_RESET;
|
||||
s->regs[REG_INDEX(SPI_CCTL_REG)] = SPI_CCTL_RESET;
|
||||
s->regs[REG_INDEX(SPI_BC_REG)] = SPI_BC_RESET;
|
||||
s->regs[REG_INDEX(SPI_TC_REG)] = SPI_TC_RESET;
|
||||
s->regs[REG_INDEX(SPI_FIFO_STA_REG)] = SPI_FIFO_STA_RESET;
|
||||
|
||||
allwinner_a10_spi_txfifo_reset(s);
|
||||
allwinner_a10_spi_rxfifo_reset(s);
|
||||
}
|
||||
|
||||
static void allwinner_a10_spi_update_irq(AWA10SPIState *s)
|
||||
{
|
||||
bool level;
|
||||
|
||||
if (fifo8_is_empty(&s->rx_fifo)) {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] &= ~SPI_INT_STA_RR;
|
||||
} else {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] |= SPI_INT_STA_RR;
|
||||
}
|
||||
|
||||
if (fifo8_num_used(&s->rx_fifo) >= (AW_A10_SPI_FIFO_SIZE >> 2)) {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] |= SPI_INT_STA_RF14;
|
||||
} else {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] &= ~SPI_INT_STA_RF14;
|
||||
}
|
||||
|
||||
if (fifo8_num_used(&s->rx_fifo) >= (AW_A10_SPI_FIFO_SIZE >> 1)) {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] |= SPI_INT_STA_RHF;
|
||||
} else {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] &= ~SPI_INT_STA_RHF;
|
||||
}
|
||||
|
||||
if (fifo8_num_free(&s->rx_fifo) <= (AW_A10_SPI_FIFO_SIZE >> 2)) {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] |= SPI_INT_STA_RF34;
|
||||
} else {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] &= ~SPI_INT_STA_RF34;
|
||||
}
|
||||
|
||||
if (fifo8_is_full(&s->rx_fifo)) {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] |= SPI_INT_STA_RF;
|
||||
} else {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] &= ~SPI_INT_STA_RF;
|
||||
}
|
||||
|
||||
if (fifo8_is_empty(&s->tx_fifo)) {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] |= SPI_INT_STA_TE;
|
||||
} else {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] &= ~SPI_INT_STA_TE;
|
||||
}
|
||||
|
||||
if (fifo8_num_free(&s->tx_fifo) >= (AW_A10_SPI_FIFO_SIZE >> 2)) {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] |= SPI_INT_STA_TE14;
|
||||
} else {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] &= ~SPI_INT_STA_TE14;
|
||||
}
|
||||
|
||||
if (fifo8_num_free(&s->tx_fifo) >= (AW_A10_SPI_FIFO_SIZE >> 1)) {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] |= SPI_INT_STA_THE;
|
||||
} else {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] &= ~SPI_INT_STA_THE;
|
||||
}
|
||||
|
||||
if (fifo8_num_used(&s->tx_fifo) <= (AW_A10_SPI_FIFO_SIZE >> 2)) {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] |= SPI_INT_STA_TE34;
|
||||
} else {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] &= ~SPI_INT_STA_TE34;
|
||||
}
|
||||
|
||||
if (fifo8_is_full(&s->rx_fifo)) {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] |= SPI_INT_STA_TF;
|
||||
} else {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] &= ~SPI_INT_STA_TF;
|
||||
}
|
||||
|
||||
level = (s->regs[REG_INDEX(SPI_INT_STA_REG)] &
|
||||
s->regs[REG_INDEX(SPI_INTCTL_REG)]) != 0;
|
||||
|
||||
qemu_set_irq(s->irq, level);
|
||||
|
||||
trace_allwinner_a10_spi_update_irq(level);
|
||||
}
|
||||
|
||||
static void allwinner_a10_spi_flush_txfifo(AWA10SPIState *s)
|
||||
{
|
||||
uint32_t burst_count = s->regs[REG_INDEX(SPI_BC_REG)];
|
||||
uint32_t tx_burst = s->regs[REG_INDEX(SPI_TC_REG)];
|
||||
trace_allwinner_a10_spi_burst_length(tx_burst);
|
||||
|
||||
trace_allwinner_a10_spi_flush_txfifo_begin(fifo8_num_used(&s->tx_fifo),
|
||||
fifo8_num_used(&s->rx_fifo));
|
||||
|
||||
while (!fifo8_is_empty(&s->tx_fifo)) {
|
||||
uint8_t tx = fifo8_pop(&s->tx_fifo);
|
||||
uint8_t rx = 0;
|
||||
bool fill_rx = true;
|
||||
|
||||
trace_allwinner_a10_spi_tx(tx);
|
||||
|
||||
/* Write one byte at a time */
|
||||
rx = ssi_transfer(s->bus, tx);
|
||||
|
||||
trace_allwinner_a10_spi_rx(rx);
|
||||
|
||||
/* Check DHB here to determine if RX bytes should be stored */
|
||||
if (s->regs[REG_INDEX(SPI_CTL_REG)] & SPI_CTL_DHB) {
|
||||
/* Store rx bytes only after WTC transfers */
|
||||
if (tx_burst > 0u) {
|
||||
fill_rx = false;
|
||||
tx_burst--;
|
||||
}
|
||||
}
|
||||
|
||||
if (fill_rx) {
|
||||
if (fifo8_is_full(&s->rx_fifo)) {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] |= SPI_INT_STA_RF;
|
||||
} else {
|
||||
fifo8_push(&s->rx_fifo, rx);
|
||||
}
|
||||
}
|
||||
|
||||
allwinner_a10_spi_update_irq(s);
|
||||
|
||||
burst_count--;
|
||||
|
||||
if (burst_count == 0) {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] |= SPI_INT_STA_TC;
|
||||
s->regs[REG_INDEX(SPI_CTL_REG)] &= ~SPI_CTL_XCH;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fifo8_is_empty(&s->tx_fifo)) {
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] |= SPI_INT_STA_TC;
|
||||
s->regs[REG_INDEX(SPI_CTL_REG)] &= ~SPI_CTL_XCH;
|
||||
}
|
||||
|
||||
trace_allwinner_a10_spi_flush_txfifo_end(fifo8_num_used(&s->tx_fifo),
|
||||
fifo8_num_used(&s->rx_fifo));
|
||||
}
|
||||
|
||||
static uint64_t allwinner_a10_spi_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
uint32_t value = 0;
|
||||
AWA10SPIState *s = opaque;
|
||||
uint32_t index = offset >> 2;
|
||||
|
||||
if (offset > SPI_FIFO_STA_REG) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"[%s]%s: Bad register at offset 0x%" HWADDR_PRIx "\n",
|
||||
TYPE_AW_A10_SPI, __func__, offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
value = s->regs[index];
|
||||
|
||||
if (allwinner_a10_spi_is_enabled(s)) {
|
||||
switch (offset) {
|
||||
case SPI_RXDATA_REG:
|
||||
if (fifo8_is_empty(&s->rx_fifo)) {
|
||||
/* value is undefined */
|
||||
value = 0xdeadbeef;
|
||||
} else {
|
||||
/* read from the RX FIFO */
|
||||
value = fifo8_pop(&s->rx_fifo);
|
||||
}
|
||||
break;
|
||||
case SPI_TXDATA_REG:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"[%s]%s: Trying to read from TX FIFO\n",
|
||||
TYPE_AW_A10_SPI, __func__);
|
||||
|
||||
/* Reading from TXDATA gives 0 */
|
||||
break;
|
||||
case SPI_FIFO_STA_REG:
|
||||
/* Read current tx/rx fifo data count */
|
||||
value = fifo8_num_used(&s->tx_fifo) << SPI_FIFO_STA_TF_CNT_SHIFT |
|
||||
fifo8_num_used(&s->rx_fifo) << SPI_FIFO_STA_RF_CNT_SHIFT;
|
||||
break;
|
||||
case SPI_CTL_REG:
|
||||
case SPI_INTCTL_REG:
|
||||
case SPI_INT_STA_REG:
|
||||
case SPI_DMACTL_REG:
|
||||
case SPI_WAIT_REG:
|
||||
case SPI_CCTL_REG:
|
||||
case SPI_BC_REG:
|
||||
case SPI_TC_REG:
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: bad offset 0x%x\n", __func__,
|
||||
(uint32_t)offset);
|
||||
break;
|
||||
}
|
||||
|
||||
allwinner_a10_spi_update_irq(s);
|
||||
}
|
||||
trace_allwinner_a10_spi_read(allwinner_a10_spi_get_regname(offset), value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static bool allwinner_a10_spi_update_cs_level(AWA10SPIState *s, int cs_line_nr)
|
||||
{
|
||||
if (cs_line_nr == allwinner_a10_spi_selected_channel(s)) {
|
||||
return (s->regs[REG_INDEX(SPI_CTL_REG)] & SPI_CTL_SS_LEVEL) != 0;
|
||||
} else {
|
||||
return (s->regs[REG_INDEX(SPI_CTL_REG)] & SPI_CTL_SSPOL) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void allwinner_a10_spi_write(void *opaque, hwaddr offset, uint64_t value,
|
||||
unsigned size)
|
||||
{
|
||||
AWA10SPIState *s = opaque;
|
||||
uint32_t index = offset >> 2;
|
||||
int i = 0;
|
||||
|
||||
if (offset > SPI_FIFO_STA_REG) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"[%s]%s: Bad register at offset 0x%" HWADDR_PRIx "\n",
|
||||
TYPE_AW_A10_SPI, __func__, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
trace_allwinner_a10_spi_write(allwinner_a10_spi_get_regname(offset),
|
||||
(uint32_t)value);
|
||||
|
||||
if (!allwinner_a10_spi_is_enabled(s)) {
|
||||
/* Block is disabled */
|
||||
if (offset != SPI_CTL_REG) {
|
||||
/* Ignore access */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (offset) {
|
||||
case SPI_RXDATA_REG:
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Trying to write to RX FIFO\n",
|
||||
TYPE_AW_A10_SPI, __func__);
|
||||
break;
|
||||
case SPI_TXDATA_REG:
|
||||
if (fifo8_is_full(&s->tx_fifo)) {
|
||||
/* Ignore writes if queue is full */
|
||||
break;
|
||||
}
|
||||
|
||||
fifo8_push(&s->tx_fifo, (uint8_t)value);
|
||||
|
||||
break;
|
||||
case SPI_INT_STA_REG:
|
||||
/* Handle W1C bits - everything except SPI_INT_STA_INT_CBF. */
|
||||
value &= ~SPI_INT_STA_INT_CBF;
|
||||
s->regs[REG_INDEX(SPI_INT_STA_REG)] &= ~(value & SPI_INT_STA_MASK);
|
||||
break;
|
||||
case SPI_CTL_REG:
|
||||
s->regs[REG_INDEX(SPI_CTL_REG)] = value;
|
||||
|
||||
for (i = 0; i < AW_A10_SPI_CS_LINES_NR; i++) {
|
||||
qemu_set_irq(
|
||||
s->cs_lines[i],
|
||||
allwinner_a10_spi_update_cs_level(s, i));
|
||||
}
|
||||
|
||||
if (s->regs[REG_INDEX(SPI_CTL_REG)] & SPI_CTL_XCH) {
|
||||
/* Request to start emitting */
|
||||
allwinner_a10_spi_flush_txfifo(s);
|
||||
}
|
||||
if (s->regs[REG_INDEX(SPI_CTL_REG)] & SPI_CTL_TF_RST) {
|
||||
allwinner_a10_spi_txfifo_reset(s);
|
||||
s->regs[REG_INDEX(SPI_CTL_REG)] &= ~SPI_CTL_TF_RST;
|
||||
}
|
||||
if (s->regs[REG_INDEX(SPI_CTL_REG)] & SPI_CTL_RF_RST) {
|
||||
allwinner_a10_spi_rxfifo_reset(s);
|
||||
s->regs[REG_INDEX(SPI_CTL_REG)] &= ~SPI_CTL_RF_RST;
|
||||
}
|
||||
break;
|
||||
case SPI_INTCTL_REG:
|
||||
case SPI_DMACTL_REG:
|
||||
case SPI_WAIT_REG:
|
||||
case SPI_CCTL_REG:
|
||||
case SPI_BC_REG:
|
||||
case SPI_TC_REG:
|
||||
case SPI_FIFO_STA_REG:
|
||||
s->regs[index] = value;
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: bad offset 0x%x\n", __func__,
|
||||
(uint32_t)offset);
|
||||
break;
|
||||
}
|
||||
|
||||
allwinner_a10_spi_update_irq(s);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps allwinner_a10_spi_ops = {
|
||||
.read = allwinner_a10_spi_read,
|
||||
.write = allwinner_a10_spi_write,
|
||||
.valid.min_access_size = 1,
|
||||
.valid.max_access_size = 4,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
};
|
||||
|
||||
static const VMStateDescription allwinner_a10_spi_vmstate = {
|
||||
.name = TYPE_AW_A10_SPI,
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_FIFO8(tx_fifo, AWA10SPIState),
|
||||
VMSTATE_FIFO8(rx_fifo, AWA10SPIState),
|
||||
VMSTATE_UINT32_ARRAY(regs, AWA10SPIState, AW_A10_SPI_REGS_NUM),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void allwinner_a10_spi_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
AWA10SPIState *s = AW_A10_SPI(dev);
|
||||
int i = 0;
|
||||
|
||||
memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_a10_spi_ops, s,
|
||||
TYPE_AW_A10_SPI, AW_A10_SPI_IOSIZE);
|
||||
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
|
||||
sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
|
||||
|
||||
s->bus = ssi_create_bus(dev, "spi");
|
||||
for (i = 0; i < AW_A10_SPI_CS_LINES_NR; i++) {
|
||||
sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->cs_lines[i]);
|
||||
}
|
||||
fifo8_create(&s->tx_fifo, AW_A10_SPI_FIFO_SIZE);
|
||||
fifo8_create(&s->rx_fifo, AW_A10_SPI_FIFO_SIZE);
|
||||
}
|
||||
|
||||
static void allwinner_a10_spi_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
ResettableClass *rc = RESETTABLE_CLASS(klass);
|
||||
|
||||
rc->phases.hold = allwinner_a10_spi_reset_hold;
|
||||
dc->vmsd = &allwinner_a10_spi_vmstate;
|
||||
dc->realize = allwinner_a10_spi_realize;
|
||||
dc->desc = "Allwinner A10 SPI Controller";
|
||||
}
|
||||
|
||||
static const TypeInfo allwinner_a10_spi_type_info = {
|
||||
.name = TYPE_AW_A10_SPI,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(AWA10SPIState),
|
||||
.class_init = allwinner_a10_spi_class_init,
|
||||
};
|
||||
|
||||
static void allwinner_a10_spi_register_types(void)
|
||||
{
|
||||
type_register_static(&allwinner_a10_spi_type_info);
|
||||
}
|
||||
|
||||
type_init(allwinner_a10_spi_register_types)
|
@ -1,3 +1,4 @@
|
||||
system_ss.add(when: 'CONFIG_ALLWINNER_A10_SPI', if_true: files('allwinner-a10-spi.c'))
|
||||
system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_smc.c'))
|
||||
system_ss.add(when: 'CONFIG_MSF2', if_true: files('mss-spi.c'))
|
||||
system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_fiu.c', 'npcm_pspi.c'))
|
||||
|
@ -53,3 +53,13 @@ pnv_spi_rx_read_N2frame(void) ""
|
||||
pnv_spi_shift_rx(uint8_t byte, uint32_t index) "byte = 0x%2.2x into RDR from payload index %d"
|
||||
pnv_spi_sequencer_stop_requested(const char* reason) "due to %s"
|
||||
pnv_spi_RDR_match(const char* result) "%s"
|
||||
|
||||
# allwinner_a10_spi.c
|
||||
allwinner_a10_spi_update_irq(uint32_t level) "IRQ level is %d"
|
||||
allwinner_a10_spi_flush_txfifo_begin(uint32_t tx, uint32_t rx) "Begin: TX Fifo Size = %d, RX Fifo Size = %d"
|
||||
allwinner_a10_spi_flush_txfifo_end(uint32_t tx, uint32_t rx) "End: TX Fifo Size = %d, RX Fifo Size = %d"
|
||||
allwinner_a10_spi_burst_length(uint32_t len) "Burst length = %d"
|
||||
allwinner_a10_spi_tx(uint8_t byte) "write 0x%02x"
|
||||
allwinner_a10_spi_rx(uint8_t byte) "read 0x%02x"
|
||||
allwinner_a10_spi_read(const char* regname, uint32_t value) "reg[%s] => 0x%08x"
|
||||
allwinner_a10_spi_write(const char* regname, uint32_t value) "reg[%s] <= 0x%08x"
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "qemu/coroutine-core.h"
|
||||
#include "qemu/queue.h"
|
||||
#include "qemu/event_notifier.h"
|
||||
#include "qemu/lockcnt.h"
|
||||
#include "qemu/thread.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "block/graph-lock.h"
|
||||
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Maxim MAX1110/1111 ADC chip emulation.
|
||||
*
|
||||
* Copyright (c) 2006 Openedhand Ltd.
|
||||
* Written by Andrzej Zaborowski <balrog@zabor.org>
|
||||
*
|
||||
* This code is licensed under the GNU GPLv2.
|
||||
*
|
||||
* Contributions after 2012-01-13 are licensed under the terms of the
|
||||
* GNU GPL, version 2 or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef HW_MISC_MAX111X_H
|
||||
#define HW_MISC_MAX111X_H
|
||||
|
||||
#include "hw/ssi/ssi.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
/*
|
||||
* This is a model of the Maxim MAX1110/1111 ADC chip, which for QEMU
|
||||
* is an SSI slave device. It has either 4 (max1110) or 8 (max1111)
|
||||
* 8-bit ADC channels.
|
||||
*
|
||||
* QEMU interface:
|
||||
* + GPIO inputs 0..3 (for max1110) or 0..7 (for max1111): set the value
|
||||
* of each ADC input, as an unsigned 8-bit value
|
||||
* + GPIO output 0: interrupt line
|
||||
* + Properties "input0" to "input3" (max1110) or "input0" to "input7"
|
||||
* (max1111): initial reset values for ADC inputs.
|
||||
*
|
||||
* Known bugs:
|
||||
* + the interrupt line is not correctly implemented, and will never
|
||||
* be lowered once it has been asserted.
|
||||
*/
|
||||
struct MAX111xState {
|
||||
SSIPeripheral parent_obj;
|
||||
|
||||
qemu_irq interrupt;
|
||||
/* Values of inputs at system reset (settable by QOM property) */
|
||||
uint8_t reset_input[8];
|
||||
|
||||
uint8_t tb1, rb2, rb3;
|
||||
int cycle;
|
||||
|
||||
uint8_t input[8];
|
||||
int inputs, com;
|
||||
};
|
||||
|
||||
#define TYPE_MAX_111X "max111x"
|
||||
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(MAX111xState, MAX_111X)
|
||||
|
||||
#define TYPE_MAX_1110 "max1110"
|
||||
#define TYPE_MAX_1111 "max1111"
|
||||
|
||||
#endif
|
@ -12,6 +12,7 @@
|
||||
#include "hw/misc/allwinner-a10-ccm.h"
|
||||
#include "hw/misc/allwinner-a10-dramc.h"
|
||||
#include "hw/i2c/allwinner-i2c.h"
|
||||
#include "hw/ssi/allwinner-a10-spi.h"
|
||||
#include "hw/watchdog/allwinner-wdt.h"
|
||||
#include "sysemu/block-backend.h"
|
||||
|
||||
@ -40,6 +41,7 @@ struct AwA10State {
|
||||
AllwinnerAHCIState sata;
|
||||
AwSdHostState mmc0;
|
||||
AWI2CState i2c0;
|
||||
AWA10SPIState spi0;
|
||||
AwRtcState rtc;
|
||||
AwWdtState wdt;
|
||||
MemoryRegion sram_a;
|
||||
|
@ -59,7 +59,7 @@ int64_t omap_clk_getrate(omap_clk clk);
|
||||
void omap_clk_reparent(omap_clk clk, omap_clk parent);
|
||||
|
||||
/* omap_intc.c */
|
||||
#define TYPE_OMAP_INTC "common-omap-intc"
|
||||
#define TYPE_OMAP_INTC "omap-intc"
|
||||
typedef struct OMAPIntcState OMAPIntcState;
|
||||
DECLARE_INSTANCE_CHECKER(OMAPIntcState, OMAP_INTC, TYPE_OMAP_INTC)
|
||||
|
||||
@ -490,15 +490,7 @@ qemu_irq *omap_mpuio_in_get(struct omap_mpuio_s *s);
|
||||
void omap_mpuio_out_set(struct omap_mpuio_s *s, int line, qemu_irq handler);
|
||||
void omap_mpuio_key(struct omap_mpuio_s *s, int row, int col, int down);
|
||||
|
||||
typedef struct uWireSlave {
|
||||
uint16_t (*receive)(void *opaque);
|
||||
void (*send)(void *opaque, uint16_t data);
|
||||
void *opaque;
|
||||
} uWireSlave;
|
||||
|
||||
struct omap_uwire_s;
|
||||
void omap_uwire_attach(struct omap_uwire_s *s,
|
||||
uWireSlave *slave, int chipselect);
|
||||
|
||||
struct I2SCodec {
|
||||
void *opaque;
|
||||
|
@ -25,6 +25,7 @@
|
||||
#ifndef HW_ARM_STM32F405_SOC_H
|
||||
#define HW_ARM_STM32F405_SOC_H
|
||||
|
||||
#include "hw/misc/stm32_rcc.h"
|
||||
#include "hw/misc/stm32f4xx_syscfg.h"
|
||||
#include "hw/timer/stm32f2xx_timer.h"
|
||||
#include "hw/char/stm32f2xx_usart.h"
|
||||
@ -55,6 +56,7 @@ struct STM32F405State {
|
||||
|
||||
ARMv7MState armv7m;
|
||||
|
||||
STM32RccState rcc;
|
||||
STM32F4xxSyscfgState syscfg;
|
||||
STM32F4xxExtiState exti;
|
||||
STM32F2XXUsartState usart[STM_NUM_USARTS];
|
||||
|
@ -62,17 +62,6 @@ uint32_t nand_getbuswidth(DeviceState *dev);
|
||||
#define NAND_MFR_HYNIX 0xad
|
||||
#define NAND_MFR_MICRON 0x2c
|
||||
|
||||
/* ecc.c */
|
||||
typedef struct {
|
||||
uint8_t cp; /* Column parity */
|
||||
uint16_t lp[2]; /* Line parity */
|
||||
uint16_t count;
|
||||
} ECCState;
|
||||
|
||||
uint8_t ecc_digest(ECCState *s, uint8_t sample);
|
||||
void ecc_reset(ECCState *s);
|
||||
extern const VMStateDescription vmstate_ecc_state;
|
||||
|
||||
/* m25p80.c */
|
||||
|
||||
#define TYPE_M25P80 "m25p80-generic"
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "qemu/bitmap.h"
|
||||
#include "qemu/rcu_queue.h"
|
||||
#include "qemu/queue.h"
|
||||
#include "qemu/lockcnt.h"
|
||||
#include "qemu/thread.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
|
91
include/hw/misc/stm32_rcc.h
Normal file
91
include/hw/misc/stm32_rcc.h
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* STM32 RCC (only reset and enable registers are implemented)
|
||||
*
|
||||
* Copyright (c) 2024 Román Cárdenas <rcardenas.rod@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef HW_STM32_RCC_H
|
||||
#define HW_STM32_RCC_H
|
||||
|
||||
#include "hw/sysbus.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define STM32_RCC_CR 0x00
|
||||
#define STM32_RCC_PLL_CFGR 0x04
|
||||
#define STM32_RCC_CFGR 0x08
|
||||
#define STM32_RCC_CIR 0x0C
|
||||
#define STM32_RCC_AHB1_RSTR 0x10
|
||||
#define STM32_RCC_AHB2_RSTR 0x14
|
||||
#define STM32_RCC_AHB3_RSTR 0x18
|
||||
|
||||
#define STM32_RCC_APB1_RSTR 0x20
|
||||
#define STM32_RCC_APB2_RSTR 0x24
|
||||
|
||||
#define STM32_RCC_AHB1_ENR 0x30
|
||||
#define STM32_RCC_AHB2_ENR 0x34
|
||||
#define STM32_RCC_AHB3_ENR 0x38
|
||||
|
||||
#define STM32_RCC_APB1_ENR 0x40
|
||||
#define STM32_RCC_APB2_ENR 0x44
|
||||
|
||||
#define STM32_RCC_AHB1_LPENR 0x50
|
||||
#define STM32_RCC_AHB2_LPENR 0x54
|
||||
#define STM32_RCC_AHB3_LPENR 0x58
|
||||
|
||||
#define STM32_RCC_APB1_LPENR 0x60
|
||||
#define STM32_RCC_APB2_LPENR 0x64
|
||||
|
||||
#define STM32_RCC_BDCR 0x70
|
||||
#define STM32_RCC_CSR 0x74
|
||||
|
||||
#define STM32_RCC_SSCGR 0x80
|
||||
#define STM32_RCC_PLLI2SCFGR 0x84
|
||||
#define STM32_RCC_PLLSAI_CFGR 0x88
|
||||
#define STM32_RCC_DCKCFGR 0x8C
|
||||
#define STM32_RCC_CKGATENR 0x90
|
||||
#define STM32_RCC_DCKCFGR2 0x94
|
||||
|
||||
#define STM32_RCC_NREGS ((STM32_RCC_DCKCFGR2 >> 2) + 1)
|
||||
#define STM32_RCC_PERIPHERAL_SIZE 0x400
|
||||
#define STM32_RCC_NIRQS (32 * 5) /* 32 bits per reg, 5 en/rst regs */
|
||||
|
||||
#define STM32_RCC_GPIO_IRQ_OFFSET 0
|
||||
|
||||
#define TYPE_STM32_RCC "stm32.rcc"
|
||||
|
||||
typedef struct STM32RccState STM32RccState;
|
||||
|
||||
DECLARE_INSTANCE_CHECKER(STM32RccState, STM32_RCC, TYPE_STM32_RCC)
|
||||
|
||||
#define NUM_GPIO_EVENT_IN_LINES 16
|
||||
|
||||
struct STM32RccState {
|
||||
SysBusDevice parent_obj;
|
||||
|
||||
MemoryRegion mmio;
|
||||
|
||||
uint32_t regs[STM32_RCC_NREGS];
|
||||
|
||||
qemu_irq enable_irq[STM32_RCC_NIRQS];
|
||||
qemu_irq reset_irq[STM32_RCC_NIRQS];
|
||||
};
|
||||
|
||||
#endif /* HW_STM32_RCC_H */
|
@ -48,6 +48,7 @@ struct Stm32l4x5SyscfgState {
|
||||
uint32_t swpr2;
|
||||
|
||||
qemu_irq gpio_out[GPIO_NUM_PINS];
|
||||
Clock *clk;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -1,66 +0,0 @@
|
||||
#ifndef HW_PCMCIA_H
|
||||
#define HW_PCMCIA_H
|
||||
|
||||
/* PCMCIA/Cardbus */
|
||||
|
||||
#include "hw/qdev-core.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
typedef struct PCMCIASocket {
|
||||
qemu_irq irq;
|
||||
bool attached;
|
||||
} PCMCIASocket;
|
||||
|
||||
#define TYPE_PCMCIA_CARD "pcmcia-card"
|
||||
OBJECT_DECLARE_TYPE(PCMCIACardState, PCMCIACardClass, PCMCIA_CARD)
|
||||
|
||||
struct PCMCIACardState {
|
||||
/*< private >*/
|
||||
DeviceState parent_obj;
|
||||
/*< public >*/
|
||||
|
||||
PCMCIASocket *slot;
|
||||
};
|
||||
|
||||
struct PCMCIACardClass {
|
||||
/*< private >*/
|
||||
DeviceClass parent_class;
|
||||
/*< public >*/
|
||||
|
||||
int (*attach)(PCMCIACardState *state);
|
||||
int (*detach)(PCMCIACardState *state);
|
||||
|
||||
const uint8_t *cis;
|
||||
int cis_len;
|
||||
|
||||
/* Only valid if attached */
|
||||
uint8_t (*attr_read)(PCMCIACardState *card, uint32_t address);
|
||||
void (*attr_write)(PCMCIACardState *card, uint32_t address, uint8_t value);
|
||||
uint16_t (*common_read)(PCMCIACardState *card, uint32_t address);
|
||||
void (*common_write)(PCMCIACardState *card,
|
||||
uint32_t address, uint16_t value);
|
||||
uint16_t (*io_read)(PCMCIACardState *card, uint32_t address);
|
||||
void (*io_write)(PCMCIACardState *card, uint32_t address, uint16_t value);
|
||||
};
|
||||
|
||||
#define CISTPL_DEVICE 0x01 /* 5V Device Information Tuple */
|
||||
#define CISTPL_NO_LINK 0x14 /* No Link Tuple */
|
||||
#define CISTPL_VERS_1 0x15 /* Level 1 Version Tuple */
|
||||
#define CISTPL_JEDEC_C 0x18 /* JEDEC ID Tuple */
|
||||
#define CISTPL_JEDEC_A 0x19 /* JEDEC ID Tuple */
|
||||
#define CISTPL_CONFIG 0x1a /* Configuration Tuple */
|
||||
#define CISTPL_CFTABLE_ENTRY 0x1b /* 16-bit PCCard Configuration */
|
||||
#define CISTPL_DEVICE_OC 0x1c /* Additional Device Information */
|
||||
#define CISTPL_DEVICE_OA 0x1d /* Additional Device Information */
|
||||
#define CISTPL_DEVICE_GEO 0x1e /* Additional Device Information */
|
||||
#define CISTPL_DEVICE_GEO_A 0x1f /* Additional Device Information */
|
||||
#define CISTPL_MANFID 0x20 /* Manufacture ID Tuple */
|
||||
#define CISTPL_FUNCID 0x21 /* Function ID Tuple */
|
||||
#define CISTPL_FUNCE 0x22 /* Function Extension Tuple */
|
||||
#define CISTPL_END 0xff /* Tuple End */
|
||||
#define CISTPL_ENDMARK 0xff
|
||||
|
||||
/* dscm1xxxx.c */
|
||||
PCMCIACardState *dscm1xxxx_init(DriveInfo *bdrv);
|
||||
|
||||
#endif
|
57
include/hw/ssi/allwinner-a10-spi.h
Normal file
57
include/hw/ssi/allwinner-a10-spi.h
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Allwinner SPI Bus Serial Interface registers definition
|
||||
*
|
||||
* Copyright (C) 2024 Strahinja Jankovic. <strahinja.p.jankovic@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef ALLWINNER_A10_SPI_H
|
||||
#define ALLWINNER_A10_SPI_H
|
||||
|
||||
#include "hw/ssi/ssi.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "qemu/fifo8.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
/** Size of register I/O address space used by SPI device */
|
||||
#define AW_A10_SPI_IOSIZE (0x1000)
|
||||
|
||||
/** Total number of known registers */
|
||||
#define AW_A10_SPI_REGS_NUM (AW_A10_SPI_IOSIZE / sizeof(uint32_t))
|
||||
#define AW_A10_SPI_FIFO_SIZE (64)
|
||||
#define AW_A10_SPI_CS_LINES_NR (4)
|
||||
|
||||
#define TYPE_AW_A10_SPI "allwinner.spi"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(AWA10SPIState, AW_A10_SPI)
|
||||
|
||||
struct AWA10SPIState {
|
||||
/*< private >*/
|
||||
SysBusDevice parent_obj;
|
||||
|
||||
/*< public >*/
|
||||
MemoryRegion iomem;
|
||||
SSIBus *bus;
|
||||
qemu_irq irq;
|
||||
qemu_irq cs_lines[AW_A10_SPI_CS_LINES_NR];
|
||||
|
||||
uint32_t regs[AW_A10_SPI_REGS_NUM];
|
||||
|
||||
Fifo8 rx_fifo;
|
||||
Fifo8 tx_fifo;
|
||||
};
|
||||
|
||||
#endif /* ALLWINNER_A10_SPI_H */
|
130
include/qemu/lockcnt.h
Normal file
130
include/qemu/lockcnt.h
Normal file
@ -0,0 +1,130 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* QemuLockCnt implementation
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2017
|
||||
*
|
||||
* Author:
|
||||
* Paolo Bonzini <pbonzini@redhat.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QEMU_LOCKCNT_H
|
||||
#define QEMU_LOCKCNT_H
|
||||
|
||||
#include "qemu/thread.h"
|
||||
|
||||
typedef struct QemuLockCnt QemuLockCnt;
|
||||
|
||||
struct QemuLockCnt {
|
||||
#ifndef CONFIG_LINUX
|
||||
QemuMutex mutex;
|
||||
#endif
|
||||
unsigned count;
|
||||
};
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_init: initialize a QemuLockcnt
|
||||
* @lockcnt: the lockcnt to initialize
|
||||
*
|
||||
* Initialize lockcnt's counter to zero and prepare its mutex
|
||||
* for usage.
|
||||
*/
|
||||
void qemu_lockcnt_init(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_destroy: destroy a QemuLockcnt
|
||||
* @lockcnt: the lockcnt to destruct
|
||||
*
|
||||
* Destroy lockcnt's mutex.
|
||||
*/
|
||||
void qemu_lockcnt_destroy(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_inc: increment a QemuLockCnt's counter
|
||||
* @lockcnt: the lockcnt to operate on
|
||||
*
|
||||
* If the lockcnt's count is zero, wait for critical sections
|
||||
* to finish and increment lockcnt's count to 1. If the count
|
||||
* is not zero, just increment it.
|
||||
*
|
||||
* Because this function can wait on the mutex, it must not be
|
||||
* called while the lockcnt's mutex is held by the current thread.
|
||||
* For the same reason, qemu_lockcnt_inc can also contribute to
|
||||
* AB-BA deadlocks. This is a sample deadlock scenario::
|
||||
*
|
||||
* thread 1 thread 2
|
||||
* -------------------------------------------------------
|
||||
* qemu_lockcnt_lock(&lc1);
|
||||
* qemu_lockcnt_lock(&lc2);
|
||||
* qemu_lockcnt_inc(&lc2);
|
||||
* qemu_lockcnt_inc(&lc1);
|
||||
*/
|
||||
void qemu_lockcnt_inc(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_dec: decrement a QemuLockCnt's counter
|
||||
* @lockcnt: the lockcnt to operate on
|
||||
*/
|
||||
void qemu_lockcnt_dec(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_dec_and_lock: decrement a QemuLockCnt's counter and
|
||||
* possibly lock it.
|
||||
* @lockcnt: the lockcnt to operate on
|
||||
*
|
||||
* Decrement lockcnt's count. If the new count is zero, lock
|
||||
* the mutex and return true. Otherwise, return false.
|
||||
*/
|
||||
bool qemu_lockcnt_dec_and_lock(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_dec_if_lock: possibly decrement a QemuLockCnt's counter and
|
||||
* lock it.
|
||||
* @lockcnt: the lockcnt to operate on
|
||||
*
|
||||
* If the count is 1, decrement the count to zero, lock
|
||||
* the mutex and return true. Otherwise, return false.
|
||||
*/
|
||||
bool qemu_lockcnt_dec_if_lock(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_lock: lock a QemuLockCnt's mutex.
|
||||
* @lockcnt: the lockcnt to operate on
|
||||
*
|
||||
* Remember that concurrent visits are not blocked unless the count is
|
||||
* also zero. You can use qemu_lockcnt_count to check for this inside a
|
||||
* critical section.
|
||||
*/
|
||||
void qemu_lockcnt_lock(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_unlock: release a QemuLockCnt's mutex.
|
||||
* @lockcnt: the lockcnt to operate on.
|
||||
*/
|
||||
void qemu_lockcnt_unlock(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_inc_and_unlock: combined unlock/increment on a QemuLockCnt.
|
||||
* @lockcnt: the lockcnt to operate on.
|
||||
*
|
||||
* This is the same as
|
||||
*
|
||||
* qemu_lockcnt_unlock(lockcnt);
|
||||
* qemu_lockcnt_inc(lockcnt);
|
||||
*
|
||||
* but more efficient.
|
||||
*/
|
||||
void qemu_lockcnt_inc_and_unlock(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_count: query a LockCnt's count.
|
||||
* @lockcnt: the lockcnt to query.
|
||||
*
|
||||
* Note that the count can change at any time. Still, while the
|
||||
* lockcnt is locked, one can usefully check whether the count
|
||||
* is non-zero.
|
||||
*/
|
||||
unsigned qemu_lockcnt_count(QemuLockCnt *lockcnt);
|
||||
|
||||
#endif
|
@ -293,115 +293,4 @@ static inline void qemu_spin_unlock(QemuSpin *spin)
|
||||
#endif
|
||||
}
|
||||
|
||||
struct QemuLockCnt {
|
||||
#ifndef CONFIG_LINUX
|
||||
QemuMutex mutex;
|
||||
#endif
|
||||
unsigned count;
|
||||
};
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_init: initialize a QemuLockcnt
|
||||
* @lockcnt: the lockcnt to initialize
|
||||
*
|
||||
* Initialize lockcnt's counter to zero and prepare its mutex
|
||||
* for usage.
|
||||
*/
|
||||
void qemu_lockcnt_init(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_destroy: destroy a QemuLockcnt
|
||||
* @lockcnt: the lockcnt to destruct
|
||||
*
|
||||
* Destroy lockcnt's mutex.
|
||||
*/
|
||||
void qemu_lockcnt_destroy(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_inc: increment a QemuLockCnt's counter
|
||||
* @lockcnt: the lockcnt to operate on
|
||||
*
|
||||
* If the lockcnt's count is zero, wait for critical sections
|
||||
* to finish and increment lockcnt's count to 1. If the count
|
||||
* is not zero, just increment it.
|
||||
*
|
||||
* Because this function can wait on the mutex, it must not be
|
||||
* called while the lockcnt's mutex is held by the current thread.
|
||||
* For the same reason, qemu_lockcnt_inc can also contribute to
|
||||
* AB-BA deadlocks. This is a sample deadlock scenario:
|
||||
*
|
||||
* thread 1 thread 2
|
||||
* -------------------------------------------------------
|
||||
* qemu_lockcnt_lock(&lc1);
|
||||
* qemu_lockcnt_lock(&lc2);
|
||||
* qemu_lockcnt_inc(&lc2);
|
||||
* qemu_lockcnt_inc(&lc1);
|
||||
*/
|
||||
void qemu_lockcnt_inc(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_dec: decrement a QemuLockCnt's counter
|
||||
* @lockcnt: the lockcnt to operate on
|
||||
*/
|
||||
void qemu_lockcnt_dec(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_dec_and_lock: decrement a QemuLockCnt's counter and
|
||||
* possibly lock it.
|
||||
* @lockcnt: the lockcnt to operate on
|
||||
*
|
||||
* Decrement lockcnt's count. If the new count is zero, lock
|
||||
* the mutex and return true. Otherwise, return false.
|
||||
*/
|
||||
bool qemu_lockcnt_dec_and_lock(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_dec_if_lock: possibly decrement a QemuLockCnt's counter and
|
||||
* lock it.
|
||||
* @lockcnt: the lockcnt to operate on
|
||||
*
|
||||
* If the count is 1, decrement the count to zero, lock
|
||||
* the mutex and return true. Otherwise, return false.
|
||||
*/
|
||||
bool qemu_lockcnt_dec_if_lock(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_lock: lock a QemuLockCnt's mutex.
|
||||
* @lockcnt: the lockcnt to operate on
|
||||
*
|
||||
* Remember that concurrent visits are not blocked unless the count is
|
||||
* also zero. You can use qemu_lockcnt_count to check for this inside a
|
||||
* critical section.
|
||||
*/
|
||||
void qemu_lockcnt_lock(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_unlock: release a QemuLockCnt's mutex.
|
||||
* @lockcnt: the lockcnt to operate on.
|
||||
*/
|
||||
void qemu_lockcnt_unlock(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_inc_and_unlock: combined unlock/increment on a QemuLockCnt.
|
||||
* @lockcnt: the lockcnt to operate on.
|
||||
*
|
||||
* This is the same as
|
||||
*
|
||||
* qemu_lockcnt_unlock(lockcnt);
|
||||
* qemu_lockcnt_inc(lockcnt);
|
||||
*
|
||||
* but more efficient.
|
||||
*/
|
||||
void qemu_lockcnt_inc_and_unlock(QemuLockCnt *lockcnt);
|
||||
|
||||
/**
|
||||
* qemu_lockcnt_count: query a LockCnt's count.
|
||||
* @lockcnt: the lockcnt to query.
|
||||
*
|
||||
* Note that the count can change at any time. Still, while the
|
||||
* lockcnt is locked, one can usefully check whether the count
|
||||
* is non-zero.
|
||||
*/
|
||||
unsigned qemu_lockcnt_count(QemuLockCnt *lockcnt);
|
||||
|
||||
#endif
|
||||
|
@ -152,7 +152,7 @@ static inline MemTxResult dma_memory_read(AddressSpace *as, dma_addr_t addr,
|
||||
}
|
||||
|
||||
/**
|
||||
* address_space_write: Write to address space from DMA controller.
|
||||
* dma_memory_write: Write to address space from DMA controller.
|
||||
*
|
||||
* Return a MemTxResult indicating whether the operation succeeded
|
||||
* or failed (eg unassigned memory, device rejected the transaction,
|
||||
@ -189,7 +189,7 @@ MemTxResult dma_memory_set(AddressSpace *as, dma_addr_t addr,
|
||||
uint8_t c, dma_addr_t len, MemTxAttrs attrs);
|
||||
|
||||
/**
|
||||
* address_space_map: Map a physical memory region into a host virtual address.
|
||||
* dma_memory_map: Map a physical memory region into a host virtual address.
|
||||
*
|
||||
* May map a subset of the requested range, given by and returned in @plen.
|
||||
* May return %NULL and set *@plen to zero(0), if resources needed to perform
|
||||
@ -216,16 +216,15 @@ static inline void *dma_memory_map(AddressSpace *as,
|
||||
}
|
||||
|
||||
/**
|
||||
* address_space_unmap: Unmaps a memory region previously mapped
|
||||
* by dma_memory_map()
|
||||
* dma_memory_unmap: Unmaps a memory region previously mapped by dma_memory_map()
|
||||
*
|
||||
* Will also mark the memory as dirty if @dir == %DMA_DIRECTION_FROM_DEVICE.
|
||||
* @access_len gives the amount of memory that was actually read or written
|
||||
* by the caller.
|
||||
*
|
||||
* @as: #AddressSpace used
|
||||
* @buffer: host pointer as returned by address_space_map()
|
||||
* @len: buffer length as returned by address_space_map()
|
||||
* @buffer: host pointer as returned by dma_memory_map()
|
||||
* @len: buffer length as returned by dma_memory_map()
|
||||
* @dir: indicates the transfer direction
|
||||
* @access_len: amount of data actually transferred
|
||||
*/
|
||||
|
@ -41,7 +41,6 @@ extern int graphic_height;
|
||||
extern int graphic_depth;
|
||||
extern int display_opengl;
|
||||
extern const char *keyboard_layout;
|
||||
extern int graphic_rotate;
|
||||
extern int old_param;
|
||||
extern uint8_t *boot_splash_filedata;
|
||||
extern bool enable_mlock;
|
||||
|
@ -2331,22 +2331,6 @@ SRST
|
||||
pick the first available. (Since 2.9)
|
||||
ERST
|
||||
|
||||
DEF("portrait", 0, QEMU_OPTION_portrait,
|
||||
"-portrait rotate graphical output 90 deg left (only PXA LCD)\n",
|
||||
QEMU_ARCH_ALL)
|
||||
SRST
|
||||
``-portrait``
|
||||
Rotate graphical output 90 deg left (only PXA LCD).
|
||||
ERST
|
||||
|
||||
DEF("rotate", HAS_ARG, QEMU_OPTION_rotate,
|
||||
"-rotate <deg> rotate graphical output some deg left (only PXA LCD)\n",
|
||||
QEMU_ARCH_ALL)
|
||||
SRST
|
||||
``-rotate deg``
|
||||
Rotate graphical output some deg left (only PXA LCD).
|
||||
ERST
|
||||
|
||||
DEF("vga", HAS_ARG, QEMU_OPTION_vga,
|
||||
"-vga [std|cirrus|vmware|qxl|xenfb|tcx|cg3|virtio|none]\n"
|
||||
" select video card type\n", QEMU_ARCH_ALL)
|
||||
|
@ -40,7 +40,6 @@ int autostart = 1;
|
||||
int vga_interface_type = VGA_NONE;
|
||||
bool vga_interface_created;
|
||||
Chardev *parallel_hds[MAX_PARALLEL_PORTS];
|
||||
int graphic_rotate;
|
||||
QEMUOptionRom option_rom[MAX_OPTION_ROMS];
|
||||
int nb_option_roms;
|
||||
int old_param;
|
||||
|
11
system/vl.c
11
system/vl.c
@ -2910,17 +2910,6 @@ void qemu_init(int argc, char **argv)
|
||||
nographic = true;
|
||||
dpy.type = DISPLAY_TYPE_NONE;
|
||||
break;
|
||||
case QEMU_OPTION_portrait:
|
||||
graphic_rotate = 90;
|
||||
break;
|
||||
case QEMU_OPTION_rotate:
|
||||
graphic_rotate = strtol(optarg, (char **) &optarg, 10);
|
||||
if (graphic_rotate != 0 && graphic_rotate != 90 &&
|
||||
graphic_rotate != 180 && graphic_rotate != 270) {
|
||||
error_report("only 90, 180, 270 deg rotation is available");
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case QEMU_OPTION_kernel:
|
||||
qdict_put_str(machine_opts_dict, "kernel", optarg);
|
||||
break;
|
||||
|
42
tests/qtest/stm32l4x5.h
Normal file
42
tests/qtest/stm32l4x5.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* QTest testcase header for STM32L4X5 :
|
||||
* used for consolidating common objects in stm32l4x5_*-test.c
|
||||
*
|
||||
* Copyright (c) 2024 Arnaud Minier <arnaud.minier@telecom-paris.fr>
|
||||
* Copyright (c) 2024 Inès Varhol <ines.varhol@telecom-paris.fr>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "libqtest.h"
|
||||
|
||||
/* copied from clock.h */
|
||||
#define CLOCK_PERIOD_1SEC (1000000000llu << 32)
|
||||
#define CLOCK_PERIOD_FROM_HZ(hz) (((hz) != 0) ? CLOCK_PERIOD_1SEC / (hz) : 0u)
|
||||
/*
|
||||
* MSI (4 MHz) is used as system clock source after startup
|
||||
* from Reset.
|
||||
* AHB, APB1 and APB2 prescalers are set to 1 at reset.
|
||||
*/
|
||||
#define SYSCLK_PERIOD CLOCK_PERIOD_FROM_HZ(4000000)
|
||||
#define RCC_AHB2ENR 0x4002104C
|
||||
#define RCC_APB1ENR1 0x40021058
|
||||
#define RCC_APB1ENR2 0x4002105C
|
||||
#define RCC_APB2ENR 0x40021060
|
||||
|
||||
|
||||
static inline uint64_t get_clock_period(QTestState *qts, const char *path)
|
||||
{
|
||||
uint64_t clock_period = 0;
|
||||
QDict *r;
|
||||
|
||||
r = qtest_qmp(qts, "{ 'execute': 'qom-get', 'arguments':"
|
||||
" { 'path': %s, 'property': 'qtest-clock-period'} }", path);
|
||||
g_assert_false(qdict_haskey(r, "error"));
|
||||
clock_period = qdict_get_int(r, "return");
|
||||
qobject_unref(r);
|
||||
return clock_period;
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "libqtest-single.h"
|
||||
#include "stm32l4x5.h"
|
||||
|
||||
#define GPIO_BASE_ADDR 0x48000000
|
||||
#define GPIO_SIZE 0x400
|
||||
@ -505,6 +506,26 @@ static void test_bsrr_brr(const void *data)
|
||||
gpio_writel(gpio, ODR, reset(gpio, ODR));
|
||||
}
|
||||
|
||||
static void test_clock_enable(void)
|
||||
{
|
||||
/*
|
||||
* For each GPIO, enable its clock in RCC
|
||||
* and check that its clock period changes to SYSCLK_PERIOD
|
||||
*/
|
||||
unsigned int gpio_id;
|
||||
|
||||
for (uint32_t gpio = GPIO_A; gpio <= GPIO_H; gpio += GPIO_B - GPIO_A) {
|
||||
gpio_id = get_gpio_id(gpio);
|
||||
g_autofree char *path = g_strdup_printf("/machine/soc/gpio%c/clk",
|
||||
gpio_id + 'a');
|
||||
g_assert_cmpuint(get_clock_period(global_qtest, path), ==, 0);
|
||||
/* Enable the gpio clock */
|
||||
writel(RCC_AHB2ENR, readl(RCC_AHB2ENR) | (0x1 << gpio_id));
|
||||
g_assert_cmpuint(get_clock_period(global_qtest, path), ==,
|
||||
SYSCLK_PERIOD);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ret;
|
||||
@ -556,6 +577,8 @@ int main(int argc, char **argv)
|
||||
qtest_add_data_func("stm32l4x5/gpio/test_bsrr_brr2",
|
||||
test_data(GPIO_D, 0),
|
||||
test_bsrr_brr);
|
||||
qtest_add_func("stm32l4x5/gpio/test_clock_enable",
|
||||
test_clock_enable);
|
||||
|
||||
qtest_start("-machine b-l475e-iot01a");
|
||||
ret = g_test_run();
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "libqtest-single.h"
|
||||
#include "stm32l4x5.h"
|
||||
|
||||
#define SYSCFG_BASE_ADDR 0x40010000
|
||||
#define SYSCFG_MEMRMP 0x00
|
||||
@ -26,7 +27,9 @@
|
||||
#define INVALID_ADDR 0x2C
|
||||
|
||||
/* SoC forwards GPIOs to SysCfg */
|
||||
#define SYSCFG "/machine/soc"
|
||||
#define SOC "/machine/soc"
|
||||
#define SYSCFG "/machine/soc/syscfg"
|
||||
#define SYSCFG_CLK "/machine/soc/syscfg/clk"
|
||||
#define EXTI "/machine/soc/exti"
|
||||
|
||||
static void syscfg_writel(unsigned int offset, uint32_t value)
|
||||
@ -41,7 +44,7 @@ static uint32_t syscfg_readl(unsigned int offset)
|
||||
|
||||
static void syscfg_set_irq(int num, int level)
|
||||
{
|
||||
qtest_set_irq_in(global_qtest, SYSCFG, NULL, num, level);
|
||||
qtest_set_irq_in(global_qtest, SOC, NULL, num, level);
|
||||
}
|
||||
|
||||
static void system_reset(void)
|
||||
@ -301,6 +304,17 @@ static void test_irq_gpio_multiplexer(void)
|
||||
syscfg_writel(SYSCFG_EXTICR1, 0x00000000);
|
||||
}
|
||||
|
||||
static void test_clock_enable(void)
|
||||
{
|
||||
g_assert_cmpuint(get_clock_period(global_qtest, SYSCFG_CLK), ==, 0);
|
||||
|
||||
/* Enable SYSCFG clock */
|
||||
writel(RCC_APB2ENR, readl(RCC_APB2ENR) | (0x1 << 0));
|
||||
|
||||
g_assert_cmpuint(get_clock_period(global_qtest, SYSCFG_CLK), ==,
|
||||
SYSCLK_PERIOD);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ret;
|
||||
@ -325,6 +339,8 @@ int main(int argc, char **argv)
|
||||
test_irq_pin_multiplexer);
|
||||
qtest_add_func("stm32l4x5/syscfg/test_irq_gpio_multiplexer",
|
||||
test_irq_gpio_multiplexer);
|
||||
qtest_add_func("stm32l4x5/syscfg/test_clock_enable",
|
||||
test_clock_enable);
|
||||
|
||||
qtest_start("-machine b-l475e-iot01a");
|
||||
ret = g_test_run();
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "libqtest.h"
|
||||
#include "hw/misc/stm32l4x5_rcc_internals.h"
|
||||
#include "hw/registerfields.h"
|
||||
#include "stm32l4x5.h"
|
||||
|
||||
#define RCC_BASE_ADDR 0x40021000
|
||||
/* Use USART 1 ADDR, assume the others work the same */
|
||||
@ -331,6 +332,32 @@ static void test_ack(void)
|
||||
qtest_quit(qts);
|
||||
}
|
||||
|
||||
static void check_clock(QTestState *qts, const char *path, uint32_t rcc_reg,
|
||||
uint32_t reg_offset)
|
||||
{
|
||||
g_assert_cmpuint(get_clock_period(qts, path), ==, 0);
|
||||
qtest_writel(qts, rcc_reg, qtest_readl(qts, rcc_reg) | (0x1 << reg_offset));
|
||||
g_assert_cmpuint(get_clock_period(qts, path), ==, SYSCLK_PERIOD);
|
||||
}
|
||||
|
||||
static void test_clock_enable(void)
|
||||
{
|
||||
/*
|
||||
* For each USART device, enable its clock in RCC
|
||||
* and check that its clock frequency is SYSCLK_PERIOD
|
||||
*/
|
||||
QTestState *qts = qtest_init("-M b-l475e-iot01a");
|
||||
|
||||
check_clock(qts, "machine/soc/usart[0]/clk", RCC_APB2ENR, 14);
|
||||
check_clock(qts, "machine/soc/usart[1]/clk", RCC_APB1ENR1, 17);
|
||||
check_clock(qts, "machine/soc/usart[2]/clk", RCC_APB1ENR1, 18);
|
||||
check_clock(qts, "machine/soc/uart[0]/clk", RCC_APB1ENR1, 19);
|
||||
check_clock(qts, "machine/soc/uart[1]/clk", RCC_APB1ENR1, 20);
|
||||
check_clock(qts, "machine/soc/lpuart1/clk", RCC_APB1ENR2, 0);
|
||||
|
||||
qtest_quit(qts);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ret;
|
||||
@ -344,6 +371,7 @@ int main(int argc, char **argv)
|
||||
qtest_add_func("stm32l4x5/usart/receive_str", test_receive_str);
|
||||
qtest_add_func("stm32l4x5/usart/send_str", test_send_str);
|
||||
qtest_add_func("stm32l4x5/usart/ack", test_ack);
|
||||
qtest_add_func("stm32l4x5/usart/clock_enable", test_clock_enable);
|
||||
ret = g_test_run();
|
||||
|
||||
return ret;
|
||||
|
36
ui/input.c
36
ui/input.c
@ -174,37 +174,6 @@ void qmp_input_send_event(const char *device,
|
||||
qemu_input_event_sync();
|
||||
}
|
||||
|
||||
static int qemu_input_transform_invert_abs_value(int value)
|
||||
{
|
||||
return (int64_t)INPUT_EVENT_ABS_MAX - value + INPUT_EVENT_ABS_MIN;
|
||||
}
|
||||
|
||||
static void qemu_input_transform_abs_rotate(InputEvent *evt)
|
||||
{
|
||||
InputMoveEvent *move = evt->u.abs.data;
|
||||
switch (graphic_rotate) {
|
||||
case 90:
|
||||
if (move->axis == INPUT_AXIS_X) {
|
||||
move->axis = INPUT_AXIS_Y;
|
||||
} else if (move->axis == INPUT_AXIS_Y) {
|
||||
move->axis = INPUT_AXIS_X;
|
||||
move->value = qemu_input_transform_invert_abs_value(move->value);
|
||||
}
|
||||
break;
|
||||
case 180:
|
||||
move->value = qemu_input_transform_invert_abs_value(move->value);
|
||||
break;
|
||||
case 270:
|
||||
if (move->axis == INPUT_AXIS_X) {
|
||||
move->axis = INPUT_AXIS_Y;
|
||||
move->value = qemu_input_transform_invert_abs_value(move->value);
|
||||
} else if (move->axis == INPUT_AXIS_Y) {
|
||||
move->axis = INPUT_AXIS_X;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void qemu_input_event_trace(QemuConsole *src, InputEvent *evt)
|
||||
{
|
||||
const char *name;
|
||||
@ -340,11 +309,6 @@ void qemu_input_event_send_impl(QemuConsole *src, InputEvent *evt)
|
||||
|
||||
qemu_input_event_trace(src, evt);
|
||||
|
||||
/* pre processing */
|
||||
if (graphic_rotate && (evt->type == INPUT_EVENT_KIND_ABS)) {
|
||||
qemu_input_transform_abs_rotate(evt);
|
||||
}
|
||||
|
||||
/* send event */
|
||||
s = qemu_input_find_handler(1 << evt->type, src);
|
||||
if (!s) {
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "block/block.h"
|
||||
#include "block/thread-pool.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qemu/lockcnt.h"
|
||||
#include "qemu/rcu.h"
|
||||
#include "qemu/rcu_queue.h"
|
||||
#include "qemu/sockets.h"
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "block/block.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qemu/lockcnt.h"
|
||||
#include "qemu/queue.h"
|
||||
#include "qemu/sockets.h"
|
||||
#include "qapi/error.h"
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "block/graph-lock.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qemu/atomic.h"
|
||||
#include "qemu/lockcnt.h"
|
||||
#include "qemu/rcu_queue.h"
|
||||
#include "block/raw-aio.h"
|
||||
#include "qemu/coroutine_int.h"
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include <sys/epoll.h>
|
||||
#include "qemu/lockcnt.h"
|
||||
#include "qemu/rcu_queue.h"
|
||||
#include "aio-posix.h"
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
* Paolo Bonzini <pbonzini@redhat.com>
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/lockcnt.h"
|
||||
#include "qemu/thread.h"
|
||||
#include "qemu/atomic.h"
|
||||
#include "trace.h"
|
||||
|
Loading…
Reference in New Issue
Block a user