mirror of
https://gitlab.com/qemu-project/qemu
synced 2024-10-04 18:20:06 +00:00
virtio,pc,pci: features, fixes
virtio sound card support vhost-user: back-end state migration cxl: line length reduction enabling fabric management vhost-vdpa: shadow virtqueue hash calculation Support shadow virtqueue RSS Support tests: CPU topology related smbios test cases Fixes, cleanups all over the place Signed-off-by: Michael S. Tsirkin <mst@redhat.com> -----BEGIN PGP SIGNATURE----- iQFDBAABCAAtFiEEXQn9CHHI+FuUyooNKB8NuNKNVGkFAmVKDDoPHG1zdEByZWRo YXQuY29tAAoJECgfDbjSjVRpF08H/0Zts8uvkHbgiOEJw4JMHU6/VaCipfIYsp01 GSfwYOyEsXJ7GIxKWaCiMnWXEm7tebNCPKf3DoUtcAojQj3vuF9XbWBKw/bfRn83 nGO/iiwbYViSKxkwqUI+Up5YiN9o0M8gBFrY0kScPezbnYmo5u2bcADdEEq6gH68 D0Ea8i+WmszL891ypvgCDBL2ObDk3qX3vA5Q6J2I+HKX2ofJM59BwaKwS5ghw+IG BmbKXUZJNjUQfN9dQ7vJuiuqdknJ2xUzwW2Vn612ffarbOZB1DZ6ruWlrHty5TjX 0w4IXEJPBgZYbX9oc6zvTQnbLDBJbDU89mnme0TcmNMKWmQKTtc= =vEv+ -----END PGP SIGNATURE----- Merge tag 'for_upstream' of https://git.kernel.org/pub/scm/virt/kvm/mst/qemu into staging virtio,pc,pci: features, fixes virtio sound card support vhost-user: back-end state migration cxl: line length reduction enabling fabric management vhost-vdpa: shadow virtqueue hash calculation Support shadow virtqueue RSS Support tests: CPU topology related smbios test cases Fixes, cleanups all over the place Signed-off-by: Michael S. Tsirkin <mst@redhat.com> # -----BEGIN PGP SIGNATURE----- # # iQFDBAABCAAtFiEEXQn9CHHI+FuUyooNKB8NuNKNVGkFAmVKDDoPHG1zdEByZWRo # YXQuY29tAAoJECgfDbjSjVRpF08H/0Zts8uvkHbgiOEJw4JMHU6/VaCipfIYsp01 # GSfwYOyEsXJ7GIxKWaCiMnWXEm7tebNCPKf3DoUtcAojQj3vuF9XbWBKw/bfRn83 # nGO/iiwbYViSKxkwqUI+Up5YiN9o0M8gBFrY0kScPezbnYmo5u2bcADdEEq6gH68 # D0Ea8i+WmszL891ypvgCDBL2ObDk3qX3vA5Q6J2I+HKX2ofJM59BwaKwS5ghw+IG # BmbKXUZJNjUQfN9dQ7vJuiuqdknJ2xUzwW2Vn612ffarbOZB1DZ6ruWlrHty5TjX # 0w4IXEJPBgZYbX9oc6zvTQnbLDBJbDU89mnme0TcmNMKWmQKTtc= # =vEv+ # -----END PGP SIGNATURE----- # gpg: Signature made Tue 07 Nov 2023 18:06:50 HKT # gpg: using RSA key 5D09FD0871C8F85B94CA8A0D281F0DB8D28D5469 # gpg: issuer "mst@redhat.com" # gpg: Good signature from "Michael S. Tsirkin <mst@kernel.org>" [full] # gpg: aka "Michael S. Tsirkin <mst@redhat.com>" [full] # Primary key fingerprint: 0270 606B 6F3C DF3D 0B17 0970 C350 3912 AFBE 8E67 # Subkey fingerprint: 5D09 FD08 71C8 F85B 94CA 8A0D 281F 0DB8 D28D 5469 * tag 'for_upstream' of https://git.kernel.org/pub/scm/virt/kvm/mst/qemu: (63 commits) acpi/tests/avocado/bits: enable console logging from bits VM acpi/tests/avocado/bits: enforce 32-bit SMBIOS entry point hw/cxl: Add tunneled command support to mailbox for switch cci. hw/cxl: Add dummy security state get hw/cxl/type3: Cleanup multiple CXL_TYPE3() calls in read/write functions hw/cxl/mbox: Add Get Background Operation Status Command hw/cxl: Add support for device sanitation hw/cxl/mbox: Wire up interrupts for background completion hw/cxl/mbox: Add support for background operations hw/cxl: Implement Physical Ports status retrieval hw/pci-bridge/cxl_downstream: Set default link width and link speed hw/cxl/mbox: Add Physical Switch Identify command. hw/cxl/mbox: Add Information and Status / Identify command hw/cxl: Add a switch mailbox CCI function hw/pci-bridge/cxl_upstream: Move defintion of device to header. hw/cxl/mbox: Generalize the CCI command processing hw/cxl/mbox: Pull the CCI definition out of the CXLDeviceState hw/cxl/mbox: Split mailbox command payload into separate input and output hw/cxl/mbox: Pull the payload out of struct cxl_cmd and make instances constant hw/cxl: Fix a QEMU_BUILD_BUG_ON() in switch statement scope issue. ... Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
commit
f6b615b52d
|
@ -2310,6 +2310,15 @@ F: hw/virtio/virtio-mem-pci.h
|
|||
F: hw/virtio/virtio-mem-pci.c
|
||||
F: include/hw/virtio/virtio-mem.h
|
||||
|
||||
virtio-snd
|
||||
M: Gerd Hoffmann <kraxel@redhat.com>
|
||||
R: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
|
||||
S: Supported
|
||||
F: hw/audio/virtio-snd.c
|
||||
F: hw/audio/virtio-snd-pci.c
|
||||
F: include/hw/audio/virtio-snd.h
|
||||
F: docs/system/devices/virtio-snd.rst
|
||||
|
||||
nvme
|
||||
M: Keith Busch <kbusch@kernel.org>
|
||||
M: Klaus Jensen <its@irrelevant.dk>
|
||||
|
|
|
@ -108,6 +108,43 @@ A vring state description
|
|||
|
||||
:num: a 32-bit number
|
||||
|
||||
A vring descriptor index for split virtqueues
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
+-------------+---------------------+
|
||||
| vring index | index in avail ring |
|
||||
+-------------+---------------------+
|
||||
|
||||
:vring index: 32-bit index of the respective virtqueue
|
||||
|
||||
:index in avail ring: 32-bit value, of which currently only the lower 16
|
||||
bits are used:
|
||||
|
||||
- Bits 0–15: Index of the next *Available Ring* descriptor that the
|
||||
back-end will process. This is a free-running index that is not
|
||||
wrapped by the ring size.
|
||||
- Bits 16–31: Reserved (set to zero)
|
||||
|
||||
Vring descriptor indices for packed virtqueues
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
+-------------+--------------------+
|
||||
| vring index | descriptor indices |
|
||||
+-------------+--------------------+
|
||||
|
||||
:vring index: 32-bit index of the respective virtqueue
|
||||
|
||||
:descriptor indices: 32-bit value:
|
||||
|
||||
- Bits 0–14: Index of the next *Available Ring* descriptor that the
|
||||
back-end will process. This is a free-running index that is not
|
||||
wrapped by the ring size.
|
||||
- Bit 15: Driver (Available) Ring Wrap Counter
|
||||
- Bits 16–30: Index of the entry in the *Used Ring* where the back-end
|
||||
will place the next descriptor. This is a free-running index that
|
||||
is not wrapped by the ring size.
|
||||
- Bit 31: Device (Used) Ring Wrap Counter
|
||||
|
||||
A vring address description
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -285,6 +322,32 @@ VhostUserShared
|
|||
:UUID: 16 bytes UUID, whose first three components (a 32-bit value, then
|
||||
two 16-bit values) are stored in big endian.
|
||||
|
||||
Device state transfer parameters
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
+--------------------+-----------------+
|
||||
| transfer direction | migration phase |
|
||||
+--------------------+-----------------+
|
||||
|
||||
:transfer direction: a 32-bit enum, describing the direction in which
|
||||
the state is transferred:
|
||||
|
||||
- 0: Save: Transfer the state from the back-end to the front-end,
|
||||
which happens on the source side of migration
|
||||
- 1: Load: Transfer the state from the front-end to the back-end,
|
||||
which happens on the destination side of migration
|
||||
|
||||
:migration phase: a 32-bit enum, describing the state in which the VM
|
||||
guest and devices are:
|
||||
|
||||
- 0: Stopped (in the period after the transfer of memory-mapped
|
||||
regions before switch-over to the destination): The VM guest is
|
||||
stopped, and the vhost-user device is suspended (see
|
||||
:ref:`Suspended device state <suspended_device_state>`).
|
||||
|
||||
In the future, additional phases might be added e.g. to allow
|
||||
iterative migration while the device is running.
|
||||
|
||||
C structure
|
||||
-----------
|
||||
|
||||
|
@ -344,6 +407,7 @@ in the ancillary data:
|
|||
* ``VHOST_USER_SET_VRING_ERR``
|
||||
* ``VHOST_USER_SET_BACKEND_REQ_FD`` (previous name ``VHOST_USER_SET_SLAVE_REQ_FD``)
|
||||
* ``VHOST_USER_SET_INFLIGHT_FD`` (if ``VHOST_USER_PROTOCOL_F_INFLIGHT_SHMFD``)
|
||||
* ``VHOST_USER_SET_DEVICE_STATE_FD``
|
||||
|
||||
If *front-end* is unable to send the full message or receives a wrong
|
||||
reply it will close the connection. An optional reconnection mechanism
|
||||
|
@ -374,35 +438,50 @@ negotiation.
|
|||
Ring states
|
||||
-----------
|
||||
|
||||
Rings can be in one of three states:
|
||||
Rings have two independent states: started/stopped, and enabled/disabled.
|
||||
|
||||
* stopped: the back-end must not process the ring at all.
|
||||
* While a ring is stopped, the back-end must not process the ring at
|
||||
all, regardless of whether it is enabled or disabled. The
|
||||
enabled/disabled state should still be tracked, though, so it can come
|
||||
into effect once the ring is started.
|
||||
|
||||
* started but disabled: the back-end must process the ring without
|
||||
* started and disabled: The back-end must process the ring without
|
||||
causing any side effects. For example, for a networking device,
|
||||
in the disabled state the back-end must not supply any new RX packets,
|
||||
but must process and discard any TX packets.
|
||||
|
||||
* started and enabled.
|
||||
* started and enabled: The back-end must process the ring normally, i.e.
|
||||
process all requests and execute them.
|
||||
|
||||
Each ring is initialized in a stopped state. The back-end must start
|
||||
ring upon receiving a kick (that is, detecting that file descriptor is
|
||||
readable) on the descriptor specified by ``VHOST_USER_SET_VRING_KICK``
|
||||
or receiving the in-band message ``VHOST_USER_VRING_KICK`` if negotiated,
|
||||
and stop ring upon receiving ``VHOST_USER_GET_VRING_BASE``.
|
||||
Each ring is initialized in a stopped and disabled state. The back-end
|
||||
must start a ring upon receiving a kick (that is, detecting that file
|
||||
descriptor is readable) on the descriptor specified by
|
||||
``VHOST_USER_SET_VRING_KICK`` or receiving the in-band message
|
||||
``VHOST_USER_VRING_KICK`` if negotiated, and stop a ring upon receiving
|
||||
``VHOST_USER_GET_VRING_BASE``.
|
||||
|
||||
Rings can be enabled or disabled by ``VHOST_USER_SET_VRING_ENABLE``.
|
||||
|
||||
If ``VHOST_USER_F_PROTOCOL_FEATURES`` has not been negotiated, the
|
||||
ring starts directly in the enabled state.
|
||||
|
||||
If ``VHOST_USER_F_PROTOCOL_FEATURES`` has been negotiated, the ring is
|
||||
initialized in a disabled state and is enabled by
|
||||
``VHOST_USER_SET_VRING_ENABLE`` with parameter 1.
|
||||
In addition, upon receiving a ``VHOST_USER_SET_FEATURES`` message from
|
||||
the front-end without ``VHOST_USER_F_PROTOCOL_FEATURES`` set, the
|
||||
back-end must enable all rings immediately.
|
||||
|
||||
While processing the rings (whether they are enabled or not), the back-end
|
||||
must support changing some configuration aspects on the fly.
|
||||
|
||||
.. _suspended_device_state:
|
||||
|
||||
Suspended device state
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
While all vrings are stopped, the device is *suspended*. In addition to
|
||||
not processing any vring (because they are stopped), the device must:
|
||||
|
||||
* not write to any guest memory regions,
|
||||
* not send any notifications to the guest,
|
||||
* not send any messages to the front-end,
|
||||
* still process and reply to messages from the front-end.
|
||||
|
||||
Multiple queue support
|
||||
----------------------
|
||||
|
||||
|
@ -490,7 +569,8 @@ ancillary data, it may be used to inform the front-end that the log has
|
|||
been modified.
|
||||
|
||||
Once the source has finished migration, rings will be stopped by the
|
||||
source. No further update must be done before rings are restarted.
|
||||
source (:ref:`Suspended device state <suspended_device_state>`). No
|
||||
further update must be done before rings are restarted.
|
||||
|
||||
In postcopy migration the back-end is started before all the memory has
|
||||
been received from the source host, and care must be taken to avoid
|
||||
|
@ -502,6 +582,80 @@ it performs WAKE ioctl's on the userfaultfd to wake the stalled
|
|||
back-end. The front-end indicates support for this via the
|
||||
``VHOST_USER_PROTOCOL_F_PAGEFAULT`` feature.
|
||||
|
||||
.. _migrating_backend_state:
|
||||
|
||||
Migrating back-end state
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Migrating device state involves transferring the state from one
|
||||
back-end, called the source, to another back-end, called the
|
||||
destination. After migration, the destination transparently resumes
|
||||
operation without requiring the driver to re-initialize the device at
|
||||
the VIRTIO level. If the migration fails, then the source can
|
||||
transparently resume operation until another migration attempt is made.
|
||||
|
||||
Generally, the front-end is connected to a virtual machine guest (which
|
||||
contains the driver), which has its own state to transfer between source
|
||||
and destination, and therefore will have an implementation-specific
|
||||
mechanism to do so. The ``VHOST_USER_PROTOCOL_F_DEVICE_STATE`` feature
|
||||
provides functionality to have the front-end include the back-end's
|
||||
state in this transfer operation so the back-end does not need to
|
||||
implement its own mechanism, and so the virtual machine may have its
|
||||
complete state, including vhost-user devices' states, contained within a
|
||||
single stream of data.
|
||||
|
||||
To do this, the back-end state is transferred from back-end to front-end
|
||||
on the source side, and vice versa on the destination side. This
|
||||
transfer happens over a channel that is negotiated using the
|
||||
``VHOST_USER_SET_DEVICE_STATE_FD`` message. This message has two
|
||||
parameters:
|
||||
|
||||
* Direction of transfer: On the source, the data is saved, transferring
|
||||
it from the back-end to the front-end. On the destination, the data
|
||||
is loaded, transferring it from the front-end to the back-end.
|
||||
|
||||
* Migration phase: Currently, the only supported phase is the period
|
||||
after the transfer of memory-mapped regions before switch-over to the
|
||||
destination, when both the source and destination devices are
|
||||
suspended (:ref:`Suspended device state <suspended_device_state>`).
|
||||
In the future, additional phases might be supported to allow iterative
|
||||
migration while the device is running.
|
||||
|
||||
The nature of the channel is implementation-defined, but it must
|
||||
generally behave like a pipe: The writing end will write all the data it
|
||||
has into it, signalling the end of data by closing its end. The reading
|
||||
end must read all of this data (until encountering the end of file) and
|
||||
process it.
|
||||
|
||||
* When saving, the writing end is the source back-end, and the reading
|
||||
end is the source front-end. After reading the state data from the
|
||||
channel, the source front-end must transfer it to the destination
|
||||
front-end through an implementation-defined mechanism.
|
||||
|
||||
* When loading, the writing end is the destination front-end, and the
|
||||
reading end is the destination back-end. After reading the state data
|
||||
from the channel, the destination back-end must deserialize its
|
||||
internal state from that data and set itself up to allow the driver to
|
||||
seamlessly resume operation on the VIRTIO level.
|
||||
|
||||
Seamlessly resuming operation means that the migration must be
|
||||
transparent to the guest driver, which operates on the VIRTIO level.
|
||||
This driver will not perform any re-initialization steps, but continue
|
||||
to use the device as if no migration had occurred. The vhost-user
|
||||
front-end, however, will re-initialize the vhost state on the
|
||||
destination, following the usual protocol for establishing a connection
|
||||
to a vhost-user back-end: This includes, for example, setting up memory
|
||||
mappings and kick and call FDs as necessary, negotiating protocol
|
||||
features, or setting the initial vring base indices (to the same value
|
||||
as on the source side, so that operation can resume).
|
||||
|
||||
Both on the source and on the destination side, after the respective
|
||||
front-end has seen all data transferred (when the transfer FD has been
|
||||
closed), it sends the ``VHOST_USER_CHECK_DEVICE_STATE`` message to
|
||||
verify that data transfer was successful in the back-end, too. The
|
||||
back-end responds once it knows whether the transfer and processing was
|
||||
successful or not.
|
||||
|
||||
Memory access
|
||||
-------------
|
||||
|
||||
|
@ -896,6 +1050,7 @@ Protocol features
|
|||
#define VHOST_USER_PROTOCOL_F_STATUS 16
|
||||
#define VHOST_USER_PROTOCOL_F_XEN_MMAP 17
|
||||
#define VHOST_USER_PROTOCOL_F_SHARED_OBJECT 18
|
||||
#define VHOST_USER_PROTOCOL_F_DEVICE_STATE 19
|
||||
|
||||
Front-end message types
|
||||
-----------------------
|
||||
|
@ -1042,18 +1197,54 @@ Front-end message types
|
|||
``VHOST_USER_SET_VRING_BASE``
|
||||
:id: 10
|
||||
:equivalent ioctl: ``VHOST_SET_VRING_BASE``
|
||||
:request payload: vring state description
|
||||
:request payload: vring descriptor index/indices
|
||||
:reply payload: N/A
|
||||
|
||||
Sets the base offset in the available vring.
|
||||
Sets the next index to use for descriptors in this vring:
|
||||
|
||||
* For a split virtqueue, sets only the next descriptor index to
|
||||
process in the *Available Ring*. The device is supposed to read the
|
||||
next index in the *Used Ring* from the respective vring structure in
|
||||
guest memory.
|
||||
|
||||
* For a packed virtqueue, both indices are supplied, as they are not
|
||||
explicitly available in memory.
|
||||
|
||||
Consequently, the payload type is specific to the type of virt queue
|
||||
(*a vring descriptor index for split virtqueues* vs. *vring descriptor
|
||||
indices for packed virtqueues*).
|
||||
|
||||
``VHOST_USER_GET_VRING_BASE``
|
||||
:id: 11
|
||||
:equivalent ioctl: ``VHOST_USER_GET_VRING_BASE``
|
||||
:request payload: vring state description
|
||||
:reply payload: vring state description
|
||||
:reply payload: vring descriptor index/indices
|
||||
|
||||
Get the available vring base offset.
|
||||
Stops the vring and returns the current descriptor index or indices:
|
||||
|
||||
* For a split virtqueue, returns only the 16-bit next descriptor
|
||||
index to process in the *Available Ring*. Note that this may
|
||||
differ from the available ring index in the vring structure in
|
||||
memory, which points to where the driver will put new available
|
||||
descriptors. For the *Used Ring*, the device only needs the next
|
||||
descriptor index at which to put new descriptors, which is the
|
||||
value in the vring structure in memory, so this value is not
|
||||
covered by this message.
|
||||
|
||||
* For a packed virtqueue, neither index is explicitly available to
|
||||
read from memory, so both indices (as maintained by the device) are
|
||||
returned.
|
||||
|
||||
Consequently, the payload type is specific to the type of virt queue
|
||||
(*a vring descriptor index for split virtqueues* vs. *vring descriptor
|
||||
indices for packed virtqueues*).
|
||||
|
||||
When and as long as all of a device’s vrings are stopped, it is
|
||||
*suspended*, see :ref:`Suspended device state
|
||||
<suspended_device_state>`.
|
||||
|
||||
The request payload’s *num* field is currently reserved and must be
|
||||
set to 0.
|
||||
|
||||
``VHOST_USER_SET_VRING_KICK``
|
||||
:id: 12
|
||||
|
@ -1464,6 +1655,76 @@ Front-end message types
|
|||
the requested UUID. Back-end will reply passing the fd when the operation
|
||||
is successful, or no fd otherwise.
|
||||
|
||||
``VHOST_USER_SET_DEVICE_STATE_FD``
|
||||
:id: 42
|
||||
:equivalent ioctl: N/A
|
||||
:request payload: device state transfer parameters
|
||||
:reply payload: ``u64``
|
||||
|
||||
Front-end and back-end negotiate a channel over which to transfer the
|
||||
back-end’s internal state during migration. Either side (front-end or
|
||||
back-end) may create the channel. The nature of this channel is not
|
||||
restricted or defined in this document, but whichever side creates it
|
||||
must create a file descriptor that is provided to the respectively
|
||||
other side, allowing access to the channel. This FD must behave as
|
||||
follows:
|
||||
|
||||
* For the writing end, it must allow writing the whole back-end state
|
||||
sequentially. Closing the file descriptor signals the end of
|
||||
transfer.
|
||||
|
||||
* For the reading end, it must allow reading the whole back-end state
|
||||
sequentially. The end of file signals the end of the transfer.
|
||||
|
||||
For example, the channel may be a pipe, in which case the two ends of
|
||||
the pipe fulfill these requirements respectively.
|
||||
|
||||
Initially, the front-end creates a channel along with such an FD. It
|
||||
passes the FD to the back-end as ancillary data of a
|
||||
``VHOST_USER_SET_DEVICE_STATE_FD`` message. The back-end may create a
|
||||
different transfer channel, passing the respective FD back to the
|
||||
front-end as ancillary data of the reply. If so, the front-end must
|
||||
then discard its channel and use the one provided by the back-end.
|
||||
|
||||
Whether the back-end should decide to use its own channel is decided
|
||||
based on efficiency: If the channel is a pipe, both ends will most
|
||||
likely need to copy data into and out of it. Any channel that allows
|
||||
for more efficient processing on at least one end, e.g. through
|
||||
zero-copy, is considered more efficient and thus preferred. If the
|
||||
back-end can provide such a channel, it should decide to use it.
|
||||
|
||||
The request payload contains parameters for the subsequent data
|
||||
transfer, as described in the :ref:`Migrating back-end state
|
||||
<migrating_backend_state>` section.
|
||||
|
||||
The value returned is both an indication for success, and whether a
|
||||
file descriptor for a back-end-provided channel is returned: Bits 0–7
|
||||
are 0 on success, and non-zero on error. Bit 8 is the invalid FD
|
||||
flag; this flag is set when there is no file descriptor returned.
|
||||
When this flag is not set, the front-end must use the returned file
|
||||
descriptor as its end of the transfer channel. The back-end must not
|
||||
both indicate an error and return a file descriptor.
|
||||
|
||||
Using this function requires prior negotiation of the
|
||||
``VHOST_USER_PROTOCOL_F_DEVICE_STATE`` feature.
|
||||
|
||||
``VHOST_USER_CHECK_DEVICE_STATE``
|
||||
:id: 43
|
||||
:equivalent ioctl: N/A
|
||||
:request payload: N/A
|
||||
:reply payload: ``u64``
|
||||
|
||||
After transferring the back-end’s internal state during migration (see
|
||||
the :ref:`Migrating back-end state <migrating_backend_state>`
|
||||
section), check whether the back-end was able to successfully fully
|
||||
process the state.
|
||||
|
||||
The value returned indicates success or error; 0 is success, any
|
||||
non-zero value is an error.
|
||||
|
||||
Using this function requires prior negotiation of the
|
||||
``VHOST_USER_PROTOCOL_F_DEVICE_STATE`` feature.
|
||||
|
||||
Back-end message types
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -93,6 +93,7 @@ Emulated Devices
|
|||
devices/vhost-user.rst
|
||||
devices/virtio-gpu.rst
|
||||
devices/virtio-pmem.rst
|
||||
devices/virtio-snd.rst
|
||||
devices/vhost-user-rng.rst
|
||||
devices/canokey.rst
|
||||
devices/usb-u2f.rst
|
||||
|
|
49
docs/system/devices/virtio-snd.rst
Normal file
49
docs/system/devices/virtio-snd.rst
Normal file
|
@ -0,0 +1,49 @@
|
|||
virtio sound
|
||||
============
|
||||
|
||||
This document explains the setup and usage of the Virtio sound device.
|
||||
The Virtio sound device is a paravirtualized sound card device.
|
||||
|
||||
Linux kernel support
|
||||
--------------------
|
||||
|
||||
Virtio sound requires a guest Linux kernel built with the
|
||||
``CONFIG_SND_VIRTIO`` option.
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Virtio sound implements capture and playback from inside a guest using the
|
||||
configured audio backend of the host machine.
|
||||
|
||||
Device properties
|
||||
-----------------
|
||||
|
||||
The Virtio sound device can be configured with the following properties:
|
||||
|
||||
* ``jacks`` number of physical jacks (Unimplemented).
|
||||
* ``streams`` number of PCM streams. At the moment, no stream configuration is supported: the first one will always be a playback stream, an optional second will always be a capture stream. Adding more will cycle stream directions from playback to capture.
|
||||
* ``chmaps`` number of channel maps (Unimplemented).
|
||||
|
||||
All streams are stereo and have the default channel positions ``Front left, right``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Add an audio device and an audio backend at once with ``-audio`` and ``model=virtio``:
|
||||
|
||||
* pulseaudio: ``-audio driver=pa,model=virtio``
|
||||
or ``-audio driver=pa,model=virtio,server=/run/user/1000/pulse/native``
|
||||
* sdl: ``-audio driver=sdl,model=virtio``
|
||||
* coreaudio: ``-audio driver=coreaudio,model=virtio``
|
||||
|
||||
etc.
|
||||
|
||||
To specifically add virtualized sound devices, you have to specify a PCI device
|
||||
and an audio backend listed with ``-audio driver=help`` that works on your host
|
||||
machine, e.g.:
|
||||
|
||||
::
|
||||
|
||||
-device virtio-sound-pci,audiodev=my_audiodev \
|
||||
-audiodev alsa,id=my_audiodev
|
|
@ -50,3 +50,8 @@ config CS4231
|
|||
|
||||
config ASC
|
||||
bool
|
||||
|
||||
config VIRTIO_SND
|
||||
bool
|
||||
default y
|
||||
depends on VIRTIO
|
||||
|
|
|
@ -13,3 +13,5 @@ system_ss.add(when: 'CONFIG_PL041', if_true: files('pl041.c', 'lm4549.c'))
|
|||
system_ss.add(when: 'CONFIG_SB16', if_true: files('sb16.c'))
|
||||
system_ss.add(when: 'CONFIG_VT82C686', if_true: files('via-ac97.c'))
|
||||
system_ss.add(when: 'CONFIG_WM8750', if_true: files('wm8750.c'))
|
||||
system_ss.add(when: ['CONFIG_VIRTIO_SND', 'CONFIG_VIRTIO'], if_true: files('virtio-snd.c'))
|
||||
system_ss.add(when: ['CONFIG_VIRTIO_SND', 'CONFIG_VIRTIO', 'CONFIG_VIRTIO_PCI'], if_true: files('virtio-snd-pci.c'))
|
||||
|
|
|
@ -38,3 +38,23 @@ asc_write_fifo(const char fifo, int reg, unsigned size, int wrptr, int cnt, uint
|
|||
asc_write_reg(int reg, unsigned size, uint64_t value) "reg=0x%03x size=%u value=0x%"PRIx64
|
||||
asc_write_extreg(const char fifo, int reg, unsigned size, uint64_t value) "fifo %c reg=0x%03x size=%u value=0x%"PRIx64
|
||||
asc_update_irq(int irq, int a, int b) "set IRQ to %d (A: 0x%x B: 0x%x)"
|
||||
|
||||
#virtio-snd.c
|
||||
virtio_snd_get_config(void *vdev, uint32_t jacks, uint32_t streams, uint32_t chmaps) "snd %p: get_config jacks=%"PRIu32" streams=%"PRIu32" chmaps=%"PRIu32""
|
||||
virtio_snd_set_config(void *vdev, uint32_t jacks, uint32_t new_jacks, uint32_t streams, uint32_t new_streams, uint32_t chmaps, uint32_t new_chmaps) "snd %p: set_config jacks from %"PRIu32"->%"PRIu32", streams from %"PRIu32"->%"PRIu32", chmaps from %"PRIu32"->%"PRIu32
|
||||
virtio_snd_get_features(void *vdev, uint64_t features) "snd %p: get_features 0x%"PRIx64
|
||||
virtio_snd_vm_state_running(void) "vm state running"
|
||||
virtio_snd_vm_state_stopped(void) "vm state stopped"
|
||||
virtio_snd_realize(void *snd) "snd %p: realize"
|
||||
virtio_snd_unrealize(void *snd) "snd %p: unrealize"
|
||||
virtio_snd_handle_pcm_set_params(uint32_t stream) "VIRTIO_SND_PCM_SET_PARAMS called for stream %"PRIu32
|
||||
virtio_snd_handle_ctrl(void *vdev, void *vq) "snd %p: handle ctrl event for queue %p"
|
||||
virtio_snd_handle_pcm_info(uint32_t stream) "VIRTIO_SND_R_PCM_INFO called for stream %"PRIu32
|
||||
virtio_snd_handle_pcm_start_stop(const char *code, uint32_t stream) "%s called for stream %"PRIu32
|
||||
virtio_snd_handle_pcm_release(uint32_t stream) "VIRTIO_SND_PCM_RELEASE called for stream %"PRIu32
|
||||
virtio_snd_handle_code(uint32_t val, const char *code) "ctrl code msg val = %"PRIu32" == %s"
|
||||
virtio_snd_handle_chmap_info(void) "VIRTIO_SND_CHMAP_INFO called"
|
||||
virtio_snd_handle_event(void) "event queue callback called"
|
||||
virtio_snd_pcm_stream_flush(uint32_t stream) "flushing stream %"PRIu32
|
||||
virtio_snd_handle_tx_xfer(void) "tx queue callback called"
|
||||
virtio_snd_handle_rx_xfer(void) "rx queue callback called"
|
||||
|
|
93
hw/audio/virtio-snd-pci.c
Normal file
93
hw/audio/virtio-snd-pci.c
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* VIRTIO Sound Device PCI Bindings
|
||||
*
|
||||
* Copyright (c) 2023 Emmanouil Pitsidianakis <manos.pitsidianakis@linaro.org>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or
|
||||
* (at your option) any later version. See the COPYING file in the
|
||||
* top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qom/object.h"
|
||||
#include "qapi/error.h"
|
||||
#include "hw/audio/soundhw.h"
|
||||
#include "hw/virtio/virtio-pci.h"
|
||||
#include "hw/audio/virtio-snd.h"
|
||||
|
||||
/*
|
||||
* virtio-snd-pci: This extends VirtioPCIProxy.
|
||||
*/
|
||||
#define TYPE_VIRTIO_SND_PCI "virtio-sound-pci"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(VirtIOSoundPCI, VIRTIO_SND_PCI)
|
||||
|
||||
struct VirtIOSoundPCI {
|
||||
VirtIOPCIProxy parent_obj;
|
||||
|
||||
VirtIOSound vdev;
|
||||
};
|
||||
|
||||
static Property virtio_snd_pci_properties[] = {
|
||||
DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags,
|
||||
VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, true),
|
||||
DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, 2),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void virtio_snd_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
|
||||
{
|
||||
VirtIOSoundPCI *dev = VIRTIO_SND_PCI(vpci_dev);
|
||||
DeviceState *vdev = DEVICE(&dev->vdev);
|
||||
|
||||
virtio_pci_force_virtio_1(vpci_dev);
|
||||
qdev_realize(vdev, BUS(&vpci_dev->bus), errp);
|
||||
}
|
||||
|
||||
static void virtio_snd_pci_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
VirtioPCIClass *vpciklass = VIRTIO_PCI_CLASS(klass);
|
||||
|
||||
device_class_set_props(dc, virtio_snd_pci_properties);
|
||||
dc->desc = "Virtio Sound";
|
||||
set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
|
||||
|
||||
vpciklass->realize = virtio_snd_pci_realize;
|
||||
}
|
||||
|
||||
static void virtio_snd_pci_instance_init(Object *obj)
|
||||
{
|
||||
VirtIOSoundPCI *dev = VIRTIO_SND_PCI(obj);
|
||||
|
||||
virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
|
||||
TYPE_VIRTIO_SND);
|
||||
}
|
||||
|
||||
static const VirtioPCIDeviceTypeInfo virtio_snd_pci_info = {
|
||||
.generic_name = TYPE_VIRTIO_SND_PCI,
|
||||
.instance_size = sizeof(VirtIOSoundPCI),
|
||||
.instance_init = virtio_snd_pci_instance_init,
|
||||
.class_init = virtio_snd_pci_class_init,
|
||||
};
|
||||
|
||||
/* Create a Virtio Sound PCI device, so '-audio driver,model=virtio' works. */
|
||||
static int virtio_snd_pci_init(PCIBus *bus, const char *audiodev)
|
||||
{
|
||||
DeviceState *vdev = NULL;
|
||||
VirtIOSoundPCI *dev = NULL;
|
||||
|
||||
vdev = qdev_new(TYPE_VIRTIO_SND_PCI);
|
||||
assert(vdev);
|
||||
dev = VIRTIO_SND_PCI(vdev);
|
||||
qdev_prop_set_string(DEVICE(&dev->vdev), "audiodev", audiodev);
|
||||
qdev_realize_and_unref(vdev, BUS(bus), &error_fatal);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void virtio_snd_pci_register(void)
|
||||
{
|
||||
virtio_pci_types_register(&virtio_snd_pci_info);
|
||||
pci_register_soundhw("virtio", "Virtio Sound", virtio_snd_pci_init);
|
||||
}
|
||||
|
||||
type_init(virtio_snd_pci_register);
|
1409
hw/audio/virtio-snd.c
Normal file
1409
hw/audio/virtio-snd.c
Normal file
File diff suppressed because it is too large
Load diff
|
@ -60,7 +60,8 @@ static void ct3_build_cdat(CDATObject *cdat, Error **errp)
|
|||
return;
|
||||
}
|
||||
|
||||
cdat->built_buf_len = cdat->build_cdat_table(&cdat->built_buf, cdat->private);
|
||||
cdat->built_buf_len = cdat->build_cdat_table(&cdat->built_buf,
|
||||
cdat->private);
|
||||
|
||||
if (!cdat->built_buf_len) {
|
||||
/* Build later as not all data available yet */
|
||||
|
|
|
@ -67,16 +67,24 @@ static uint64_t cxl_cache_mem_read_reg(void *opaque, hwaddr offset,
|
|||
CXLComponentState *cxl_cstate = opaque;
|
||||
ComponentRegisters *cregs = &cxl_cstate->crb;
|
||||
|
||||
if (size == 8) {
|
||||
switch (size) {
|
||||
case 4:
|
||||
if (cregs->special_ops && cregs->special_ops->read) {
|
||||
return cregs->special_ops->read(cxl_cstate, offset, 4);
|
||||
} else {
|
||||
QEMU_BUILD_BUG_ON(sizeof(*cregs->cache_mem_registers) != 4);
|
||||
return cregs->cache_mem_registers[offset / 4];
|
||||
}
|
||||
case 8:
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"CXL 8 byte cache mem registers not implemented\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cregs->special_ops && cregs->special_ops->read) {
|
||||
return cregs->special_ops->read(cxl_cstate, offset, size);
|
||||
} else {
|
||||
return cregs->cache_mem_registers[offset / sizeof(*cregs->cache_mem_registers)];
|
||||
default:
|
||||
/*
|
||||
* In line with specifiction limitaions on access sizes, this
|
||||
* routine is not called with other sizes.
|
||||
*/
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,25 +125,37 @@ static void cxl_cache_mem_write_reg(void *opaque, hwaddr offset, uint64_t value,
|
|||
ComponentRegisters *cregs = &cxl_cstate->crb;
|
||||
uint32_t mask;
|
||||
|
||||
if (size == 8) {
|
||||
switch (size) {
|
||||
case 4: {
|
||||
QEMU_BUILD_BUG_ON(sizeof(*cregs->cache_mem_regs_write_mask) != 4);
|
||||
QEMU_BUILD_BUG_ON(sizeof(*cregs->cache_mem_registers) != 4);
|
||||
mask = cregs->cache_mem_regs_write_mask[offset / 4];
|
||||
value &= mask;
|
||||
/* RO bits should remain constant. Done by reading existing value */
|
||||
value |= ~mask & cregs->cache_mem_registers[offset / 4];
|
||||
if (cregs->special_ops && cregs->special_ops->write) {
|
||||
cregs->special_ops->write(cxl_cstate, offset, value, size);
|
||||
return;
|
||||
}
|
||||
|
||||
if (offset >= A_CXL_HDM_DECODER_CAPABILITY &&
|
||||
offset <= A_CXL_HDM_DECODER3_TARGET_LIST_HI) {
|
||||
dumb_hdm_handler(cxl_cstate, offset, value);
|
||||
} else {
|
||||
cregs->cache_mem_registers[offset / 4] = value;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 8:
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"CXL 8 byte cache mem registers not implemented\n");
|
||||
return;
|
||||
}
|
||||
mask = cregs->cache_mem_regs_write_mask[offset / sizeof(*cregs->cache_mem_regs_write_mask)];
|
||||
value &= mask;
|
||||
/* RO bits should remain constant. Done by reading existing value */
|
||||
value |= ~mask & cregs->cache_mem_registers[offset / sizeof(*cregs->cache_mem_registers)];
|
||||
if (cregs->special_ops && cregs->special_ops->write) {
|
||||
cregs->special_ops->write(cxl_cstate, offset, value, size);
|
||||
return;
|
||||
}
|
||||
|
||||
if (offset >= A_CXL_HDM_DECODER_CAPABILITY &&
|
||||
offset <= A_CXL_HDM_DECODER3_TARGET_LIST_HI) {
|
||||
dumb_hdm_handler(cxl_cstate, offset, value);
|
||||
} else {
|
||||
cregs->cache_mem_registers[offset / sizeof(*cregs->cache_mem_registers)] = value;
|
||||
default:
|
||||
/*
|
||||
* In line with specifiction limitaions on access sizes, this
|
||||
* routine is not called with other sizes.
|
||||
*/
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -221,7 +241,8 @@ static void hdm_init_common(uint32_t *reg_state, uint32_t *write_msk,
|
|||
ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, TARGET_COUNT, 1);
|
||||
ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, INTERLEAVE_256B, 1);
|
||||
ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, INTERLEAVE_4K, 1);
|
||||
ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, POISON_ON_ERR_CAP, 0);
|
||||
ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY,
|
||||
POISON_ON_ERR_CAP, 0);
|
||||
ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_GLOBAL_CONTROL,
|
||||
HDM_DECODER_ENABLE, 0);
|
||||
write_msk[R_CXL_HDM_DECODER_GLOBAL_CONTROL] = 0x3;
|
||||
|
@ -244,15 +265,16 @@ static void hdm_init_common(uint32_t *reg_state, uint32_t *write_msk,
|
|||
}
|
||||
}
|
||||
|
||||
void cxl_component_register_init_common(uint32_t *reg_state, uint32_t *write_msk,
|
||||
void cxl_component_register_init_common(uint32_t *reg_state,
|
||||
uint32_t *write_msk,
|
||||
enum reg_type type)
|
||||
{
|
||||
int caps = 0;
|
||||
|
||||
/*
|
||||
* In CXL 2.0 the capabilities required for each CXL component are such that,
|
||||
* with the ordering chosen here, a single number can be used to define
|
||||
* which capabilities should be provided.
|
||||
* In CXL 2.0 the capabilities required for each CXL component are such
|
||||
* that, with the ordering chosen here, a single number can be used to
|
||||
* define which capabilities should be provided.
|
||||
*/
|
||||
switch (type) {
|
||||
case CXL2_DOWNSTREAM_PORT:
|
||||
|
@ -283,7 +305,6 @@ void cxl_component_register_init_common(uint32_t *reg_state, uint32_t *write_msk
|
|||
ARRAY_FIELD_DP32(reg_state, CXL_CAPABILITY_HEADER, ARRAY_SIZE, caps);
|
||||
|
||||
#define init_cap_reg(reg, id, version) \
|
||||
QEMU_BUILD_BUG_ON(CXL_##reg##_REGISTERS_OFFSET == 0); \
|
||||
do { \
|
||||
int which = R_CXL_##reg##_CAPABILITY_HEADER; \
|
||||
reg_state[which] = FIELD_DP32(reg_state[which], \
|
||||
|
@ -373,26 +394,35 @@ void cxl_component_create_dvsec(CXLComponentState *cxl,
|
|||
case NON_CXL_FUNCTION_MAP_DVSEC:
|
||||
break; /* Not yet implemented */
|
||||
case EXTENSIONS_PORT_DVSEC:
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, control)] = 0x0F;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, control) + 1] = 0x40;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_bus_base)] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_bus_limit)] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_memory_base)] = 0xF0;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_memory_base) + 1] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_memory_limit)] = 0xF0;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_memory_limit) + 1] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_prefetch_base)] = 0xF0;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_prefetch_base) + 1] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_prefetch_limit)] = 0xF0;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_prefetch_limit) + 1] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_prefetch_base_high)] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_prefetch_base_high) + 1] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_prefetch_base_high) + 2] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_prefetch_base_high) + 3] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_prefetch_limit_high)] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_prefetch_limit_high) + 1] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_prefetch_limit_high) + 2] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExtensions, alt_prefetch_limit_high) + 3] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, control)] = 0x0F;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, control) + 1] = 0x40;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_bus_base)] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_bus_limit)] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_memory_base)] = 0xF0;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_memory_base) + 1] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_memory_limit)] = 0xF0;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_memory_limit) + 1] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_prefetch_base)] = 0xF0;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_prefetch_base) + 1] = 0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_prefetch_limit)] = 0xF0;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_prefetch_limit) + 1] =
|
||||
0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_prefetch_base_high)] =
|
||||
0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_prefetch_base_high) + 1] =
|
||||
0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_prefetch_base_high) + 2] =
|
||||
0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_prefetch_base_high) + 3] =
|
||||
0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_prefetch_limit_high)] =
|
||||
0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_prefetch_limit_high) + 1] =
|
||||
0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_prefetch_limit_high) + 2] =
|
||||
0xFF;
|
||||
wmask[offset + offsetof(CXLDVSECPortExt, alt_prefetch_limit_high) + 3] =
|
||||
0xFF;
|
||||
break;
|
||||
case GPF_PORT_DVSEC:
|
||||
wmask[offset + offsetof(CXLDVSECPortGPF, phase1_ctrl)] = 0x0F;
|
||||
|
@ -420,7 +450,7 @@ void cxl_component_create_dvsec(CXLComponentState *cxl,
|
|||
default: /* Registers are RO for other component types */
|
||||
break;
|
||||
}
|
||||
/* There are rw1cs bits in the status register but never set currently */
|
||||
/* There are rw1cs bits in the status register but never set */
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,10 +32,13 @@ static uint64_t caps_reg_read(void *opaque, hwaddr offset, unsigned size)
|
|||
{
|
||||
CXLDeviceState *cxl_dstate = opaque;
|
||||
|
||||
if (size == 4) {
|
||||
return cxl_dstate->caps_reg_state32[offset / sizeof(*cxl_dstate->caps_reg_state32)];
|
||||
} else {
|
||||
return cxl_dstate->caps_reg_state64[offset / sizeof(*cxl_dstate->caps_reg_state64)];
|
||||
switch (size) {
|
||||
case 4:
|
||||
return cxl_dstate->caps_reg_state32[offset / size];
|
||||
case 8:
|
||||
return cxl_dstate->caps_reg_state64[offset / size];
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,7 +62,17 @@ static uint64_t dev_reg_read(void *opaque, hwaddr offset, unsigned size)
|
|||
|
||||
static uint64_t mailbox_reg_read(void *opaque, hwaddr offset, unsigned size)
|
||||
{
|
||||
CXLDeviceState *cxl_dstate = opaque;
|
||||
CXLDeviceState *cxl_dstate;
|
||||
CXLCCI *cci = opaque;
|
||||
|
||||
if (object_dynamic_cast(OBJECT(cci->intf), TYPE_CXL_TYPE3)) {
|
||||
cxl_dstate = &CXL_TYPE3(cci->intf)->cxl_dstate;
|
||||
} else if (object_dynamic_cast(OBJECT(cci->intf),
|
||||
TYPE_CXL_SWITCH_MAILBOX_CCI)) {
|
||||
cxl_dstate = &CXL_SWITCH_MAILBOX_CCI(cci->intf)->cxl_dstate;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (size) {
|
||||
case 1:
|
||||
|
@ -69,6 +82,25 @@ static uint64_t mailbox_reg_read(void *opaque, hwaddr offset, unsigned size)
|
|||
case 4:
|
||||
return cxl_dstate->mbox_reg_state32[offset / size];
|
||||
case 8:
|
||||
if (offset == A_CXL_DEV_BG_CMD_STS) {
|
||||
uint64_t bg_status_reg;
|
||||
bg_status_reg = FIELD_DP64(0, CXL_DEV_BG_CMD_STS, OP,
|
||||
cci->bg.opcode);
|
||||
bg_status_reg = FIELD_DP64(bg_status_reg, CXL_DEV_BG_CMD_STS,
|
||||
PERCENTAGE_COMP, cci->bg.complete_pct);
|
||||
bg_status_reg = FIELD_DP64(bg_status_reg, CXL_DEV_BG_CMD_STS,
|
||||
RET_CODE, cci->bg.ret_code);
|
||||
/* endian? */
|
||||
cxl_dstate->mbox_reg_state64[offset / size] = bg_status_reg;
|
||||
}
|
||||
if (offset == A_CXL_DEV_MAILBOX_STS) {
|
||||
uint64_t status_reg = cxl_dstate->mbox_reg_state64[offset / size];
|
||||
if (cci->bg.complete_pct) {
|
||||
status_reg = FIELD_DP64(status_reg, CXL_DEV_MAILBOX_STS, BG_OP,
|
||||
0);
|
||||
cxl_dstate->mbox_reg_state64[offset / size] = status_reg;
|
||||
}
|
||||
}
|
||||
return cxl_dstate->mbox_reg_state64[offset / size];
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
|
@ -101,8 +133,7 @@ static void mailbox_mem_writeq(uint64_t *reg_state, hwaddr offset,
|
|||
case A_CXL_DEV_MAILBOX_CMD:
|
||||
break;
|
||||
case A_CXL_DEV_BG_CMD_STS:
|
||||
/* BG not supported */
|
||||
/* fallthrough */
|
||||
break;
|
||||
case A_CXL_DEV_MAILBOX_STS:
|
||||
/* Read only register, will get updated by the state machine */
|
||||
return;
|
||||
|
@ -120,7 +151,17 @@ static void mailbox_mem_writeq(uint64_t *reg_state, hwaddr offset,
|
|||
static void mailbox_reg_write(void *opaque, hwaddr offset, uint64_t value,
|
||||
unsigned size)
|
||||
{
|
||||
CXLDeviceState *cxl_dstate = opaque;
|
||||
CXLDeviceState *cxl_dstate;
|
||||
CXLCCI *cci = opaque;
|
||||
|
||||
if (object_dynamic_cast(OBJECT(cci->intf), TYPE_CXL_TYPE3)) {
|
||||
cxl_dstate = &CXL_TYPE3(cci->intf)->cxl_dstate;
|
||||
} else if (object_dynamic_cast(OBJECT(cci->intf),
|
||||
TYPE_CXL_SWITCH_MAILBOX_CCI)) {
|
||||
cxl_dstate = &CXL_SWITCH_MAILBOX_CCI(cci->intf)->cxl_dstate;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (offset >= A_CXL_DEV_CMD_PAYLOAD) {
|
||||
memcpy(cxl_dstate->mbox_reg_state + offset, &value, size);
|
||||
|
@ -140,7 +181,49 @@ static void mailbox_reg_write(void *opaque, hwaddr offset, uint64_t value,
|
|||
|
||||
if (ARRAY_FIELD_EX32(cxl_dstate->mbox_reg_state32, CXL_DEV_MAILBOX_CTRL,
|
||||
DOORBELL)) {
|
||||
cxl_process_mailbox(cxl_dstate);
|
||||
uint64_t command_reg =
|
||||
cxl_dstate->mbox_reg_state64[R_CXL_DEV_MAILBOX_CMD];
|
||||
uint8_t cmd_set = FIELD_EX64(command_reg, CXL_DEV_MAILBOX_CMD,
|
||||
COMMAND_SET);
|
||||
uint8_t cmd = FIELD_EX64(command_reg, CXL_DEV_MAILBOX_CMD, COMMAND);
|
||||
size_t len_in = FIELD_EX64(command_reg, CXL_DEV_MAILBOX_CMD, LENGTH);
|
||||
uint8_t *pl = cxl_dstate->mbox_reg_state + A_CXL_DEV_CMD_PAYLOAD;
|
||||
/*
|
||||
* Copy taken to avoid need for individual command handlers to care
|
||||
* about aliasing.
|
||||
*/
|
||||
g_autofree uint8_t *pl_in_copy = NULL;
|
||||
size_t len_out = 0;
|
||||
uint64_t status_reg;
|
||||
bool bg_started = false;
|
||||
int rc;
|
||||
|
||||
pl_in_copy = g_memdup2(pl, len_in);
|
||||
if (len_in == 0 || pl_in_copy) {
|
||||
/* Avoid stale data - including from earlier cmds */
|
||||
memset(pl, 0, CXL_MAILBOX_MAX_PAYLOAD_SIZE);
|
||||
rc = cxl_process_cci_message(cci, cmd_set, cmd, len_in, pl_in_copy,
|
||||
&len_out, pl, &bg_started);
|
||||
} else {
|
||||
rc = CXL_MBOX_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
/* Set bg and the return code */
|
||||
status_reg = FIELD_DP64(0, CXL_DEV_MAILBOX_STS, BG_OP,
|
||||
bg_started ? 1 : 0);
|
||||
status_reg = FIELD_DP64(status_reg, CXL_DEV_MAILBOX_STS, ERRNO, rc);
|
||||
/* Set the return length */
|
||||
command_reg = FIELD_DP64(0, CXL_DEV_MAILBOX_CMD, COMMAND_SET, cmd_set);
|
||||
command_reg = FIELD_DP64(command_reg, CXL_DEV_MAILBOX_CMD,
|
||||
COMMAND, cmd);
|
||||
command_reg = FIELD_DP64(command_reg, CXL_DEV_MAILBOX_CMD,
|
||||
LENGTH, len_out);
|
||||
|
||||
cxl_dstate->mbox_reg_state64[R_CXL_DEV_MAILBOX_CMD] = command_reg;
|
||||
cxl_dstate->mbox_reg_state64[R_CXL_DEV_MAILBOX_STS] = status_reg;
|
||||
/* Tell the host we're done */
|
||||
ARRAY_FIELD_DP32(cxl_dstate->mbox_reg_state32, CXL_DEV_MAILBOX_CTRL,
|
||||
DOORBELL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,7 +303,8 @@ static const MemoryRegionOps caps_ops = {
|
|||
},
|
||||
};
|
||||
|
||||
void cxl_device_register_block_init(Object *obj, CXLDeviceState *cxl_dstate)
|
||||
void cxl_device_register_block_init(Object *obj, CXLDeviceState *cxl_dstate,
|
||||
CXLCCI *cci)
|
||||
{
|
||||
/* This will be a BAR, so needs to be rounded up to pow2 for PCI spec */
|
||||
memory_region_init(&cxl_dstate->device_registers, obj, "device-registers",
|
||||
|
@ -230,7 +314,7 @@ void cxl_device_register_block_init(Object *obj, CXLDeviceState *cxl_dstate)
|
|||
"cap-array", CXL_CAPS_SIZE);
|
||||
memory_region_init_io(&cxl_dstate->device, obj, &dev_ops, cxl_dstate,
|
||||
"device-status", CXL_DEVICE_STATUS_REGISTERS_LENGTH);
|
||||
memory_region_init_io(&cxl_dstate->mailbox, obj, &mailbox_ops, cxl_dstate,
|
||||
memory_region_init_io(&cxl_dstate->mailbox, obj, &mailbox_ops, cci,
|
||||
"mailbox", CXL_MAILBOX_REGISTERS_LENGTH);
|
||||
memory_region_init_io(&cxl_dstate->memory_device, obj, &mdev_ops,
|
||||
cxl_dstate, "memory device caps",
|
||||
|
@ -273,16 +357,25 @@ static void device_reg_init_common(CXLDeviceState *cxl_dstate)
|
|||
|
||||
static void mailbox_reg_init_common(CXLDeviceState *cxl_dstate)
|
||||
{
|
||||
/* 2048 payload size, with no interrupt or background support */
|
||||
const uint8_t msi_n = 9;
|
||||
|
||||
/* 2048 payload size */
|
||||
ARRAY_FIELD_DP32(cxl_dstate->mbox_reg_state32, CXL_DEV_MAILBOX_CAP,
|
||||
PAYLOAD_SIZE, CXL_MAILBOX_PAYLOAD_SHIFT);
|
||||
cxl_dstate->payload_size = CXL_MAILBOX_MAX_PAYLOAD_SIZE;
|
||||
/* irq support */
|
||||
ARRAY_FIELD_DP32(cxl_dstate->mbox_reg_state32, CXL_DEV_MAILBOX_CAP,
|
||||
BG_INT_CAP, 1);
|
||||
ARRAY_FIELD_DP32(cxl_dstate->mbox_reg_state32, CXL_DEV_MAILBOX_CAP,
|
||||
MSI_N, msi_n);
|
||||
cxl_dstate->mbox_msi_n = msi_n;
|
||||
}
|
||||
|
||||
static void memdev_reg_init_common(CXLDeviceState *cxl_dstate) { }
|
||||
|
||||
void cxl_device_register_init_common(CXLDeviceState *cxl_dstate)
|
||||
void cxl_device_register_init_t3(CXLType3Dev *ct3d)
|
||||
{
|
||||
CXLDeviceState *cxl_dstate = &ct3d->cxl_dstate;
|
||||
uint64_t *cap_h = cxl_dstate->caps_reg_state64;
|
||||
const int cap_count = 3;
|
||||
|
||||
|
@ -300,7 +393,29 @@ void cxl_device_register_init_common(CXLDeviceState *cxl_dstate)
|
|||
cxl_device_cap_init(cxl_dstate, MEMORY_DEVICE, 0x4000, 1);
|
||||
memdev_reg_init_common(cxl_dstate);
|
||||
|
||||
cxl_initialize_mailbox(cxl_dstate);
|
||||
cxl_initialize_mailbox_t3(&ct3d->cci, DEVICE(ct3d),
|
||||
CXL_MAILBOX_MAX_PAYLOAD_SIZE);
|
||||
}
|
||||
|
||||
void cxl_device_register_init_swcci(CSWMBCCIDev *sw)
|
||||
{
|
||||
CXLDeviceState *cxl_dstate = &sw->cxl_dstate;
|
||||
uint64_t *cap_h = cxl_dstate->caps_reg_state64;
|
||||
const int cap_count = 3;
|
||||
|
||||
/* CXL Device Capabilities Array Register */
|
||||
ARRAY_FIELD_DP64(cap_h, CXL_DEV_CAP_ARRAY, CAP_ID, 0);
|
||||
ARRAY_FIELD_DP64(cap_h, CXL_DEV_CAP_ARRAY, CAP_VERSION, 1);
|
||||
ARRAY_FIELD_DP64(cap_h, CXL_DEV_CAP_ARRAY, CAP_COUNT, cap_count);
|
||||
|
||||
cxl_device_cap_init(cxl_dstate, DEVICE_STATUS, 1, 2);
|
||||
device_reg_init_common(cxl_dstate);
|
||||
|
||||
cxl_device_cap_init(cxl_dstate, MAILBOX, 2, 1);
|
||||
mailbox_reg_init_common(cxl_dstate);
|
||||
|
||||
cxl_device_cap_init(cxl_dstate, MEMORY_DEVICE, 0x4000, 1);
|
||||
memdev_reg_init_common(cxl_dstate);
|
||||
}
|
||||
|
||||
uint64_t cxl_device_get_timestamp(CXLDeviceState *cxl_dstate)
|
||||
|
|
|
@ -143,7 +143,7 @@ bool cxl_event_insert(CXLDeviceState *cxlds, CXLEventLogType log_type,
|
|||
|
||||
CXLRetCode cxl_event_get_records(CXLDeviceState *cxlds, CXLGetEventPayload *pl,
|
||||
uint8_t log_type, int max_recs,
|
||||
uint16_t *len)
|
||||
size_t *len)
|
||||
{
|
||||
CXLEventLog *log;
|
||||
CXLEvent *entry;
|
||||
|
@ -170,8 +170,10 @@ CXLRetCode cxl_event_get_records(CXLDeviceState *cxlds, CXLGetEventPayload *pl,
|
|||
if (log->overflow_err_count) {
|
||||
pl->flags |= CXL_GET_EVENT_FLAG_OVERFLOW;
|
||||
pl->overflow_err_count = cpu_to_le16(log->overflow_err_count);
|
||||
pl->first_overflow_timestamp = cpu_to_le64(log->first_overflow_timestamp);
|
||||
pl->last_overflow_timestamp = cpu_to_le64(log->last_overflow_timestamp);
|
||||
pl->first_overflow_timestamp =
|
||||
cpu_to_le64(log->first_overflow_timestamp);
|
||||
pl->last_overflow_timestamp =
|
||||
cpu_to_le64(log->last_overflow_timestamp);
|
||||
}
|
||||
|
||||
pl->record_count = cpu_to_le16(nr);
|
||||
|
@ -180,7 +182,8 @@ CXLRetCode cxl_event_get_records(CXLDeviceState *cxlds, CXLGetEventPayload *pl,
|
|||
return CXL_MBOX_SUCCESS;
|
||||
}
|
||||
|
||||
CXLRetCode cxl_event_clear_records(CXLDeviceState *cxlds, CXLClearEventPayload *pl)
|
||||
CXLRetCode cxl_event_clear_records(CXLDeviceState *cxlds,
|
||||
CXLClearEventPayload *pl)
|
||||
{
|
||||
CXLEventLog *log;
|
||||
uint8_t log_type;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -6,6 +6,7 @@ system_ss.add(when: 'CONFIG_CXL',
|
|||
'cxl-host.c',
|
||||
'cxl-cdat.c',
|
||||
'cxl-events.c',
|
||||
'switch-mailbox-cci.c',
|
||||
),
|
||||
if_false: files(
|
||||
'cxl-host-stubs.c',
|
||||
|
|
111
hw/cxl/switch-mailbox-cci.c
Normal file
111
hw/cxl/switch-mailbox-cci.c
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Emulation of a CXL Switch Mailbox CCI PCIe function.
|
||||
*
|
||||
* Copyright (c) 2023 Huawei Technologies.
|
||||
*
|
||||
* From www.computeexpresslink.org
|
||||
* Compute Express Link (CXL) Specification revision 3.0 Version 1.0
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/pci/pci.h"
|
||||
#include "hw/pci-bridge/cxl_upstream_port.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/module.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "hw/cxl/cxl.h"
|
||||
|
||||
static void cswmbcci_reset(DeviceState *dev)
|
||||
{
|
||||
CSWMBCCIDev *cswmb = CXL_SWITCH_MAILBOX_CCI(dev);
|
||||
cxl_device_register_init_swcci(cswmb);
|
||||
}
|
||||
|
||||
static void cswbcci_realize(PCIDevice *pci_dev, Error **errp)
|
||||
{
|
||||
CSWMBCCIDev *cswmb = CXL_SWITCH_MAILBOX_CCI(pci_dev);
|
||||
CXLComponentState *cxl_cstate = &cswmb->cxl_cstate;
|
||||
CXLDeviceState *cxl_dstate = &cswmb->cxl_dstate;
|
||||
CXLDVSECRegisterLocator *regloc_dvsec;
|
||||
CXLUpstreamPort *usp;
|
||||
|
||||
if (!cswmb->target) {
|
||||
error_setg(errp, "Target not set");
|
||||
return;
|
||||
}
|
||||
usp = CXL_USP(cswmb->target);
|
||||
|
||||
pcie_endpoint_cap_init(pci_dev, 0x80);
|
||||
cxl_cstate->dvsec_offset = 0x100;
|
||||
cxl_cstate->pdev = pci_dev;
|
||||
cswmb->cci = &usp->swcci;
|
||||
cxl_device_register_block_init(OBJECT(pci_dev), cxl_dstate, cswmb->cci);
|
||||
pci_register_bar(pci_dev, 0,
|
||||
PCI_BASE_ADDRESS_SPACE_MEMORY |
|
||||
PCI_BASE_ADDRESS_MEM_TYPE_64,
|
||||
&cxl_dstate->device_registers);
|
||||
regloc_dvsec = &(CXLDVSECRegisterLocator) {
|
||||
.rsvd = 0,
|
||||
.reg0_base_lo = RBI_CXL_DEVICE_REG | 0,
|
||||
.reg0_base_hi = 0,
|
||||
};
|
||||
cxl_component_create_dvsec(cxl_cstate, CXL3_SWITCH_MAILBOX_CCI,
|
||||
REG_LOC_DVSEC_LENGTH, REG_LOC_DVSEC,
|
||||
REG_LOC_DVSEC_REVID, (uint8_t *)regloc_dvsec);
|
||||
|
||||
cxl_initialize_mailbox_swcci(cswmb->cci, DEVICE(pci_dev),
|
||||
DEVICE(cswmb->target),
|
||||
CXL_MAILBOX_MAX_PAYLOAD_SIZE);
|
||||
}
|
||||
|
||||
static void cswmbcci_exit(PCIDevice *pci_dev)
|
||||
{
|
||||
/* Nothing to do here yet */
|
||||
}
|
||||
|
||||
static Property cxl_switch_cci_props[] = {
|
||||
DEFINE_PROP_LINK("target", CSWMBCCIDev,
|
||||
target, TYPE_CXL_USP, PCIDevice *),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void cswmbcci_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(oc);
|
||||
PCIDeviceClass *pc = PCI_DEVICE_CLASS(oc);
|
||||
|
||||
pc->realize = cswbcci_realize;
|
||||
pc->exit = cswmbcci_exit;
|
||||
/* Serial bus, CXL Switch CCI */
|
||||
pc->class_id = 0x0c0b;
|
||||
/*
|
||||
* Huawei Technologies
|
||||
* CXL Switch Mailbox CCI - DID assigned for emulation only.
|
||||
* No real hardware will ever use this ID.
|
||||
*/
|
||||
pc->vendor_id = 0x19e5;
|
||||
pc->device_id = 0xa123;
|
||||
pc->revision = 0;
|
||||
dc->desc = "CXL Switch Mailbox CCI";
|
||||
dc->reset = cswmbcci_reset;
|
||||
device_class_set_props(dc, cxl_switch_cci_props);
|
||||
}
|
||||
|
||||
static const TypeInfo cswmbcci_info = {
|
||||
.name = TYPE_CXL_SWITCH_MAILBOX_CCI,
|
||||
.parent = TYPE_PCI_DEVICE,
|
||||
.class_init = cswmbcci_class_init,
|
||||
.instance_size = sizeof(CSWMBCCIDev),
|
||||
.interfaces = (InterfaceInfo[]) {
|
||||
{ INTERFACE_PCIE_DEVICE },
|
||||
{ }
|
||||
},
|
||||
};
|
||||
|
||||
static void cxl_switch_mailbox_cci_register(void)
|
||||
{
|
||||
type_register_static(&cswmbcci_info);
|
||||
}
|
||||
type_init(cxl_switch_mailbox_cci_register);
|
|
@ -23,6 +23,7 @@
|
|||
#include "qemu/pmem.h"
|
||||
#include "qemu/range.h"
|
||||
#include "qemu/rcu.h"
|
||||
#include "qemu/guest-random.h"
|
||||
#include "sysemu/hostmem.h"
|
||||
#include "sysemu/numa.h"
|
||||
#include "hw/cxl/cxl.h"
|
||||
|
@ -208,10 +209,9 @@ static int ct3_build_cdat_table(CDATSubHeader ***cdat_table, void *priv)
|
|||
}
|
||||
|
||||
if (nonvolatile_mr) {
|
||||
uint64_t base = volatile_mr ? memory_region_size(volatile_mr) : 0;
|
||||
rc = ct3_build_cdat_entries_for_mr(&(table[cur_ent]), dsmad_handle++,
|
||||
nonvolatile_mr, true,
|
||||
(volatile_mr ?
|
||||
memory_region_size(volatile_mr) : 0));
|
||||
nonvolatile_mr, true, base);
|
||||
if (rc < 0) {
|
||||
goto error_cleanup;
|
||||
}
|
||||
|
@ -514,7 +514,8 @@ static void ct3d_reg_write(void *opaque, hwaddr offset, uint64_t value,
|
|||
case A_CXL_RAS_UNC_ERR_STATUS:
|
||||
{
|
||||
uint32_t capctrl = ldl_le_p(cache_mem + R_CXL_RAS_ERR_CAP_CTRL);
|
||||
uint32_t fe = FIELD_EX32(capctrl, CXL_RAS_ERR_CAP_CTRL, FIRST_ERROR_POINTER);
|
||||
uint32_t fe = FIELD_EX32(capctrl, CXL_RAS_ERR_CAP_CTRL,
|
||||
FIRST_ERROR_POINTER);
|
||||
CXLError *cxl_err;
|
||||
uint32_t unc_err;
|
||||
|
||||
|
@ -533,7 +534,8 @@ static void ct3d_reg_write(void *opaque, hwaddr offset, uint64_t value,
|
|||
* closest to behavior of hardware not capable of multiple
|
||||
* header recording.
|
||||
*/
|
||||
QTAILQ_FOREACH_SAFE(cxl_err, &ct3d->error_list, node, cxl_next) {
|
||||
QTAILQ_FOREACH_SAFE(cxl_err, &ct3d->error_list, node,
|
||||
cxl_next) {
|
||||
if ((1 << cxl_err->type) & value) {
|
||||
QTAILQ_REMOVE(&ct3d->error_list, cxl_err, node);
|
||||
g_free(cxl_err);
|
||||
|
@ -715,7 +717,8 @@ static void ct3_realize(PCIDevice *pci_dev, Error **errp)
|
|||
pci_dev, CXL_COMPONENT_REG_BAR_IDX,
|
||||
PCI_BASE_ADDRESS_SPACE_MEMORY | PCI_BASE_ADDRESS_MEM_TYPE_64, mr);
|
||||
|
||||
cxl_device_register_block_init(OBJECT(pci_dev), &ct3d->cxl_dstate);
|
||||
cxl_device_register_block_init(OBJECT(pci_dev), &ct3d->cxl_dstate,
|
||||
&ct3d->cci);
|
||||
pci_register_bar(pci_dev, CXL_DEVICE_REG_BAR_IDX,
|
||||
PCI_BASE_ADDRESS_SPACE_MEMORY |
|
||||
PCI_BASE_ADDRESS_MEM_TYPE_64,
|
||||
|
@ -885,32 +888,43 @@ static int cxl_type3_hpa_to_as_and_dpa(CXLType3Dev *ct3d,
|
|||
MemTxResult cxl_type3_read(PCIDevice *d, hwaddr host_addr, uint64_t *data,
|
||||
unsigned size, MemTxAttrs attrs)
|
||||
{
|
||||
CXLType3Dev *ct3d = CXL_TYPE3(d);
|
||||
uint64_t dpa_offset = 0;
|
||||
AddressSpace *as = NULL;
|
||||
int res;
|
||||
|
||||
res = cxl_type3_hpa_to_as_and_dpa(CXL_TYPE3(d), host_addr, size,
|
||||
res = cxl_type3_hpa_to_as_and_dpa(ct3d, host_addr, size,
|
||||
&as, &dpa_offset);
|
||||
if (res) {
|
||||
return MEMTX_ERROR;
|
||||
}
|
||||
|
||||
if (sanitize_running(&ct3d->cci)) {
|
||||
qemu_guest_getrandom_nofail(data, size);
|
||||
return MEMTX_OK;
|
||||
}
|
||||
|
||||
return address_space_read(as, dpa_offset, attrs, data, size);
|
||||
}
|
||||
|
||||
MemTxResult cxl_type3_write(PCIDevice *d, hwaddr host_addr, uint64_t data,
|
||||
unsigned size, MemTxAttrs attrs)
|
||||
{
|
||||
CXLType3Dev *ct3d = CXL_TYPE3(d);
|
||||
uint64_t dpa_offset = 0;
|
||||
AddressSpace *as = NULL;
|
||||
int res;
|
||||
|
||||
res = cxl_type3_hpa_to_as_and_dpa(CXL_TYPE3(d), host_addr, size,
|
||||
res = cxl_type3_hpa_to_as_and_dpa(ct3d, host_addr, size,
|
||||
&as, &dpa_offset);
|
||||
if (res) {
|
||||
return MEMTX_ERROR;
|
||||
}
|
||||
|
||||
if (sanitize_running(&ct3d->cci)) {
|
||||
return MEMTX_OK;
|
||||
}
|
||||
|
||||
return address_space_write(as, dpa_offset, attrs, &data, size);
|
||||
}
|
||||
|
||||
|
@ -921,7 +935,18 @@ static void ct3d_reset(DeviceState *dev)
|
|||
uint32_t *write_msk = ct3d->cxl_cstate.crb.cache_mem_regs_write_mask;
|
||||
|
||||
cxl_component_register_init_common(reg_state, write_msk, CXL2_TYPE3_DEVICE);
|
||||
cxl_device_register_init_common(&ct3d->cxl_dstate);
|
||||
cxl_device_register_init_t3(ct3d);
|
||||
|
||||
/*
|
||||
* Bring up an endpoint to target with MCTP over VDM.
|
||||
* This device is emulating an MLD with single LD for now.
|
||||
*/
|
||||
cxl_initialize_t3_fm_owned_ld_mctpcci(&ct3d->vdm_fm_owned_ld_mctp_cci,
|
||||
DEVICE(ct3d), DEVICE(ct3d),
|
||||
512); /* Max payload made up */
|
||||
cxl_initialize_t3_ld_cci(&ct3d->ld0_cci, DEVICE(ct3d), DEVICE(ct3d),
|
||||
512); /* Max payload made up */
|
||||
|
||||
}
|
||||
|
||||
static Property ct3_props[] = {
|
||||
|
@ -1072,7 +1097,8 @@ void qmp_cxl_inject_poison(const char *path, uint64_t start, uint64_t length,
|
|||
if (((start >= p->start) && (start < p->start + p->length)) ||
|
||||
((start + length > p->start) &&
|
||||
(start + length <= p->start + p->length))) {
|
||||
error_setg(errp, "Overlap with existing poisoned region not supported");
|
||||
error_setg(errp,
|
||||
"Overlap with existing poisoned region not supported");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1085,7 +1111,8 @@ void qmp_cxl_inject_poison(const char *path, uint64_t start, uint64_t length,
|
|||
p = g_new0(CXLPoison, 1);
|
||||
p->length = length;
|
||||
p->start = start;
|
||||
p->type = CXL_POISON_TYPE_INTERNAL; /* Different from injected via the mbox */
|
||||
/* Different from injected via the mbox */
|
||||
p->type = CXL_POISON_TYPE_INTERNAL;
|
||||
|
||||
QLIST_INSERT_HEAD(&ct3d->poison_list, p, node);
|
||||
ct3d->poison_list_cnt++;
|
||||
|
@ -1222,7 +1249,8 @@ void qmp_cxl_inject_correctable_error(const char *path, CxlCorErrorType type,
|
|||
return;
|
||||
}
|
||||
/* If the error is masked, nothting to do here */
|
||||
if (!((1 << cxl_err_type) & ~ldl_le_p(reg_state + R_CXL_RAS_COR_ERR_MASK))) {
|
||||
if (!((1 << cxl_err_type) &
|
||||
~ldl_le_p(reg_state + R_CXL_RAS_COR_ERR_MASK))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1372,7 +1400,8 @@ void qmp_cxl_inject_dram_event(const char *path, CxlEventLog log, uint8_t flags,
|
|||
bool has_bank, uint8_t bank,
|
||||
bool has_row, uint32_t row,
|
||||
bool has_column, uint16_t column,
|
||||
bool has_correction_mask, uint64List *correction_mask,
|
||||
bool has_correction_mask,
|
||||
uint64List *correction_mask,
|
||||
Error **errp)
|
||||
{
|
||||
Object *obj = object_resolve_path(path, NULL);
|
||||
|
@ -1473,7 +1502,7 @@ void qmp_cxl_inject_memory_module_event(const char *path, CxlEventLog log,
|
|||
int16_t temperature,
|
||||
uint32_t dirty_shutdown_count,
|
||||
uint32_t corrected_volatile_error_count,
|
||||
uint32_t corrected_persistent_error_count,
|
||||
uint32_t corrected_persist_error_count,
|
||||
Error **errp)
|
||||
{
|
||||
Object *obj = object_resolve_path(path, NULL);
|
||||
|
@ -1513,8 +1542,10 @@ void qmp_cxl_inject_memory_module_event(const char *path, CxlEventLog log,
|
|||
module.life_used = life_used;
|
||||
stw_le_p(&module.temperature, temperature);
|
||||
stl_le_p(&module.dirty_shutdown_count, dirty_shutdown_count);
|
||||
stl_le_p(&module.corrected_volatile_error_count, corrected_volatile_error_count);
|
||||
stl_le_p(&module.corrected_persistent_error_count, corrected_persistent_error_count);
|
||||
stl_le_p(&module.corrected_volatile_error_count,
|
||||
corrected_volatile_error_count);
|
||||
stl_le_p(&module.corrected_persistent_error_count,
|
||||
corrected_persist_error_count);
|
||||
|
||||
if (cxl_event_insert(cxlds, enc_log, (CXLEventRecordRaw *)&module)) {
|
||||
cxl_event_irq_assert(ct3d);
|
||||
|
|
|
@ -33,7 +33,8 @@ void qmp_cxl_inject_dram_event(const char *path, CxlEventLog log, uint8_t flags,
|
|||
bool has_bank, uint8_t bank,
|
||||
bool has_row, uint32_t row,
|
||||
bool has_column, uint16_t column,
|
||||
bool has_correction_mask, uint64List *correction_mask,
|
||||
bool has_correction_mask,
|
||||
uint64List *correction_mask,
|
||||
Error **errp) {}
|
||||
|
||||
void qmp_cxl_inject_memory_module_event(const char *path, CxlEventLog log,
|
||||
|
@ -45,7 +46,7 @@ void qmp_cxl_inject_memory_module_event(const char *path, CxlEventLog log,
|
|||
int16_t temperature,
|
||||
uint32_t dirty_shutdown_count,
|
||||
uint32_t corrected_volatile_error_count,
|
||||
uint32_t corrected_persistent_error_count,
|
||||
uint32_t corrected_persist_error_count,
|
||||
Error **errp) {}
|
||||
|
||||
void qmp_cxl_inject_poison(const char *path, uint64_t start, uint64_t length,
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "hw/pci/msi.h"
|
||||
#include "hw/pci/pcie.h"
|
||||
#include "hw/pci/pcie_port.h"
|
||||
#include "hw/cxl/cxl.h"
|
||||
#include "qapi/error.h"
|
||||
|
||||
typedef struct CXLDownstreamPort {
|
||||
|
@ -23,9 +24,6 @@ typedef struct CXLDownstreamPort {
|
|||
CXLComponentState cxl_cstate;
|
||||
} CXLDownstreamPort;
|
||||
|
||||
#define TYPE_CXL_DSP "cxl-downstream"
|
||||
DECLARE_INSTANCE_CHECKER(CXLDownstreamPort, CXL_DSP, TYPE_CXL_DSP)
|
||||
|
||||
#define CXL_DOWNSTREAM_PORT_MSI_OFFSET 0x70
|
||||
#define CXL_DOWNSTREAM_PORT_MSI_NR_VECTOR 1
|
||||
#define CXL_DOWNSTREAM_PORT_EXP_OFFSET 0x90
|
||||
|
@ -98,7 +96,7 @@ static void build_dvsecs(CXLComponentState *cxl)
|
|||
{
|
||||
uint8_t *dvsec;
|
||||
|
||||
dvsec = (uint8_t *)&(CXLDVSECPortExtensions){ 0 };
|
||||
dvsec = (uint8_t *)&(CXLDVSECPortExt){ 0 };
|
||||
cxl_component_create_dvsec(cxl, CXL2_DOWNSTREAM_PORT,
|
||||
EXTENSIONS_PORT_DVSEC_LENGTH,
|
||||
EXTENSIONS_PORT_DVSEC,
|
||||
|
@ -212,6 +210,19 @@ static void cxl_dsp_exitfn(PCIDevice *d)
|
|||
pci_bridge_exitfn(d);
|
||||
}
|
||||
|
||||
static void cxl_dsp_instance_post_init(Object *obj)
|
||||
{
|
||||
PCIESlot *s = PCIE_SLOT(obj);
|
||||
|
||||
if (!s->speed) {
|
||||
s->speed = QEMU_PCI_EXP_LNK_2_5GT;
|
||||
}
|
||||
|
||||
if (!s->width) {
|
||||
s->width = QEMU_PCI_EXP_LNK_X1;
|
||||
}
|
||||
}
|
||||
|
||||
static void cxl_dsp_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(oc);
|
||||
|
@ -232,6 +243,7 @@ static const TypeInfo cxl_dsp_info = {
|
|||
.name = TYPE_CXL_DSP,
|
||||
.instance_size = sizeof(CXLDownstreamPort),
|
||||
.parent = TYPE_PCIE_SLOT,
|
||||
.instance_post_init = cxl_dsp_instance_post_init,
|
||||
.class_init = cxl_dsp_class_init,
|
||||
.interfaces = (InterfaceInfo[]) {
|
||||
{ INTERFACE_PCIE_DEVICE },
|
||||
|
|
|
@ -107,7 +107,7 @@ static void build_dvsecs(CXLComponentState *cxl)
|
|||
{
|
||||
uint8_t *dvsec;
|
||||
|
||||
dvsec = (uint8_t *)&(CXLDVSECPortExtensions){ 0 };
|
||||
dvsec = (uint8_t *)&(CXLDVSECPortExt){ 0 };
|
||||
cxl_component_create_dvsec(cxl, CXL2_ROOT_PORT,
|
||||
EXTENSIONS_PORT_DVSEC_LENGTH,
|
||||
EXTENSIONS_PORT_DVSEC,
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "hw/pci/msi.h"
|
||||
#include "hw/pci/pcie.h"
|
||||
#include "hw/pci/pcie_port.h"
|
||||
#include "hw/pci-bridge/cxl_upstream_port.h"
|
||||
/*
|
||||
* Null value of all Fs suggested by IEEE RA guidelines for use of
|
||||
* EU, OUI and CID
|
||||
|
@ -30,16 +31,6 @@
|
|||
#define CXL_UPSTREAM_PORT_DVSEC_OFFSET \
|
||||
(CXL_UPSTREAM_PORT_SN_OFFSET + PCI_EXT_CAP_DSN_SIZEOF)
|
||||
|
||||
typedef struct CXLUpstreamPort {
|
||||
/*< private >*/
|
||||
PCIEPort parent_obj;
|
||||
|
||||
/*< public >*/
|
||||
CXLComponentState cxl_cstate;
|
||||
DOECap doe_cdat;
|
||||
uint64_t sn;
|
||||
} CXLUpstreamPort;
|
||||
|
||||
CXLComponentState *cxl_usp_to_cstate(CXLUpstreamPort *usp)
|
||||
{
|
||||
return &usp->cxl_cstate;
|
||||
|
@ -116,7 +107,7 @@ static void build_dvsecs(CXLComponentState *cxl)
|
|||
{
|
||||
uint8_t *dvsec;
|
||||
|
||||
dvsec = (uint8_t *)&(CXLDVSECPortExtensions){
|
||||
dvsec = (uint8_t *)&(CXLDVSECPortExt){
|
||||
.status = 0x1, /* Port Power Management Init Complete */
|
||||
};
|
||||
cxl_component_create_dvsec(cxl, CXL2_UPSTREAM_PORT,
|
||||
|
|
|
@ -298,9 +298,108 @@ static struct vhost_dev *vuf_get_vhost(VirtIODevice *vdev)
|
|||
return &fs->vhost_dev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the internal state from virtiofsd and save it to `f`.
|
||||
*/
|
||||
static int vuf_save_state(QEMUFile *f, void *pv, size_t size,
|
||||
const VMStateField *field, JSONWriter *vmdesc)
|
||||
{
|
||||
VirtIODevice *vdev = pv;
|
||||
VHostUserFS *fs = VHOST_USER_FS(vdev);
|
||||
Error *local_error = NULL;
|
||||
int ret;
|
||||
|
||||
ret = vhost_save_backend_state(&fs->vhost_dev, f, &local_error);
|
||||
if (ret < 0) {
|
||||
error_reportf_err(local_error,
|
||||
"Error saving back-end state of %s device %s "
|
||||
"(tag: \"%s\"): ",
|
||||
vdev->name, vdev->parent_obj.canonical_path,
|
||||
fs->conf.tag ?: "<none>");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load virtiofsd's internal state from `f` and send it over to virtiofsd.
|
||||
*/
|
||||
static int vuf_load_state(QEMUFile *f, void *pv, size_t size,
|
||||
const VMStateField *field)
|
||||
{
|
||||
VirtIODevice *vdev = pv;
|
||||
VHostUserFS *fs = VHOST_USER_FS(vdev);
|
||||
Error *local_error = NULL;
|
||||
int ret;
|
||||
|
||||
ret = vhost_load_backend_state(&fs->vhost_dev, f, &local_error);
|
||||
if (ret < 0) {
|
||||
error_reportf_err(local_error,
|
||||
"Error loading back-end state of %s device %s "
|
||||
"(tag: \"%s\"): ",
|
||||
vdev->name, vdev->parent_obj.canonical_path,
|
||||
fs->conf.tag ?: "<none>");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool vuf_is_internal_migration(void *opaque)
|
||||
{
|
||||
/* TODO: Return false when an external migration is requested */
|
||||
return true;
|
||||
}
|
||||
|
||||
static int vuf_check_migration_support(void *opaque)
|
||||
{
|
||||
VirtIODevice *vdev = opaque;
|
||||
VHostUserFS *fs = VHOST_USER_FS(vdev);
|
||||
|
||||
if (!vhost_supports_device_state(&fs->vhost_dev)) {
|
||||
error_report("Back-end of %s device %s (tag: \"%s\") does not support "
|
||||
"migration through qemu",
|
||||
vdev->name, vdev->parent_obj.canonical_path,
|
||||
fs->conf.tag ?: "<none>");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vuf_backend_vmstate;
|
||||
|
||||
static const VMStateDescription vuf_vmstate = {
|
||||
.name = "vhost-user-fs",
|
||||
.unmigratable = 1,
|
||||
.version_id = 0,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_VIRTIO_DEVICE,
|
||||
VMSTATE_END_OF_LIST()
|
||||
},
|
||||
.subsections = (const VMStateDescription * []) {
|
||||
&vuf_backend_vmstate,
|
||||
NULL,
|
||||
}
|
||||
};
|
||||
|
||||
static const VMStateDescription vuf_backend_vmstate = {
|
||||
.name = "vhost-user-fs-backend",
|
||||
.version_id = 0,
|
||||
.needed = vuf_is_internal_migration,
|
||||
.pre_load = vuf_check_migration_support,
|
||||
.pre_save = vuf_check_migration_support,
|
||||
.fields = (VMStateField[]) {
|
||||
{
|
||||
.name = "back-end",
|
||||
.info = &(const VMStateInfo) {
|
||||
.name = "virtio-fs back-end state",
|
||||
.get = vuf_load_state,
|
||||
.put = vuf_save_state,
|
||||
},
|
||||
},
|
||||
VMSTATE_END_OF_LIST()
|
||||
},
|
||||
};
|
||||
|
||||
static Property vuf_properties[] = {
|
||||
|
|
|
@ -103,6 +103,8 @@ typedef enum VhostUserRequest {
|
|||
VHOST_USER_SET_STATUS = 39,
|
||||
VHOST_USER_GET_STATUS = 40,
|
||||
VHOST_USER_GET_SHARED_OBJECT = 41,
|
||||
VHOST_USER_SET_DEVICE_STATE_FD = 42,
|
||||
VHOST_USER_CHECK_DEVICE_STATE = 43,
|
||||
VHOST_USER_MAX
|
||||
} VhostUserRequest;
|
||||
|
||||
|
@ -201,6 +203,12 @@ typedef struct {
|
|||
uint32_t size; /* the following payload size */
|
||||
} QEMU_PACKED VhostUserHeader;
|
||||
|
||||
/* Request payload of VHOST_USER_SET_DEVICE_STATE_FD */
|
||||
typedef struct VhostUserTransferDeviceState {
|
||||
uint32_t direction;
|
||||
uint32_t phase;
|
||||
} VhostUserTransferDeviceState;
|
||||
|
||||
typedef union {
|
||||
#define VHOST_USER_VRING_IDX_MASK (0xff)
|
||||
#define VHOST_USER_VRING_NOFD_MASK (0x1 << 8)
|
||||
|
@ -216,6 +224,7 @@ typedef union {
|
|||
VhostUserVringArea area;
|
||||
VhostUserInflight inflight;
|
||||
VhostUserShared object;
|
||||
VhostUserTransferDeviceState transfer_state;
|
||||
} VhostUserPayload;
|
||||
|
||||
typedef struct VhostUserMsg {
|
||||
|
@ -2855,6 +2864,140 @@ static void vhost_user_reset_status(struct vhost_dev *dev)
|
|||
}
|
||||
}
|
||||
|
||||
static bool vhost_user_supports_device_state(struct vhost_dev *dev)
|
||||
{
|
||||
return virtio_has_feature(dev->protocol_features,
|
||||
VHOST_USER_PROTOCOL_F_DEVICE_STATE);
|
||||
}
|
||||
|
||||
static int vhost_user_set_device_state_fd(struct vhost_dev *dev,
|
||||
VhostDeviceStateDirection direction,
|
||||
VhostDeviceStatePhase phase,
|
||||
int fd,
|
||||
int *reply_fd,
|
||||
Error **errp)
|
||||
{
|
||||
int ret;
|
||||
struct vhost_user *vu = dev->opaque;
|
||||
VhostUserMsg msg = {
|
||||
.hdr = {
|
||||
.request = VHOST_USER_SET_DEVICE_STATE_FD,
|
||||
.flags = VHOST_USER_VERSION,
|
||||
.size = sizeof(msg.payload.transfer_state),
|
||||
},
|
||||
.payload.transfer_state = {
|
||||
.direction = direction,
|
||||
.phase = phase,
|
||||
},
|
||||
};
|
||||
|
||||
*reply_fd = -1;
|
||||
|
||||
if (!vhost_user_supports_device_state(dev)) {
|
||||
close(fd);
|
||||
error_setg(errp, "Back-end does not support migration state transfer");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
ret = vhost_user_write(dev, &msg, &fd, 1);
|
||||
close(fd);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret,
|
||||
"Failed to send SET_DEVICE_STATE_FD message");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = vhost_user_read(dev, &msg);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret,
|
||||
"Failed to receive SET_DEVICE_STATE_FD reply");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (msg.hdr.request != VHOST_USER_SET_DEVICE_STATE_FD) {
|
||||
error_setg(errp,
|
||||
"Received unexpected message type, expected %d, received %d",
|
||||
VHOST_USER_SET_DEVICE_STATE_FD, msg.hdr.request);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
if (msg.hdr.size != sizeof(msg.payload.u64)) {
|
||||
error_setg(errp,
|
||||
"Received bad message size, expected %zu, received %" PRIu32,
|
||||
sizeof(msg.payload.u64), msg.hdr.size);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
if ((msg.payload.u64 & 0xff) != 0) {
|
||||
error_setg(errp, "Back-end did not accept migration state transfer");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (!(msg.payload.u64 & VHOST_USER_VRING_NOFD_MASK)) {
|
||||
*reply_fd = qemu_chr_fe_get_msgfd(vu->user->chr);
|
||||
if (*reply_fd < 0) {
|
||||
error_setg(errp,
|
||||
"Failed to get back-end-provided transfer pipe FD");
|
||||
*reply_fd = -1;
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vhost_user_check_device_state(struct vhost_dev *dev, Error **errp)
|
||||
{
|
||||
int ret;
|
||||
VhostUserMsg msg = {
|
||||
.hdr = {
|
||||
.request = VHOST_USER_CHECK_DEVICE_STATE,
|
||||
.flags = VHOST_USER_VERSION,
|
||||
.size = 0,
|
||||
},
|
||||
};
|
||||
|
||||
if (!vhost_user_supports_device_state(dev)) {
|
||||
error_setg(errp, "Back-end does not support migration state transfer");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
ret = vhost_user_write(dev, &msg, NULL, 0);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret,
|
||||
"Failed to send CHECK_DEVICE_STATE message");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = vhost_user_read(dev, &msg);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret,
|
||||
"Failed to receive CHECK_DEVICE_STATE reply");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (msg.hdr.request != VHOST_USER_CHECK_DEVICE_STATE) {
|
||||
error_setg(errp,
|
||||
"Received unexpected message type, expected %d, received %d",
|
||||
VHOST_USER_CHECK_DEVICE_STATE, msg.hdr.request);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
if (msg.hdr.size != sizeof(msg.payload.u64)) {
|
||||
error_setg(errp,
|
||||
"Received bad message size, expected %zu, received %" PRIu32,
|
||||
sizeof(msg.payload.u64), msg.hdr.size);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
if (msg.payload.u64 != 0) {
|
||||
error_setg(errp, "Back-end failed to process its internal state");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const VhostOps user_ops = {
|
||||
.backend_type = VHOST_BACKEND_TYPE_USER,
|
||||
.vhost_backend_init = vhost_user_backend_init,
|
||||
|
@ -2890,4 +3033,7 @@ const VhostOps user_ops = {
|
|||
.vhost_set_inflight_fd = vhost_user_set_inflight_fd,
|
||||
.vhost_dev_start = vhost_user_dev_start,
|
||||
.vhost_reset_status = vhost_user_reset_status,
|
||||
.vhost_supports_device_state = vhost_user_supports_device_state,
|
||||
.vhost_set_device_state_fd = vhost_user_set_device_state_fd,
|
||||
.vhost_check_device_state = vhost_user_check_device_state,
|
||||
};
|
||||
|
|
|
@ -2159,3 +2159,244 @@ int vhost_reset_device(struct vhost_dev *hdev)
|
|||
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
bool vhost_supports_device_state(struct vhost_dev *dev)
|
||||
{
|
||||
if (dev->vhost_ops->vhost_supports_device_state) {
|
||||
return dev->vhost_ops->vhost_supports_device_state(dev);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int vhost_set_device_state_fd(struct vhost_dev *dev,
|
||||
VhostDeviceStateDirection direction,
|
||||
VhostDeviceStatePhase phase,
|
||||
int fd,
|
||||
int *reply_fd,
|
||||
Error **errp)
|
||||
{
|
||||
if (dev->vhost_ops->vhost_set_device_state_fd) {
|
||||
return dev->vhost_ops->vhost_set_device_state_fd(dev, direction, phase,
|
||||
fd, reply_fd, errp);
|
||||
}
|
||||
|
||||
error_setg(errp,
|
||||
"vhost transport does not support migration state transfer");
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
int vhost_check_device_state(struct vhost_dev *dev, Error **errp)
|
||||
{
|
||||
if (dev->vhost_ops->vhost_check_device_state) {
|
||||
return dev->vhost_ops->vhost_check_device_state(dev, errp);
|
||||
}
|
||||
|
||||
error_setg(errp,
|
||||
"vhost transport does not support migration state transfer");
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
int vhost_save_backend_state(struct vhost_dev *dev, QEMUFile *f, Error **errp)
|
||||
{
|
||||
/* Maximum chunk size in which to transfer the state */
|
||||
const size_t chunk_size = 1 * 1024 * 1024;
|
||||
g_autofree void *transfer_buf = NULL;
|
||||
g_autoptr(GError) g_err = NULL;
|
||||
int pipe_fds[2], read_fd = -1, write_fd = -1, reply_fd = -1;
|
||||
int ret;
|
||||
|
||||
/* [0] for reading (our end), [1] for writing (back-end's end) */
|
||||
if (!g_unix_open_pipe(pipe_fds, FD_CLOEXEC, &g_err)) {
|
||||
error_setg(errp, "Failed to set up state transfer pipe: %s",
|
||||
g_err->message);
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
read_fd = pipe_fds[0];
|
||||
write_fd = pipe_fds[1];
|
||||
|
||||
/*
|
||||
* VHOST_TRANSFER_STATE_PHASE_STOPPED means the device must be stopped.
|
||||
* Ideally, it is suspended, but SUSPEND/RESUME currently do not exist for
|
||||
* vhost-user, so just check that it is stopped at all.
|
||||
*/
|
||||
assert(!dev->started);
|
||||
|
||||
/* Transfer ownership of write_fd to the back-end */
|
||||
ret = vhost_set_device_state_fd(dev,
|
||||
VHOST_TRANSFER_STATE_DIRECTION_SAVE,
|
||||
VHOST_TRANSFER_STATE_PHASE_STOPPED,
|
||||
write_fd,
|
||||
&reply_fd,
|
||||
errp);
|
||||
if (ret < 0) {
|
||||
error_prepend(errp, "Failed to initiate state transfer: ");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* If the back-end wishes to use a different pipe, switch over */
|
||||
if (reply_fd >= 0) {
|
||||
close(read_fd);
|
||||
read_fd = reply_fd;
|
||||
}
|
||||
|
||||
transfer_buf = g_malloc(chunk_size);
|
||||
|
||||
while (true) {
|
||||
ssize_t read_ret;
|
||||
|
||||
read_ret = RETRY_ON_EINTR(read(read_fd, transfer_buf, chunk_size));
|
||||
if (read_ret < 0) {
|
||||
ret = -errno;
|
||||
error_setg_errno(errp, -ret, "Failed to receive state");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
assert(read_ret <= chunk_size);
|
||||
qemu_put_be32(f, read_ret);
|
||||
|
||||
if (read_ret == 0) {
|
||||
/* EOF */
|
||||
break;
|
||||
}
|
||||
|
||||
qemu_put_buffer(f, transfer_buf, read_ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* Back-end will not really care, but be clean and close our end of the pipe
|
||||
* before inquiring the back-end about whether transfer was successful
|
||||
*/
|
||||
close(read_fd);
|
||||
read_fd = -1;
|
||||
|
||||
/* Also, verify that the device is still stopped */
|
||||
assert(!dev->started);
|
||||
|
||||
ret = vhost_check_device_state(dev, errp);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
fail:
|
||||
if (read_fd >= 0) {
|
||||
close(read_fd);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int vhost_load_backend_state(struct vhost_dev *dev, QEMUFile *f, Error **errp)
|
||||
{
|
||||
size_t transfer_buf_size = 0;
|
||||
g_autofree void *transfer_buf = NULL;
|
||||
g_autoptr(GError) g_err = NULL;
|
||||
int pipe_fds[2], read_fd = -1, write_fd = -1, reply_fd = -1;
|
||||
int ret;
|
||||
|
||||
/* [0] for reading (back-end's end), [1] for writing (our end) */
|
||||
if (!g_unix_open_pipe(pipe_fds, FD_CLOEXEC, &g_err)) {
|
||||
error_setg(errp, "Failed to set up state transfer pipe: %s",
|
||||
g_err->message);
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
read_fd = pipe_fds[0];
|
||||
write_fd = pipe_fds[1];
|
||||
|
||||
/*
|
||||
* VHOST_TRANSFER_STATE_PHASE_STOPPED means the device must be stopped.
|
||||
* Ideally, it is suspended, but SUSPEND/RESUME currently do not exist for
|
||||
* vhost-user, so just check that it is stopped at all.
|
||||
*/
|
||||
assert(!dev->started);
|
||||
|
||||
/* Transfer ownership of read_fd to the back-end */
|
||||
ret = vhost_set_device_state_fd(dev,
|
||||
VHOST_TRANSFER_STATE_DIRECTION_LOAD,
|
||||
VHOST_TRANSFER_STATE_PHASE_STOPPED,
|
||||
read_fd,
|
||||
&reply_fd,
|
||||
errp);
|
||||
if (ret < 0) {
|
||||
error_prepend(errp, "Failed to initiate state transfer: ");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* If the back-end wishes to use a different pipe, switch over */
|
||||
if (reply_fd >= 0) {
|
||||
close(write_fd);
|
||||
write_fd = reply_fd;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
size_t this_chunk_size = qemu_get_be32(f);
|
||||
ssize_t write_ret;
|
||||
const uint8_t *transfer_pointer;
|
||||
|
||||
if (this_chunk_size == 0) {
|
||||
/* End of state */
|
||||
break;
|
||||
}
|
||||
|
||||
if (transfer_buf_size < this_chunk_size) {
|
||||
transfer_buf = g_realloc(transfer_buf, this_chunk_size);
|
||||
transfer_buf_size = this_chunk_size;
|
||||
}
|
||||
|
||||
if (qemu_get_buffer(f, transfer_buf, this_chunk_size) <
|
||||
this_chunk_size)
|
||||
{
|
||||
error_setg(errp, "Failed to read state");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
transfer_pointer = transfer_buf;
|
||||
while (this_chunk_size > 0) {
|
||||
write_ret = RETRY_ON_EINTR(
|
||||
write(write_fd, transfer_pointer, this_chunk_size)
|
||||
);
|
||||
if (write_ret < 0) {
|
||||
ret = -errno;
|
||||
error_setg_errno(errp, -ret, "Failed to send state");
|
||||
goto fail;
|
||||
} else if (write_ret == 0) {
|
||||
error_setg(errp, "Failed to send state: Connection is closed");
|
||||
ret = -ECONNRESET;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
assert(write_ret <= this_chunk_size);
|
||||
this_chunk_size -= write_ret;
|
||||
transfer_pointer += write_ret;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Close our end, thus ending transfer, before inquiring the back-end about
|
||||
* whether transfer was successful
|
||||
*/
|
||||
close(write_fd);
|
||||
write_fd = -1;
|
||||
|
||||
/* Also, verify that the device is still stopped */
|
||||
assert(!dev->started);
|
||||
|
||||
ret = vhost_check_device_state(dev, errp);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
fail:
|
||||
if (write_fd >= 0) {
|
||||
close(write_fd);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
235
include/hw/audio/virtio-snd.h
Normal file
235
include/hw/audio/virtio-snd.h
Normal file
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* VIRTIO Sound Device conforming to
|
||||
*
|
||||
* "Virtual I/O Device (VIRTIO) Version 1.2
|
||||
* Committee Specification Draft 01
|
||||
* 09 May 2022"
|
||||
*
|
||||
* Copyright (c) 2023 Emmanouil Pitsidianakis <manos.pitsidianakis@linaro.org>
|
||||
* Copyright (C) 2019 OpenSynergy GmbH
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or
|
||||
* (at your option) any later version. See the COPYING file in the
|
||||
* top-level directory.
|
||||
*/
|
||||
|
||||
#ifndef QEMU_VIRTIO_SOUND_H
|
||||
#define QEMU_VIRTIO_SOUND_H
|
||||
|
||||
#include "hw/virtio/virtio.h"
|
||||
#include "audio/audio.h"
|
||||
#include "standard-headers/linux/virtio_ids.h"
|
||||
#include "standard-headers/linux/virtio_snd.h"
|
||||
|
||||
#define TYPE_VIRTIO_SND "virtio-sound-device"
|
||||
#define VIRTIO_SND(obj) \
|
||||
OBJECT_CHECK(VirtIOSound, (obj), TYPE_VIRTIO_SND)
|
||||
|
||||
/* CONFIGURATION SPACE */
|
||||
|
||||
typedef struct virtio_snd_config virtio_snd_config;
|
||||
|
||||
/* COMMON DEFINITIONS */
|
||||
|
||||
/* common header for request/response*/
|
||||
typedef struct virtio_snd_hdr virtio_snd_hdr;
|
||||
|
||||
/* event notification */
|
||||
typedef struct virtio_snd_event virtio_snd_event;
|
||||
|
||||
/* common control request to query an item information */
|
||||
typedef struct virtio_snd_query_info virtio_snd_query_info;
|
||||
|
||||
/* JACK CONTROL MESSAGES */
|
||||
|
||||
typedef struct virtio_snd_jack_hdr virtio_snd_jack_hdr;
|
||||
|
||||
/* jack information structure */
|
||||
typedef struct virtio_snd_jack_info virtio_snd_jack_info;
|
||||
|
||||
/* jack remapping control request */
|
||||
typedef struct virtio_snd_jack_remap virtio_snd_jack_remap;
|
||||
|
||||
/*
|
||||
* PCM CONTROL MESSAGES
|
||||
*/
|
||||
typedef struct virtio_snd_pcm_hdr virtio_snd_pcm_hdr;
|
||||
|
||||
/* PCM stream info structure */
|
||||
typedef struct virtio_snd_pcm_info virtio_snd_pcm_info;
|
||||
|
||||
/* set PCM stream params */
|
||||
typedef struct virtio_snd_pcm_set_params virtio_snd_pcm_set_params;
|
||||
|
||||
/* I/O request header */
|
||||
typedef struct virtio_snd_pcm_xfer virtio_snd_pcm_xfer;
|
||||
|
||||
/* I/O request status */
|
||||
typedef struct virtio_snd_pcm_status virtio_snd_pcm_status;
|
||||
|
||||
/* device structs */
|
||||
|
||||
typedef struct VirtIOSound VirtIOSound;
|
||||
|
||||
typedef struct VirtIOSoundPCMStream VirtIOSoundPCMStream;
|
||||
|
||||
typedef struct virtio_snd_ctrl_command virtio_snd_ctrl_command;
|
||||
|
||||
typedef struct VirtIOSoundPCM VirtIOSoundPCM;
|
||||
|
||||
typedef struct VirtIOSoundPCMBuffer VirtIOSoundPCMBuffer;
|
||||
|
||||
/*
|
||||
* The VirtIO sound spec reuses layouts and values from the High Definition
|
||||
* Audio spec (virtio/v1.2: 5.14 Sound Device). This struct handles each I/O
|
||||
* message's buffer (virtio/v1.2: 5.14.6.8 PCM I/O Messages).
|
||||
*
|
||||
* In the case of TX (i.e. playback) buffers, we defer reading the raw PCM data
|
||||
* from the virtqueue until QEMU's sound backsystem calls the output callback.
|
||||
* This is tracked by the `bool populated;` field, which is set to true when
|
||||
* data has been read into our own buffer for consumption.
|
||||
*
|
||||
* VirtIOSoundPCMBuffer has a dynamic size since it includes the raw PCM data
|
||||
* in its allocation. It must be initialized and destroyed as follows:
|
||||
*
|
||||
* size_t size = [[derived from owned VQ element descriptor sizes]];
|
||||
* buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size);
|
||||
* buffer->elem = [[owned VQ element]];
|
||||
*
|
||||
* [..]
|
||||
*
|
||||
* g_free(buffer->elem);
|
||||
* g_free(buffer);
|
||||
*/
|
||||
struct VirtIOSoundPCMBuffer {
|
||||
QSIMPLEQ_ENTRY(VirtIOSoundPCMBuffer) entry;
|
||||
VirtQueueElement *elem;
|
||||
VirtQueue *vq;
|
||||
size_t size;
|
||||
/*
|
||||
* In TX / Plaback, `offset` represents the first unused position inside
|
||||
* `data`. If `offset == size` then there are no unused data left.
|
||||
*/
|
||||
uint64_t offset;
|
||||
/* Used for the TX queue for lazy I/O copy from `elem` */
|
||||
bool populated;
|
||||
/*
|
||||
* VirtIOSoundPCMBuffer is an unsized type because it ends with an array of
|
||||
* bytes. The size of `data` is determined from the I/O message's read-only
|
||||
* or write-only size when allocating VirtIOSoundPCMBuffer.
|
||||
*/
|
||||
uint8_t data[];
|
||||
};
|
||||
|
||||
struct VirtIOSoundPCM {
|
||||
VirtIOSound *snd;
|
||||
/*
|
||||
* PCM parameters are a separate field instead of a VirtIOSoundPCMStream
|
||||
* field, because the operation of PCM control requests is first
|
||||
* VIRTIO_SND_R_PCM_SET_PARAMS and then VIRTIO_SND_R_PCM_PREPARE; this
|
||||
* means that some times we get parameters without having an allocated
|
||||
* stream yet.
|
||||
*/
|
||||
virtio_snd_pcm_set_params *pcm_params;
|
||||
VirtIOSoundPCMStream **streams;
|
||||
};
|
||||
|
||||
struct VirtIOSoundPCMStream {
|
||||
VirtIOSoundPCM *pcm;
|
||||
virtio_snd_pcm_info info;
|
||||
virtio_snd_pcm_set_params params;
|
||||
uint32_t id;
|
||||
/* channel position values (VIRTIO_SND_CHMAP_XXX) */
|
||||
uint8_t positions[VIRTIO_SND_CHMAP_MAX_SIZE];
|
||||
VirtIOSound *s;
|
||||
bool flushing;
|
||||
audsettings as;
|
||||
union {
|
||||
SWVoiceIn *in;
|
||||
SWVoiceOut *out;
|
||||
} voice;
|
||||
QemuMutex queue_mutex;
|
||||
bool active;
|
||||
QSIMPLEQ_HEAD(, VirtIOSoundPCMBuffer) queue;
|
||||
QSIMPLEQ_HEAD(, VirtIOSoundPCMBuffer) invalid;
|
||||
};
|
||||
|
||||
/*
|
||||
* PCM stream state machine.
|
||||
* -------------------------
|
||||
*
|
||||
* 5.14.6.6.1 PCM Command Lifecycle
|
||||
* ================================
|
||||
*
|
||||
* A PCM stream has the following command lifecycle:
|
||||
* - `SET PARAMETERS`
|
||||
* The driver negotiates the stream parameters (format, transport, etc) with
|
||||
* the device.
|
||||
* Possible valid transitions: `SET PARAMETERS`, `PREPARE`.
|
||||
* - `PREPARE`
|
||||
* The device prepares the stream (allocates resources, etc).
|
||||
* Possible valid transitions: `SET PARAMETERS`, `PREPARE`, `START`,
|
||||
* `RELEASE`. Output only: the driver transfers data for pre-buffing.
|
||||
* - `START`
|
||||
* The device starts the stream (unmute, putting into running state, etc).
|
||||
* Possible valid transitions: `STOP`.
|
||||
* The driver transfers data to/from the stream.
|
||||
* - `STOP`
|
||||
* The device stops the stream (mute, putting into non-running state, etc).
|
||||
* Possible valid transitions: `START`, `RELEASE`.
|
||||
* - `RELEASE`
|
||||
* The device releases the stream (frees resources, etc).
|
||||
* Possible valid transitions: `SET PARAMETERS`, `PREPARE`.
|
||||
*
|
||||
* +---------------+ +---------+ +---------+ +-------+ +-------+
|
||||
* | SetParameters | | Prepare | | Release | | Start | | Stop |
|
||||
* +---------------+ +---------+ +---------+ +-------+ +-------+
|
||||
* |- | | | |
|
||||
* || | | | |
|
||||
* |< | | | |
|
||||
* |------------->| | | |
|
||||
* |<-------------| | | |
|
||||
* | |- | | |
|
||||
* | || | | |
|
||||
* | |< | | |
|
||||
* | |--------------------->| |
|
||||
* | |---------->| | |
|
||||
* | | | |-------->|
|
||||
* | | | |<--------|
|
||||
* | | |<-------------------|
|
||||
* |<-------------------------| | |
|
||||
* | |<----------| | |
|
||||
*
|
||||
* CTRL in the VirtIOSound device
|
||||
* ==============================
|
||||
*
|
||||
* The control messages that affect the state of a stream arrive in the
|
||||
* `virtio_snd_handle_ctrl()` queue callback and are of type `struct
|
||||
* virtio_snd_ctrl_command`. They are stored in a queue field in the device
|
||||
* type, `VirtIOSound`. This allows deferring the CTRL request completion if
|
||||
* it's not immediately possible due to locking/state reasons.
|
||||
*
|
||||
* The CTRL message is finally handled in `process_cmd()`.
|
||||
*/
|
||||
struct VirtIOSound {
|
||||
VirtIODevice parent_obj;
|
||||
|
||||
VirtQueue *queues[VIRTIO_SND_VQ_MAX];
|
||||
uint64_t features;
|
||||
VirtIOSoundPCM *pcm;
|
||||
QEMUSoundCard card;
|
||||
VMChangeStateEntry *vmstate;
|
||||
virtio_snd_config snd_conf;
|
||||
QemuMutex cmdq_mutex;
|
||||
QTAILQ_HEAD(, virtio_snd_ctrl_command) cmdq;
|
||||
bool processing_cmdq;
|
||||
};
|
||||
|
||||
struct virtio_snd_ctrl_command {
|
||||
VirtQueueElement *elem;
|
||||
VirtQueue *vq;
|
||||
virtio_snd_hdr ctrl;
|
||||
virtio_snd_hdr resp;
|
||||
QTAILQ_ENTRY(virtio_snd_ctrl_command) next;
|
||||
};
|
||||
#endif
|
|
@ -61,4 +61,10 @@ OBJECT_DECLARE_SIMPLE_TYPE(CXLHost, PXB_CXL_HOST)
|
|||
typedef struct CXLUpstreamPort CXLUpstreamPort;
|
||||
DECLARE_INSTANCE_CHECKER(CXLUpstreamPort, CXL_USP, TYPE_CXL_USP)
|
||||
CXLComponentState *cxl_usp_to_cstate(CXLUpstreamPort *usp);
|
||||
|
||||
#define TYPE_CXL_DSP "cxl-downstream"
|
||||
|
||||
typedef struct CXLDownstreamPort CXLDownstreamPort;
|
||||
DECLARE_INSTANCE_CHECKER(CXLDownstreamPort, CXL_DSP, TYPE_CXL_DSP)
|
||||
|
||||
#endif
|
||||
|
|
|
@ -26,7 +26,8 @@ enum reg_type {
|
|||
CXL2_LOGICAL_DEVICE,
|
||||
CXL2_ROOT_PORT,
|
||||
CXL2_UPSTREAM_PORT,
|
||||
CXL2_DOWNSTREAM_PORT
|
||||
CXL2_DOWNSTREAM_PORT,
|
||||
CXL3_SWITCH_MAILBOX_CCI,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -175,7 +176,8 @@ HDM_DECODER_INIT(3);
|
|||
(CXL_IDE_REGISTERS_OFFSET + CXL_IDE_REGISTERS_SIZE)
|
||||
#define CXL_SNOOP_REGISTERS_SIZE 0x8
|
||||
|
||||
QEMU_BUILD_BUG_MSG((CXL_SNOOP_REGISTERS_OFFSET + CXL_SNOOP_REGISTERS_SIZE) >= 0x1000,
|
||||
QEMU_BUILD_BUG_MSG((CXL_SNOOP_REGISTERS_OFFSET +
|
||||
CXL_SNOOP_REGISTERS_SIZE) >= 0x1000,
|
||||
"No space for registers");
|
||||
|
||||
typedef struct component_registers {
|
||||
|
|
|
@ -111,6 +111,20 @@ typedef enum {
|
|||
CXL_MBOX_MAX = 0x17
|
||||
} CXLRetCode;
|
||||
|
||||
typedef struct CXLCCI CXLCCI;
|
||||
typedef struct cxl_device_state CXLDeviceState;
|
||||
struct cxl_cmd;
|
||||
typedef CXLRetCode (*opcode_handler)(const struct cxl_cmd *cmd,
|
||||
uint8_t *payload_in, size_t len_in,
|
||||
uint8_t *payload_out, size_t *len_out,
|
||||
CXLCCI *cci);
|
||||
struct cxl_cmd {
|
||||
const char *name;
|
||||
opcode_handler handler;
|
||||
ssize_t in;
|
||||
uint16_t effect; /* Reported in CEL */
|
||||
};
|
||||
|
||||
typedef struct CXLEvent {
|
||||
CXLEventRecordRaw data;
|
||||
QSIMPLEQ_ENTRY(CXLEvent) node;
|
||||
|
@ -127,6 +141,31 @@ typedef struct CXLEventLog {
|
|||
QSIMPLEQ_HEAD(, CXLEvent) events;
|
||||
} CXLEventLog;
|
||||
|
||||
typedef struct CXLCCI {
|
||||
const struct cxl_cmd (*cxl_cmd_set)[256];
|
||||
struct cel_log {
|
||||
uint16_t opcode;
|
||||
uint16_t effect;
|
||||
} cel_log[1 << 16];
|
||||
size_t cel_size;
|
||||
|
||||
/* background command handling (times in ms) */
|
||||
struct {
|
||||
uint16_t opcode;
|
||||
uint16_t complete_pct;
|
||||
uint16_t ret_code; /* Current value of retcode */
|
||||
uint64_t starttime;
|
||||
/* set by each bg cmd, cleared by the bg_timer when complete */
|
||||
uint64_t runtime;
|
||||
QEMUTimer *timer;
|
||||
} bg;
|
||||
size_t payload_max;
|
||||
/* Pointer to device hosting the CCI */
|
||||
DeviceState *d;
|
||||
/* Pointer to the device hosting the protocol conversion */
|
||||
DeviceState *intf;
|
||||
} CXLCCI;
|
||||
|
||||
typedef struct cxl_device_state {
|
||||
MemoryRegion device_registers;
|
||||
|
||||
|
@ -154,17 +193,13 @@ typedef struct cxl_device_state {
|
|||
struct {
|
||||
MemoryRegion mailbox;
|
||||
uint16_t payload_size;
|
||||
uint8_t mbox_msi_n;
|
||||
union {
|
||||
uint8_t mbox_reg_state[CXL_MAILBOX_REGISTERS_LENGTH];
|
||||
uint16_t mbox_reg_state16[CXL_MAILBOX_REGISTERS_LENGTH / 2];
|
||||
uint32_t mbox_reg_state32[CXL_MAILBOX_REGISTERS_LENGTH / 4];
|
||||
uint64_t mbox_reg_state64[CXL_MAILBOX_REGISTERS_LENGTH / 8];
|
||||
};
|
||||
struct cel_log {
|
||||
uint16_t opcode;
|
||||
uint16_t effect;
|
||||
} cel_log[1 << 16];
|
||||
size_t cel_size;
|
||||
};
|
||||
|
||||
struct {
|
||||
|
@ -178,21 +213,26 @@ typedef struct cxl_device_state {
|
|||
uint64_t pmem_size;
|
||||
uint64_t vmem_size;
|
||||
|
||||
const struct cxl_cmd (*cxl_cmd_set)[256];
|
||||
CXLEventLog event_logs[CXL_EVENT_TYPE_MAX];
|
||||
} CXLDeviceState;
|
||||
|
||||
/* Initialize the register block for a device */
|
||||
void cxl_device_register_block_init(Object *obj, CXLDeviceState *dev);
|
||||
void cxl_device_register_block_init(Object *obj, CXLDeviceState *dev,
|
||||
CXLCCI *cci);
|
||||
|
||||
typedef struct CXLType3Dev CXLType3Dev;
|
||||
typedef struct CSWMBCCIDev CSWMBCCIDev;
|
||||
/* Set up default values for the register block */
|
||||
void cxl_device_register_init_common(CXLDeviceState *dev);
|
||||
void cxl_device_register_init_t3(CXLType3Dev *ct3d);
|
||||
void cxl_device_register_init_swcci(CSWMBCCIDev *sw);
|
||||
|
||||
/*
|
||||
* CXL 2.0 - 8.2.8.1 including errata F4
|
||||
* Documented as a 128 bit register, but 64 bit accesses and the second
|
||||
* 64 bits are currently reserved.
|
||||
*/
|
||||
REG64(CXL_DEV_CAP_ARRAY, 0) /* Documented as 128 bit register but 64 byte accesses */
|
||||
REG64(CXL_DEV_CAP_ARRAY, 0)
|
||||
FIELD(CXL_DEV_CAP_ARRAY, CAP_ID, 0, 16)
|
||||
FIELD(CXL_DEV_CAP_ARRAY, CAP_VERSION, 16, 8)
|
||||
FIELD(CXL_DEV_CAP_ARRAY, CAP_COUNT, 32, 16)
|
||||
|
@ -231,8 +271,20 @@ CXL_DEVICE_CAPABILITY_HEADER_REGISTER(MEMORY_DEVICE,
|
|||
CXL_DEVICE_CAP_HDR1_OFFSET +
|
||||
CXL_DEVICE_CAP_REG_SIZE * 2)
|
||||
|
||||
void cxl_initialize_mailbox(CXLDeviceState *cxl_dstate);
|
||||
void cxl_process_mailbox(CXLDeviceState *cxl_dstate);
|
||||
void cxl_initialize_mailbox_t3(CXLCCI *cci, DeviceState *d, size_t payload_max);
|
||||
void cxl_initialize_mailbox_swcci(CXLCCI *cci, DeviceState *intf,
|
||||
DeviceState *d, size_t payload_max);
|
||||
void cxl_init_cci(CXLCCI *cci, size_t payload_max);
|
||||
int cxl_process_cci_message(CXLCCI *cci, uint8_t set, uint8_t cmd,
|
||||
size_t len_in, uint8_t *pl_in,
|
||||
size_t *len_out, uint8_t *pl_out,
|
||||
bool *bg_started);
|
||||
void cxl_initialize_t3_fm_owned_ld_mctpcci(CXLCCI *cci, DeviceState *d,
|
||||
DeviceState *intf,
|
||||
size_t payload_max);
|
||||
|
||||
void cxl_initialize_t3_ld_cci(CXLCCI *cci, DeviceState *d,
|
||||
DeviceState *intf, size_t payload_max);
|
||||
|
||||
#define cxl_device_cap_init(dstate, reg, cap_id, ver) \
|
||||
do { \
|
||||
|
@ -297,6 +349,23 @@ REG64(CXL_MEM_DEV_STS, 0)
|
|||
FIELD(CXL_MEM_DEV_STS, MBOX_READY, 4, 1)
|
||||
FIELD(CXL_MEM_DEV_STS, RESET_NEEDED, 5, 3)
|
||||
|
||||
static inline void __toggle_media(CXLDeviceState *cxl_dstate, int val)
|
||||
{
|
||||
uint64_t dev_status_reg;
|
||||
|
||||
dev_status_reg = FIELD_DP64(0, CXL_MEM_DEV_STS, MEDIA_STATUS, val);
|
||||
cxl_dstate->mbox_reg_state64[R_CXL_MEM_DEV_STS] = dev_status_reg;
|
||||
}
|
||||
#define cxl_dev_disable_media(cxlds) \
|
||||
do { __toggle_media((cxlds), 0x3); } while (0)
|
||||
#define cxl_dev_enable_media(cxlds) \
|
||||
do { __toggle_media((cxlds), 0x1); } while (0)
|
||||
|
||||
static inline bool sanitize_running(CXLCCI *cci)
|
||||
{
|
||||
return !!cci->bg.runtime && cci->bg.opcode == 0x4400;
|
||||
}
|
||||
|
||||
typedef struct CXLError {
|
||||
QTAILQ_ENTRY(CXLError) node;
|
||||
int type; /* Error code as per FE definition */
|
||||
|
@ -333,6 +402,10 @@ struct CXLType3Dev {
|
|||
AddressSpace hostpmem_as;
|
||||
CXLComponentState cxl_cstate;
|
||||
CXLDeviceState cxl_dstate;
|
||||
CXLCCI cci; /* Primary PCI mailbox CCI */
|
||||
/* Always intialized as no way to know if a VDM might show up */
|
||||
CXLCCI vdm_fm_owned_ld_mctp_cci;
|
||||
CXLCCI ld0_cci;
|
||||
|
||||
/* DOE */
|
||||
DOECap doe_cdat;
|
||||
|
@ -361,9 +434,21 @@ struct CXLType3Class {
|
|||
uint64_t offset);
|
||||
void (*set_lsa)(CXLType3Dev *ct3d, const void *buf, uint64_t size,
|
||||
uint64_t offset);
|
||||
bool (*set_cacheline)(CXLType3Dev *ct3d, uint64_t dpa_offset, uint8_t *data);
|
||||
bool (*set_cacheline)(CXLType3Dev *ct3d, uint64_t dpa_offset,
|
||||
uint8_t *data);
|
||||
};
|
||||
|
||||
struct CSWMBCCIDev {
|
||||
PCIDevice parent_obj;
|
||||
PCIDevice *target;
|
||||
CXLComponentState cxl_cstate;
|
||||
CXLDeviceState cxl_dstate;
|
||||
CXLCCI *cci;
|
||||
};
|
||||
|
||||
#define TYPE_CXL_SWITCH_MAILBOX_CCI "cxl-switch-mailbox-cci"
|
||||
OBJECT_DECLARE_TYPE(CSWMBCCIDev, CSWMBCCIClass, CXL_SWITCH_MAILBOX_CCI)
|
||||
|
||||
MemTxResult cxl_type3_read(PCIDevice *d, hwaddr host_addr, uint64_t *data,
|
||||
unsigned size, MemTxAttrs attrs);
|
||||
MemTxResult cxl_type3_write(PCIDevice *d, hwaddr host_addr, uint64_t data,
|
||||
|
@ -376,7 +461,7 @@ bool cxl_event_insert(CXLDeviceState *cxlds, CXLEventLogType log_type,
|
|||
CXLEventRecordRaw *event);
|
||||
CXLRetCode cxl_event_get_records(CXLDeviceState *cxlds, CXLGetEventPayload *pl,
|
||||
uint8_t log_type, int max_recs,
|
||||
uint16_t *len);
|
||||
size_t *len);
|
||||
CXLRetCode cxl_event_clear_records(CXLDeviceState *cxlds,
|
||||
CXLClearEventPayload *pl);
|
||||
|
||||
|
|
|
@ -92,7 +92,8 @@ typedef enum CXLEventIntMode {
|
|||
CXL_INT_RES = 0x03,
|
||||
} CXLEventIntMode;
|
||||
#define CXL_EVENT_INT_MODE_MASK 0x3
|
||||
#define CXL_EVENT_INT_SETTING(vector) ((((uint8_t)vector & 0xf) << 4) | CXL_INT_MSI_MSIX)
|
||||
#define CXL_EVENT_INT_SETTING(vector) \
|
||||
((((uint8_t)vector & 0xf) << 4) | CXL_INT_MSI_MSIX)
|
||||
typedef struct CXLEventInterruptPolicy {
|
||||
uint8_t info_settings;
|
||||
uint8_t warn_settings;
|
||||
|
|
|
@ -86,7 +86,7 @@ typedef struct CXLDVSECDevice {
|
|||
QEMU_BUILD_BUG_ON(sizeof(CXLDVSECDevice) != 0x38);
|
||||
|
||||
/* CXL 2.0 - 8.1.5 (ID 0003) */
|
||||
typedef struct CXLDVSECPortExtensions {
|
||||
typedef struct CXLDVSECPortExt {
|
||||
DVSECHeader hdr;
|
||||
uint16_t status;
|
||||
uint16_t control;
|
||||
|
@ -100,8 +100,8 @@ typedef struct CXLDVSECPortExtensions {
|
|||
uint32_t alt_prefetch_limit_high;
|
||||
uint32_t rcrb_base;
|
||||
uint32_t rcrb_base_high;
|
||||
} CXLDVSECPortExtensions;
|
||||
QEMU_BUILD_BUG_ON(sizeof(CXLDVSECPortExtensions) != 0x28);
|
||||
} CXLDVSECPortExt;
|
||||
QEMU_BUILD_BUG_ON(sizeof(CXLDVSECPortExt) != 0x28);
|
||||
|
||||
#define PORT_CONTROL_OFFSET 0xc
|
||||
#define PORT_CONTROL_UNMASK_SBR 1
|
||||
|
|
19
include/hw/pci-bridge/cxl_upstream_port.h
Normal file
19
include/hw/pci-bridge/cxl_upstream_port.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
|
||||
#ifndef CXL_USP_H
|
||||
#define CXL_USP_H
|
||||
#include "hw/pci/pcie.h"
|
||||
#include "hw/pci/pcie_port.h"
|
||||
#include "hw/cxl/cxl.h"
|
||||
|
||||
typedef struct CXLUpstreamPort {
|
||||
/*< private >*/
|
||||
PCIEPort parent_obj;
|
||||
|
||||
/*< public >*/
|
||||
CXLComponentState cxl_cstate;
|
||||
CXLCCI swcci;
|
||||
DOECap doe_cdat;
|
||||
uint64_t sn;
|
||||
} CXLUpstreamPort;
|
||||
|
||||
#endif /* CXL_SUP_H */
|
|
@ -26,6 +26,18 @@ typedef enum VhostSetConfigType {
|
|||
VHOST_SET_CONFIG_TYPE_MIGRATION = 1,
|
||||
} VhostSetConfigType;
|
||||
|
||||
typedef enum VhostDeviceStateDirection {
|
||||
/* Transfer state from back-end (device) to front-end */
|
||||
VHOST_TRANSFER_STATE_DIRECTION_SAVE = 0,
|
||||
/* Transfer state from front-end to back-end (device) */
|
||||
VHOST_TRANSFER_STATE_DIRECTION_LOAD = 1,
|
||||
} VhostDeviceStateDirection;
|
||||
|
||||
typedef enum VhostDeviceStatePhase {
|
||||
/* The device (and all its vrings) is stopped */
|
||||
VHOST_TRANSFER_STATE_PHASE_STOPPED = 0,
|
||||
} VhostDeviceStatePhase;
|
||||
|
||||
struct vhost_inflight;
|
||||
struct vhost_dev;
|
||||
struct vhost_log;
|
||||
|
@ -129,6 +141,15 @@ typedef int (*vhost_set_config_call_op)(struct vhost_dev *dev,
|
|||
|
||||
typedef void (*vhost_reset_status_op)(struct vhost_dev *dev);
|
||||
|
||||
typedef bool (*vhost_supports_device_state_op)(struct vhost_dev *dev);
|
||||
typedef int (*vhost_set_device_state_fd_op)(struct vhost_dev *dev,
|
||||
VhostDeviceStateDirection direction,
|
||||
VhostDeviceStatePhase phase,
|
||||
int fd,
|
||||
int *reply_fd,
|
||||
Error **errp);
|
||||
typedef int (*vhost_check_device_state_op)(struct vhost_dev *dev, Error **errp);
|
||||
|
||||
typedef struct VhostOps {
|
||||
VhostBackendType backend_type;
|
||||
vhost_backend_init vhost_backend_init;
|
||||
|
@ -176,6 +197,9 @@ typedef struct VhostOps {
|
|||
vhost_force_iommu_op vhost_force_iommu;
|
||||
vhost_set_config_call_op vhost_set_config_call;
|
||||
vhost_reset_status_op vhost_reset_status;
|
||||
vhost_supports_device_state_op vhost_supports_device_state;
|
||||
vhost_set_device_state_fd_op vhost_set_device_state_fd;
|
||||
vhost_check_device_state_op vhost_check_device_state;
|
||||
} VhostOps;
|
||||
|
||||
int vhost_backend_update_device_iotlb(struct vhost_dev *dev,
|
||||
|
|
|
@ -31,6 +31,7 @@ enum VhostUserProtocolFeature {
|
|||
VHOST_USER_PROTOCOL_F_STATUS = 16,
|
||||
/* Feature 17 reserved for VHOST_USER_PROTOCOL_F_XEN_MMAP. */
|
||||
VHOST_USER_PROTOCOL_F_SHARED_OBJECT = 18,
|
||||
VHOST_USER_PROTOCOL_F_DEVICE_STATE = 19,
|
||||
VHOST_USER_PROTOCOL_F_MAX
|
||||
};
|
||||
|
||||
|
|
|
@ -351,4 +351,117 @@ static inline int vhost_reset_device(struct vhost_dev *hdev)
|
|||
}
|
||||
#endif /* CONFIG_VHOST */
|
||||
|
||||
/**
|
||||
* vhost_supports_device_state(): Checks whether the back-end supports
|
||||
* transferring internal device state for the purpose of migration.
|
||||
* Support for this feature is required for vhost_set_device_state_fd()
|
||||
* and vhost_check_device_state().
|
||||
*
|
||||
* @dev: The vhost device
|
||||
*
|
||||
* Returns true if the device supports these commands, and false if it
|
||||
* does not.
|
||||
*/
|
||||
bool vhost_supports_device_state(struct vhost_dev *dev);
|
||||
|
||||
/**
|
||||
* vhost_set_device_state_fd(): Begin transfer of internal state from/to
|
||||
* the back-end for the purpose of migration. Data is to be transferred
|
||||
* over a pipe according to @direction and @phase. The sending end must
|
||||
* only write to the pipe, and the receiving end must only read from it.
|
||||
* Once the sending end is done, it closes its FD. The receiving end
|
||||
* must take this as the end-of-transfer signal and close its FD, too.
|
||||
*
|
||||
* @fd is the back-end's end of the pipe: The write FD for SAVE, and the
|
||||
* read FD for LOAD. This function transfers ownership of @fd to the
|
||||
* back-end, i.e. closes it in the front-end.
|
||||
*
|
||||
* The back-end may optionally reply with an FD of its own, if this
|
||||
* improves efficiency on its end. In this case, the returned FD is
|
||||
* stored in *reply_fd. The back-end will discard the FD sent to it,
|
||||
* and the front-end must use *reply_fd for transferring state to/from
|
||||
* the back-end.
|
||||
*
|
||||
* @dev: The vhost device
|
||||
* @direction: The direction in which the state is to be transferred.
|
||||
* For outgoing migrations, this is SAVE, and data is read
|
||||
* from the back-end and stored by the front-end in the
|
||||
* migration stream.
|
||||
* For incoming migrations, this is LOAD, and data is read
|
||||
* by the front-end from the migration stream and sent to
|
||||
* the back-end to restore the saved state.
|
||||
* @phase: Which migration phase we are in. Currently, there is only
|
||||
* STOPPED (device and all vrings are stopped), in the future,
|
||||
* more phases such as PRE_COPY or POST_COPY may be added.
|
||||
* @fd: Back-end's end of the pipe through which to transfer state; note
|
||||
* that ownership is transferred to the back-end, so this function
|
||||
* closes @fd in the front-end.
|
||||
* @reply_fd: If the back-end wishes to use a different pipe for state
|
||||
* transfer, this will contain an FD for the front-end to
|
||||
* use. Otherwise, -1 is stored here.
|
||||
* @errp: Potential error description
|
||||
*
|
||||
* Returns 0 on success, and -errno on failure.
|
||||
*/
|
||||
int vhost_set_device_state_fd(struct vhost_dev *dev,
|
||||
VhostDeviceStateDirection direction,
|
||||
VhostDeviceStatePhase phase,
|
||||
int fd,
|
||||
int *reply_fd,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* vhost_set_device_state_fd(): After transferring state from/to the
|
||||
* back-end via vhost_set_device_state_fd(), i.e. once the sending end
|
||||
* has closed the pipe, inquire the back-end to report any potential
|
||||
* errors that have occurred on its side. This allows to sense errors
|
||||
* like:
|
||||
* - During outgoing migration, when the source side had already started
|
||||
* to produce its state, something went wrong and it failed to finish
|
||||
* - During incoming migration, when the received state is somehow
|
||||
* invalid and cannot be processed by the back-end
|
||||
*
|
||||
* @dev: The vhost device
|
||||
* @errp: Potential error description
|
||||
*
|
||||
* Returns 0 when the back-end reports successful state transfer and
|
||||
* processing, and -errno when an error occurred somewhere.
|
||||
*/
|
||||
int vhost_check_device_state(struct vhost_dev *dev, Error **errp);
|
||||
|
||||
/**
|
||||
* vhost_save_backend_state(): High-level function to receive a vhost
|
||||
* back-end's state, and save it in @f. Uses
|
||||
* `vhost_set_device_state_fd()` to get the data from the back-end, and
|
||||
* stores it in consecutive chunks that are each prefixed by their
|
||||
* respective length (be32). The end is marked by a 0-length chunk.
|
||||
*
|
||||
* Must only be called while the device and all its vrings are stopped
|
||||
* (`VHOST_TRANSFER_STATE_PHASE_STOPPED`).
|
||||
*
|
||||
* @dev: The vhost device from which to save the state
|
||||
* @f: Migration stream in which to save the state
|
||||
* @errp: Potential error message
|
||||
*
|
||||
* Returns 0 on success, and -errno otherwise.
|
||||
*/
|
||||
int vhost_save_backend_state(struct vhost_dev *dev, QEMUFile *f, Error **errp);
|
||||
|
||||
/**
|
||||
* vhost_load_backend_state(): High-level function to load a vhost
|
||||
* back-end's state from @f, and send it over to the back-end. Reads
|
||||
* the data from @f in the format used by `vhost_save_state()`, and uses
|
||||
* `vhost_set_device_state_fd()` to transfer it to the back-end.
|
||||
*
|
||||
* Must only be called while the device and all its vrings are stopped
|
||||
* (`VHOST_TRANSFER_STATE_PHASE_STOPPED`).
|
||||
*
|
||||
* @dev: The vhost device to which to send the sate
|
||||
* @f: Migration stream from which to load the state
|
||||
* @errp: Potential error message
|
||||
*
|
||||
* Returns 0 on success, and -errno otherwise.
|
||||
*/
|
||||
int vhost_load_backend_state(struct vhost_dev *dev, QEMUFile *f, Error **errp);
|
||||
|
||||
#endif
|
||||
|
|
122
net/vhost-vdpa.c
122
net/vhost-vdpa.c
|
@ -121,6 +121,8 @@ static const uint64_t vdpa_svq_device_features =
|
|||
BIT_ULL(VIRTIO_NET_F_CTRL_MAC_ADDR) |
|
||||
/* VHOST_F_LOG_ALL is exposed by SVQ */
|
||||
BIT_ULL(VHOST_F_LOG_ALL) |
|
||||
BIT_ULL(VIRTIO_NET_F_HASH_REPORT) |
|
||||
BIT_ULL(VIRTIO_NET_F_RSS) |
|
||||
BIT_ULL(VIRTIO_NET_F_RSC_EXT) |
|
||||
BIT_ULL(VIRTIO_NET_F_STANDBY) |
|
||||
BIT_ULL(VIRTIO_NET_F_SPEED_DUPLEX);
|
||||
|
@ -240,6 +242,12 @@ static void vhost_vdpa_cleanup(NetClientState *nc)
|
|||
}
|
||||
}
|
||||
|
||||
/** Dummy SetSteeringEBPF to support RSS for vhost-vdpa backend */
|
||||
static bool vhost_vdpa_set_steering_ebpf(NetClientState *nc, int prog_fd)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool vhost_vdpa_has_vnet_hdr(NetClientState *nc)
|
||||
{
|
||||
assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA);
|
||||
|
@ -422,6 +430,7 @@ static NetClientInfo net_vhost_vdpa_info = {
|
|||
.has_vnet_hdr = vhost_vdpa_has_vnet_hdr,
|
||||
.has_ufo = vhost_vdpa_has_ufo,
|
||||
.check_peer_type = vhost_vdpa_check_peer_type,
|
||||
.set_steering_ebpf = vhost_vdpa_set_steering_ebpf,
|
||||
};
|
||||
|
||||
static int64_t vhost_vdpa_get_vring_group(int device_fd, unsigned vq_index,
|
||||
|
@ -818,6 +827,103 @@ static int vhost_vdpa_net_load_mac(VhostVDPAState *s, const VirtIONet *n,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int vhost_vdpa_net_load_rss(VhostVDPAState *s, const VirtIONet *n,
|
||||
struct iovec *out_cursor,
|
||||
struct iovec *in_cursor, bool do_rss)
|
||||
{
|
||||
struct virtio_net_rss_config cfg = {};
|
||||
ssize_t r;
|
||||
g_autofree uint16_t *table = NULL;
|
||||
|
||||
/*
|
||||
* According to VirtIO standard, "Initially the device has all hash
|
||||
* types disabled and reports only VIRTIO_NET_HASH_REPORT_NONE.".
|
||||
*
|
||||
* Therefore, there is no need to send this CVQ command if the
|
||||
* driver disables the all hash types, which aligns with
|
||||
* the device's defaults.
|
||||
*
|
||||
* Note that the device's defaults can mismatch the driver's
|
||||
* configuration only at live migration.
|
||||
*/
|
||||
if (!n->rss_data.enabled ||
|
||||
n->rss_data.hash_types == VIRTIO_NET_HASH_REPORT_NONE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
table = g_malloc_n(n->rss_data.indirections_len,
|
||||
sizeof(n->rss_data.indirections_table[0]));
|
||||
cfg.hash_types = cpu_to_le32(n->rss_data.hash_types);
|
||||
|
||||
if (do_rss) {
|
||||
/*
|
||||
* According to VirtIO standard, "Number of entries in indirection_table
|
||||
* is (indirection_table_mask + 1)".
|
||||
*/
|
||||
cfg.indirection_table_mask = cpu_to_le16(n->rss_data.indirections_len -
|
||||
1);
|
||||
cfg.unclassified_queue = cpu_to_le16(n->rss_data.default_queue);
|
||||
for (int i = 0; i < n->rss_data.indirections_len; ++i) {
|
||||
table[i] = cpu_to_le16(n->rss_data.indirections_table[i]);
|
||||
}
|
||||
cfg.max_tx_vq = cpu_to_le16(n->curr_queue_pairs);
|
||||
} else {
|
||||
/*
|
||||
* According to VirtIO standard, "Field reserved MUST contain zeroes.
|
||||
* It is defined to make the structure to match the layout of
|
||||
* virtio_net_rss_config structure, defined in 5.1.6.5.7.".
|
||||
*
|
||||
* Therefore, we need to zero the fields in
|
||||
* struct virtio_net_rss_config, which corresponds to the
|
||||
* `reserved` field in struct virtio_net_hash_config.
|
||||
*
|
||||
* Note that all other fields are zeroed at their definitions,
|
||||
* except for the `indirection_table` field, where the actual data
|
||||
* is stored in the `table` variable to ensure compatibility
|
||||
* with RSS case. Therefore, we need to zero the `table` variable here.
|
||||
*/
|
||||
table[0] = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Considering that virtio_net_handle_rss() currently does not restore
|
||||
* the hash key length parsed from the CVQ command sent from the guest
|
||||
* into n->rss_data and uses the maximum key length in other code, so
|
||||
* we also employ the maximum key length here.
|
||||
*/
|
||||
cfg.hash_key_length = sizeof(n->rss_data.key);
|
||||
|
||||
const struct iovec data[] = {
|
||||
{
|
||||
.iov_base = &cfg,
|
||||
.iov_len = offsetof(struct virtio_net_rss_config,
|
||||
indirection_table),
|
||||
}, {
|
||||
.iov_base = table,
|
||||
.iov_len = n->rss_data.indirections_len *
|
||||
sizeof(n->rss_data.indirections_table[0]),
|
||||
}, {
|
||||
.iov_base = &cfg.max_tx_vq,
|
||||
.iov_len = offsetof(struct virtio_net_rss_config, hash_key_data) -
|
||||
offsetof(struct virtio_net_rss_config, max_tx_vq),
|
||||
}, {
|
||||
.iov_base = (void *)n->rss_data.key,
|
||||
.iov_len = sizeof(n->rss_data.key),
|
||||
}
|
||||
};
|
||||
|
||||
r = vhost_vdpa_net_load_cmd(s, out_cursor, in_cursor,
|
||||
VIRTIO_NET_CTRL_MQ,
|
||||
do_rss ? VIRTIO_NET_CTRL_MQ_RSS_CONFIG :
|
||||
VIRTIO_NET_CTRL_MQ_HASH_CONFIG,
|
||||
data, ARRAY_SIZE(data));
|
||||
if (unlikely(r < 0)) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vhost_vdpa_net_load_mq(VhostVDPAState *s,
|
||||
const VirtIONet *n,
|
||||
struct iovec *out_cursor,
|
||||
|
@ -843,6 +949,21 @@ static int vhost_vdpa_net_load_mq(VhostVDPAState *s,
|
|||
return r;
|
||||
}
|
||||
|
||||
if (virtio_vdev_has_feature(&n->parent_obj, VIRTIO_NET_F_RSS)) {
|
||||
/* load the receive-side scaling state */
|
||||
r = vhost_vdpa_net_load_rss(s, n, out_cursor, in_cursor, true);
|
||||
if (unlikely(r < 0)) {
|
||||
return r;
|
||||
}
|
||||
} else if (virtio_vdev_has_feature(&n->parent_obj,
|
||||
VIRTIO_NET_F_HASH_REPORT)) {
|
||||
/* load the hash calculation state */
|
||||
r = vhost_vdpa_net_load_rss(s, n, out_cursor, in_cursor, false);
|
||||
if (unlikely(r < 0)) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1166,6 +1287,7 @@ static NetClientInfo net_vhost_vdpa_cvq_info = {
|
|||
.has_vnet_hdr = vhost_vdpa_has_vnet_hdr,
|
||||
.has_ufo = vhost_vdpa_has_ufo,
|
||||
.check_peer_type = vhost_vdpa_check_peer_type,
|
||||
.set_steering_ebpf = vhost_vdpa_set_steering_ebpf,
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -111,6 +111,8 @@ static const QDevAlias qdev_alias_table[] = {
|
|||
{ "virtio-serial-device", "virtio-serial", QEMU_ARCH_VIRTIO_MMIO },
|
||||
{ "virtio-serial-ccw", "virtio-serial", QEMU_ARCH_VIRTIO_CCW },
|
||||
{ "virtio-serial-pci", "virtio-serial", QEMU_ARCH_VIRTIO_PCI},
|
||||
{ "virtio-sound-device", "virtio-sound", QEMU_ARCH_VIRTIO_MMIO },
|
||||
{ "virtio-sound-pci", "virtio-sound", QEMU_ARCH_VIRTIO_PCI },
|
||||
{ "virtio-tablet-device", "virtio-tablet", QEMU_ARCH_VIRTIO_MMIO },
|
||||
{ "virtio-tablet-ccw", "virtio-tablet", QEMU_ARCH_VIRTIO_CCW },
|
||||
{ "virtio-tablet-pci", "virtio-tablet", QEMU_ARCH_VIRTIO_PCI },
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#
|
||||
#
|
||||
# Author:
|
||||
# Ani Sinha <ani@anisinha.ca>
|
||||
# Ani Sinha <anisinha@redhat.com>
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
# pylint: disable=consider-using-f-string
|
||||
|
@ -48,6 +48,7 @@
|
|||
)
|
||||
from qemu.machine import QEMUMachine
|
||||
from avocado import skipIf
|
||||
from avocado.utils import datadrainer as drainer
|
||||
from avocado_qemu import QemuBaseTest
|
||||
|
||||
deps = ["xorriso", "mformat"] # dependent tools needed in the test setup/box.
|
||||
|
@ -141,12 +142,12 @@ def __init__(self, *args, **kwargs):
|
|||
self._baseDir = None
|
||||
|
||||
# following are some standard configuration constants
|
||||
self._bitsInternalVer = 2020
|
||||
self._bitsCommitHash = 'b48b88ff' # commit hash must match
|
||||
self._bitsInternalVer = 2020 # gitlab CI does shallow clones of depth 20
|
||||
self._bitsCommitHash = 'c7920d2b' # commit hash must match
|
||||
# the artifact tag below
|
||||
self._bitsTag = "qemu-bits-10182022" # this is the latest bits
|
||||
self._bitsTag = "qemu-bits-10262023" # this is the latest bits
|
||||
# release as of today.
|
||||
self._bitsArtSHA1Hash = 'b04790ac9b99b5662d0416392c73b97580641fe5'
|
||||
self._bitsArtSHA1Hash = 'b22cdfcfc7453875297d06d626f5474ee36a343f'
|
||||
self._bitsArtURL = ("https://gitlab.com/qemu-project/"
|
||||
"biosbits-bits/-/jobs/artifacts/%s/"
|
||||
"download?job=qemu-bits-build" %self._bitsTag)
|
||||
|
@ -380,16 +381,26 @@ def test_acpi_smbios_bits(self):
|
|||
# consistent in terms of timing. smilatency tests have consistent
|
||||
# timing requirements.
|
||||
self._vm.add_args('-icount', 'auto')
|
||||
# currently there is no support in bits for recognizing 64-bit SMBIOS
|
||||
# entry points. QEMU defaults to 64-bit entry points since the
|
||||
# upstream commit bf376f3020 ("hw/i386/pc: Default to use SMBIOS 3.0
|
||||
# for newer machine models"). Therefore, enforce 32-bit entry point.
|
||||
self._vm.add_args('-machine', 'smbios-entry-point-type=32')
|
||||
|
||||
args = " ".join(str(arg) for arg in self._vm.base_args()) + \
|
||||
" " + " ".join(str(arg) for arg in self._vm.args)
|
||||
|
||||
self.logger.info("launching QEMU vm with the following arguments: %s",
|
||||
args)
|
||||
|
||||
# enable console logging
|
||||
self._vm.set_console()
|
||||
self._vm.launch()
|
||||
|
||||
self.logger.debug("Console output from bits VM follows ...")
|
||||
c_drainer = drainer.LineLogger(self._vm.console_socket.fileno(),
|
||||
logger=self.logger.getChild("console"),
|
||||
stop_check=(lambda :
|
||||
not self._vm.is_running()))
|
||||
c_drainer.start()
|
||||
|
||||
# biosbits has been configured to run all the specified test suites
|
||||
# in batch mode and then automatically initiate a vm shutdown.
|
||||
# Rely on avocado's unit test timeout.
|
||||
self._vm.event_wait('SHUTDOWN')
|
||||
self._vm.wait(timeout=None)
|
||||
self.parse_log()
|
||||
|
|
BIN
tests/data/acpi/q35/APIC.core-count
Normal file
BIN
tests/data/acpi/q35/APIC.core-count
Normal file
Binary file not shown.
Binary file not shown.
BIN
tests/data/acpi/q35/APIC.thread-count
Normal file
BIN
tests/data/acpi/q35/APIC.thread-count
Normal file
Binary file not shown.
BIN
tests/data/acpi/q35/APIC.thread-count2
Normal file
BIN
tests/data/acpi/q35/APIC.thread-count2
Normal file
Binary file not shown.
BIN
tests/data/acpi/q35/APIC.type4-count
Normal file
BIN
tests/data/acpi/q35/APIC.type4-count
Normal file
Binary file not shown.
BIN
tests/data/acpi/q35/DSDT.core-count
Normal file
BIN
tests/data/acpi/q35/DSDT.core-count
Normal file
Binary file not shown.
Binary file not shown.
BIN
tests/data/acpi/q35/DSDT.thread-count
Normal file
BIN
tests/data/acpi/q35/DSDT.thread-count
Normal file
Binary file not shown.
BIN
tests/data/acpi/q35/DSDT.thread-count2
Normal file
BIN
tests/data/acpi/q35/DSDT.thread-count2
Normal file
Binary file not shown.
BIN
tests/data/acpi/q35/DSDT.type4-count
Normal file
BIN
tests/data/acpi/q35/DSDT.type4-count
Normal file
Binary file not shown.
BIN
tests/data/acpi/q35/FACP.core-count
Normal file
BIN
tests/data/acpi/q35/FACP.core-count
Normal file
Binary file not shown.
BIN
tests/data/acpi/q35/FACP.thread-count
Normal file
BIN
tests/data/acpi/q35/FACP.thread-count
Normal file
Binary file not shown.
BIN
tests/data/acpi/q35/FACP.thread-count2
Normal file
BIN
tests/data/acpi/q35/FACP.thread-count2
Normal file
Binary file not shown.
BIN
tests/data/acpi/q35/FACP.type4-count
Normal file
BIN
tests/data/acpi/q35/FACP.type4-count
Normal file
Binary file not shown.
|
@ -95,8 +95,11 @@ typedef struct {
|
|||
uint16_t smbios_cpu_curr_speed;
|
||||
uint8_t smbios_core_count;
|
||||
uint16_t smbios_core_count2;
|
||||
uint8_t smbios_thread_count;
|
||||
uint16_t smbios_thread_count2;
|
||||
uint8_t *required_struct_types;
|
||||
int required_struct_types_len;
|
||||
int type4_count;
|
||||
QTestState *qts;
|
||||
} test_data;
|
||||
|
||||
|
@ -639,8 +642,10 @@ static void smbios_cpu_test(test_data *data, uint32_t addr,
|
|||
SmbiosEntryPointType ep_type)
|
||||
{
|
||||
uint8_t core_count, expected_core_count = data->smbios_core_count;
|
||||
uint8_t thread_count, expected_thread_count = data->smbios_thread_count;
|
||||
uint16_t speed, expected_speed[2];
|
||||
uint16_t core_count2, expected_core_count2 = data->smbios_core_count2;
|
||||
uint16_t thread_count2, expected_thread_count2 = data->smbios_thread_count2;
|
||||
int offset[2];
|
||||
int i;
|
||||
|
||||
|
@ -662,6 +667,13 @@ static void smbios_cpu_test(test_data *data, uint32_t addr,
|
|||
g_assert_cmpuint(core_count, ==, expected_core_count);
|
||||
}
|
||||
|
||||
thread_count = qtest_readb(data->qts,
|
||||
addr + offsetof(struct smbios_type_4, thread_count));
|
||||
|
||||
if (expected_thread_count) {
|
||||
g_assert_cmpuint(thread_count, ==, expected_thread_count);
|
||||
}
|
||||
|
||||
if (ep_type == SMBIOS_ENTRY_POINT_TYPE_64) {
|
||||
core_count2 = qtest_readw(data->qts,
|
||||
addr + offsetof(struct smbios_type_4, core_count2));
|
||||
|
@ -670,6 +682,24 @@ static void smbios_cpu_test(test_data *data, uint32_t addr,
|
|||
if (expected_core_count == 0xFF && expected_core_count2) {
|
||||
g_assert_cmpuint(core_count2, ==, expected_core_count2);
|
||||
}
|
||||
|
||||
thread_count2 = qtest_readw(data->qts,
|
||||
addr + offsetof(struct smbios_type_4,
|
||||
thread_count2));
|
||||
|
||||
/* Thread Count has reached its limit, checking Thread Count 2 */
|
||||
if (expected_thread_count == 0xFF && expected_thread_count2) {
|
||||
g_assert_cmpuint(thread_count2, ==, expected_thread_count2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void smbios_type4_count_test(test_data *data, int type4_count)
|
||||
{
|
||||
int expected_type4_count = data->type4_count;
|
||||
|
||||
if (expected_type4_count) {
|
||||
g_assert_cmpuint(type4_count, ==, expected_type4_count);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -678,7 +708,7 @@ static void test_smbios_structs(test_data *data, SmbiosEntryPointType ep_type)
|
|||
DECLARE_BITMAP(struct_bitmap, SMBIOS_MAX_TYPE+1) = { 0 };
|
||||
|
||||
SmbiosEntryPoint *ep_table = &data->smbios_ep_table;
|
||||
int i = 0, len, max_len = 0;
|
||||
int i = 0, len, max_len = 0, type4_count = 0;
|
||||
uint8_t type, prv, crt;
|
||||
uint64_t addr;
|
||||
|
||||
|
@ -704,6 +734,7 @@ static void test_smbios_structs(test_data *data, SmbiosEntryPointType ep_type)
|
|||
|
||||
if (type == 4) {
|
||||
smbios_cpu_test(data, addr, ep_type);
|
||||
type4_count++;
|
||||
}
|
||||
|
||||
/* seek to end of unformatted string area of this struct ("\0\0") */
|
||||
|
@ -747,6 +778,8 @@ static void test_smbios_structs(test_data *data, SmbiosEntryPointType ep_type)
|
|||
for (i = 0; i < data->required_struct_types_len; i++) {
|
||||
g_assert(test_bit(data->required_struct_types[i], struct_bitmap));
|
||||
}
|
||||
|
||||
smbios_type4_count_test(data, type4_count);
|
||||
}
|
||||
|
||||
static void test_acpi_load_tables(test_data *data)
|
||||
|
@ -970,6 +1003,39 @@ static void test_acpi_q35_tcg(void)
|
|||
free_test_data(&data);
|
||||
}
|
||||
|
||||
static void test_acpi_q35_tcg_type4_count(void)
|
||||
{
|
||||
test_data data = {
|
||||
.machine = MACHINE_Q35,
|
||||
.variant = ".type4-count",
|
||||
.required_struct_types = base_required_struct_types,
|
||||
.required_struct_types_len = ARRAY_SIZE(base_required_struct_types),
|
||||
.type4_count = 5,
|
||||
};
|
||||
|
||||
test_acpi_one("-machine smbios-entry-point-type=64 "
|
||||
"-smp cpus=100,maxcpus=120,sockets=5,"
|
||||
"dies=2,cores=4,threads=3", &data);
|
||||
free_test_data(&data);
|
||||
}
|
||||
|
||||
static void test_acpi_q35_tcg_core_count(void)
|
||||
{
|
||||
test_data data = {
|
||||
.machine = MACHINE_Q35,
|
||||
.variant = ".core-count",
|
||||
.required_struct_types = base_required_struct_types,
|
||||
.required_struct_types_len = ARRAY_SIZE(base_required_struct_types),
|
||||
.smbios_core_count = 9,
|
||||
.smbios_core_count2 = 9,
|
||||
};
|
||||
|
||||
test_acpi_one("-machine smbios-entry-point-type=64 "
|
||||
"-smp 54,sockets=2,dies=3,cores=3,threads=3",
|
||||
&data);
|
||||
free_test_data(&data);
|
||||
}
|
||||
|
||||
static void test_acpi_q35_tcg_core_count2(void)
|
||||
{
|
||||
test_data data = {
|
||||
|
@ -978,10 +1044,46 @@ static void test_acpi_q35_tcg_core_count2(void)
|
|||
.required_struct_types = base_required_struct_types,
|
||||
.required_struct_types_len = ARRAY_SIZE(base_required_struct_types),
|
||||
.smbios_core_count = 0xFF,
|
||||
.smbios_core_count2 = 275,
|
||||
.smbios_core_count2 = 260,
|
||||
};
|
||||
|
||||
test_acpi_one("-machine smbios-entry-point-type=64 -smp 275", &data);
|
||||
test_acpi_one("-machine smbios-entry-point-type=64 "
|
||||
"-smp 260,dies=2,cores=130,threads=1",
|
||||
&data);
|
||||
free_test_data(&data);
|
||||
}
|
||||
|
||||
static void test_acpi_q35_tcg_thread_count(void)
|
||||
{
|
||||
test_data data = {
|
||||
.machine = MACHINE_Q35,
|
||||
.variant = ".thread-count",
|
||||
.required_struct_types = base_required_struct_types,
|
||||
.required_struct_types_len = ARRAY_SIZE(base_required_struct_types),
|
||||
.smbios_thread_count = 27,
|
||||
.smbios_thread_count2 = 27,
|
||||
};
|
||||
|
||||
test_acpi_one("-machine smbios-entry-point-type=64 "
|
||||
"-smp cpus=15,maxcpus=54,sockets=2,dies=3,cores=3,threads=3",
|
||||
&data);
|
||||
free_test_data(&data);
|
||||
}
|
||||
|
||||
static void test_acpi_q35_tcg_thread_count2(void)
|
||||
{
|
||||
test_data data = {
|
||||
.machine = MACHINE_Q35,
|
||||
.variant = ".thread-count2",
|
||||
.required_struct_types = base_required_struct_types,
|
||||
.required_struct_types_len = ARRAY_SIZE(base_required_struct_types),
|
||||
.smbios_thread_count = 0xFF,
|
||||
.smbios_thread_count2 = 260,
|
||||
};
|
||||
|
||||
test_acpi_one("-machine smbios-entry-point-type=64 "
|
||||
"-smp cpus=210,maxcpus=260,dies=2,cores=65,threads=2",
|
||||
&data);
|
||||
free_test_data(&data);
|
||||
}
|
||||
|
||||
|
@ -2147,8 +2249,16 @@ int main(int argc, char *argv[])
|
|||
if (has_kvm) {
|
||||
qtest_add_func("acpi/q35/kvm/xapic", test_acpi_q35_kvm_xapic);
|
||||
qtest_add_func("acpi/q35/kvm/dmar", test_acpi_q35_kvm_dmar);
|
||||
qtest_add_func("acpi/q35/type4-count",
|
||||
test_acpi_q35_tcg_type4_count);
|
||||
qtest_add_func("acpi/q35/core-count",
|
||||
test_acpi_q35_tcg_core_count);
|
||||
qtest_add_func("acpi/q35/core-count2",
|
||||
test_acpi_q35_tcg_core_count2);
|
||||
qtest_add_func("acpi/q35/thread-count",
|
||||
test_acpi_q35_tcg_thread_count);
|
||||
qtest_add_func("acpi/q35/thread-count2",
|
||||
test_acpi_q35_tcg_thread_count2);
|
||||
}
|
||||
if (qtest_has_device("virtio-iommu-pci")) {
|
||||
qtest_add_func("acpi/q35/viot", test_acpi_q35_viot);
|
||||
|
|
|
@ -394,20 +394,47 @@ static char *smp_config_to_string(const SMPConfiguration *config)
|
|||
config->has_maxcpus ? "true" : "false", config->maxcpus);
|
||||
}
|
||||
|
||||
static char *cpu_topology_to_string(const CpuTopology *topo)
|
||||
/* Use the different calculation than machine_topo_get_threads_per_socket(). */
|
||||
static unsigned int cpu_topology_get_threads_per_socket(const CpuTopology *topo)
|
||||
{
|
||||
/* Check the divisor to avoid invalid topology examples causing SIGFPE. */
|
||||
if (!topo->sockets) {
|
||||
return 0;
|
||||
} else {
|
||||
return topo->max_cpus / topo->sockets;
|
||||
}
|
||||
}
|
||||
|
||||
/* Use the different calculation than machine_topo_get_cores_per_socket(). */
|
||||
static unsigned int cpu_topology_get_cores_per_socket(const CpuTopology *topo)
|
||||
{
|
||||
/* Check the divisor to avoid invalid topology examples causing SIGFPE. */
|
||||
if (!topo->threads) {
|
||||
return 0;
|
||||
} else {
|
||||
return cpu_topology_get_threads_per_socket(topo) / topo->threads;
|
||||
}
|
||||
}
|
||||
|
||||
static char *cpu_topology_to_string(const CpuTopology *topo,
|
||||
unsigned int threads_per_socket,
|
||||
unsigned int cores_per_socket)
|
||||
{
|
||||
return g_strdup_printf(
|
||||
"(CpuTopology) {\n"
|
||||
" .cpus = %u,\n"
|
||||
" .sockets = %u,\n"
|
||||
" .dies = %u,\n"
|
||||
" .clusters = %u,\n"
|
||||
" .cores = %u,\n"
|
||||
" .threads = %u,\n"
|
||||
" .max_cpus = %u,\n"
|
||||
" .cpus = %u,\n"
|
||||
" .sockets = %u,\n"
|
||||
" .dies = %u,\n"
|
||||
" .clusters = %u,\n"
|
||||
" .cores = %u,\n"
|
||||
" .threads = %u,\n"
|
||||
" .max_cpus = %u,\n"
|
||||
" .threads_per_socket = %u,\n"
|
||||
" .cores_per_socket = %u,\n"
|
||||
"}",
|
||||
topo->cpus, topo->sockets, topo->dies, topo->clusters,
|
||||
topo->cores, topo->threads, topo->max_cpus);
|
||||
topo->cores, topo->threads, topo->max_cpus,
|
||||
threads_per_socket, cores_per_socket);
|
||||
}
|
||||
|
||||
static void check_parse(MachineState *ms, const SMPConfiguration *config,
|
||||
|
@ -415,14 +442,26 @@ static void check_parse(MachineState *ms, const SMPConfiguration *config,
|
|||
bool is_valid)
|
||||
{
|
||||
g_autofree char *config_str = smp_config_to_string(config);
|
||||
g_autofree char *expect_topo_str = cpu_topology_to_string(expect_topo);
|
||||
g_autofree char *output_topo_str = NULL;
|
||||
g_autofree char *expect_topo_str = NULL, *output_topo_str = NULL;
|
||||
unsigned int expect_threads_per_socket, expect_cores_per_socket;
|
||||
unsigned int ms_threads_per_socket, ms_cores_per_socket;
|
||||
Error *err = NULL;
|
||||
|
||||
expect_threads_per_socket =
|
||||
cpu_topology_get_threads_per_socket(expect_topo);
|
||||
expect_cores_per_socket =
|
||||
cpu_topology_get_cores_per_socket(expect_topo);
|
||||
expect_topo_str = cpu_topology_to_string(expect_topo,
|
||||
expect_threads_per_socket,
|
||||
expect_cores_per_socket);
|
||||
|
||||
/* call the generic parser */
|
||||
machine_parse_smp_config(ms, config, &err);
|
||||
|
||||
output_topo_str = cpu_topology_to_string(&ms->smp);
|
||||
ms_threads_per_socket = machine_topo_get_threads_per_socket(ms);
|
||||
ms_cores_per_socket = machine_topo_get_cores_per_socket(ms);
|
||||
output_topo_str = cpu_topology_to_string(&ms->smp, ms_threads_per_socket,
|
||||
ms_cores_per_socket);
|
||||
|
||||
/* when the configuration is supposed to be valid */
|
||||
if (is_valid) {
|
||||
|
@ -433,7 +472,9 @@ static void check_parse(MachineState *ms, const SMPConfiguration *config,
|
|||
(ms->smp.clusters == expect_topo->clusters) &&
|
||||
(ms->smp.cores == expect_topo->cores) &&
|
||||
(ms->smp.threads == expect_topo->threads) &&
|
||||
(ms->smp.max_cpus == expect_topo->max_cpus)) {
|
||||
(ms->smp.max_cpus == expect_topo->max_cpus) &&
|
||||
(ms_threads_per_socket == expect_threads_per_socket) &&
|
||||
(ms_cores_per_socket == expect_cores_per_socket)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue