aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Hajnoczi <stefanha@redhat.com>2023-11-07 18:59:40 +0800
committerStefan Hajnoczi <stefanha@redhat.com>2023-11-07 18:59:41 +0800
commitf6b615b52d1d92f02103596a30df95f31138a2e4 (patch)
treeb8d13f1b7e485177a8b6b470df30eaac268b3466
parent7eee58ae3bb15a2bceb368997ce1a48fd3c607e7 (diff)
parent94cd94f1c0137b56000c01208e03d0907ad34910 (diff)
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>
-rw-r--r--MAINTAINERS9
-rw-r--r--docs/interop/vhost-user.rst301
-rw-r--r--docs/system/device-emulation.rst1
-rw-r--r--docs/system/devices/virtio-snd.rst49
-rw-r--r--hw/audio/Kconfig5
-rw-r--r--hw/audio/meson.build2
-rw-r--r--hw/audio/trace-events20
-rw-r--r--hw/audio/virtio-snd-pci.c93
-rw-r--r--hw/audio/virtio-snd.c1409
-rw-r--r--hw/cxl/cxl-cdat.c3
-rw-r--r--hw/cxl/cxl-component-utils.c130
-rw-r--r--hw/cxl/cxl-device-utils.c143
-rw-r--r--hw/cxl/cxl-events.c11
-rw-r--r--hw/cxl/cxl-mailbox-utils.c1056
-rw-r--r--hw/cxl/meson.build1
-rw-r--r--hw/cxl/switch-mailbox-cci.c111
-rw-r--r--hw/mem/cxl_type3.c63
-rw-r--r--hw/mem/cxl_type3_stubs.c5
-rw-r--r--hw/pci-bridge/cxl_downstream.c20
-rw-r--r--hw/pci-bridge/cxl_root_port.c2
-rw-r--r--hw/pci-bridge/cxl_upstream.c13
-rw-r--r--hw/virtio/vhost-user-fs.c101
-rw-r--r--hw/virtio/vhost-user.c146
-rw-r--r--hw/virtio/vhost.c241
-rw-r--r--include/hw/audio/virtio-snd.h235
-rw-r--r--include/hw/cxl/cxl.h6
-rw-r--r--include/hw/cxl/cxl_component.h6
-rw-r--r--include/hw/cxl/cxl_device.h109
-rw-r--r--include/hw/cxl/cxl_events.h3
-rw-r--r--include/hw/cxl/cxl_pci.h6
-rw-r--r--include/hw/pci-bridge/cxl_upstream_port.h19
-rw-r--r--include/hw/virtio/vhost-backend.h24
-rw-r--r--include/hw/virtio/vhost-user.h1
-rw-r--r--include/hw/virtio/vhost.h113
-rw-r--r--net/vhost-vdpa.c122
-rw-r--r--system/qdev-monitor.c2
-rw-r--r--tests/avocado/acpi-bits.py33
-rw-r--r--tests/data/acpi/q35/APIC.core-countbin0 -> 544 bytes
-rw-r--r--tests/data/acpi/q35/APIC.core-count2bin2478 -> 3238 bytes
-rw-r--r--tests/data/acpi/q35/APIC.thread-countbin0 -> 544 bytes
-rw-r--r--tests/data/acpi/q35/APIC.thread-count2bin0 -> 3238 bytes
-rw-r--r--tests/data/acpi/q35/APIC.type4-countbin0 -> 1072 bytes
-rw-r--r--tests/data/acpi/q35/DSDT.core-countbin0 -> 12913 bytes
-rw-r--r--tests/data/acpi/q35/DSDT.core-count2bin32495 -> 33770 bytes
-rw-r--r--tests/data/acpi/q35/DSDT.thread-countbin0 -> 12913 bytes
-rw-r--r--tests/data/acpi/q35/DSDT.thread-count2bin0 -> 33770 bytes
-rw-r--r--tests/data/acpi/q35/DSDT.type4-countbin0 -> 18589 bytes
-rw-r--r--tests/data/acpi/q35/FACP.core-countbin0 -> 244 bytes
-rw-r--r--tests/data/acpi/q35/FACP.thread-countbin0 -> 244 bytes
-rw-r--r--tests/data/acpi/q35/FACP.thread-count2bin0 -> 244 bytes
-rw-r--r--tests/data/acpi/q35/FACP.type4-countbin0 -> 244 bytes
-rw-r--r--tests/qtest/bios-tables-test.c116
-rw-r--r--tests/unit/test-smp-parse.c67
53 files changed, 4475 insertions, 322 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index fd6b362311..b86ea7f75a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -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>
diff --git a/docs/interop/vhost-user.rst b/docs/interop/vhost-user.rst
index 768fb5c28c..9f1103f85a 100644
--- a/docs/interop/vhost-user.rst
+++ b/docs/interop/vhost-user.rst
@@ -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
+
+ 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*).
- Get the available vring base offset.
+ 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
----------------------
diff --git a/docs/system/device-emulation.rst b/docs/system/device-emulation.rst
index 1167f3a9f2..d1f3277cb0 100644
--- a/docs/system/device-emulation.rst
+++ b/docs/system/device-emulation.rst
@@ -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
diff --git a/docs/system/devices/virtio-snd.rst b/docs/system/devices/virtio-snd.rst
new file mode 100644
index 0000000000..2a9187fd70
--- /dev/null
+++ b/docs/system/devices/virtio-snd.rst
@@ -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
diff --git a/hw/audio/Kconfig b/hw/audio/Kconfig
index d0993514a1..daf060e1be 100644
--- a/hw/audio/Kconfig
+++ b/hw/audio/Kconfig
@@ -50,3 +50,8 @@ config CS4231
config ASC
bool
+
+config VIRTIO_SND
+ bool
+ default y
+ depends on VIRTIO
diff --git a/hw/audio/meson.build b/hw/audio/meson.build
index 8805322f5c..2990974449 100644
--- a/hw/audio/meson.build
+++ b/hw/audio/meson.build
@@ -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'))
diff --git a/hw/audio/trace-events b/hw/audio/trace-events
index 059ce451f5..b1870ff224 100644
--- a/hw/audio/trace-events
+++ b/hw/audio/trace-events
@@ -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"
diff --git a/hw/audio/virtio-snd-pci.c b/hw/audio/virtio-snd-pci.c
new file mode 100644
index 0000000000..0f92e0752b
--- /dev/null
+++ b/hw/audio/virtio-snd-pci.c
@@ -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);
diff --git a/hw/audio/virtio-snd.c b/hw/audio/virtio-snd.c
new file mode 100644
index 0000000000..a18a9949a7
--- /dev/null
+++ b/hw/audio/virtio-snd.c
@@ -0,0 +1,1409 @@
+/*
+ * VIRTIO Sound Device conforming to
+ *
+ * "Virtual I/O Device (VIRTIO) Version 1.2
+ * Committee Specification Draft 01
+ * 09 May 2022"
+ *
+ * <https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-52900014>
+ *
+ * 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.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/iov.h"
+#include "qemu/log.h"
+#include "qemu/error-report.h"
+#include "include/qemu/lockable.h"
+#include "sysemu/runstate.h"
+#include "trace.h"
+#include "qapi/error.h"
+#include "hw/audio/virtio-snd.h"
+#include "hw/core/cpu.h"
+
+#define VIRTIO_SOUND_VM_VERSION 1
+#define VIRTIO_SOUND_JACK_DEFAULT 0
+#define VIRTIO_SOUND_STREAM_DEFAULT 2
+#define VIRTIO_SOUND_CHMAP_DEFAULT 0
+#define VIRTIO_SOUND_HDA_FN_NID 0
+
+static void virtio_snd_pcm_out_cb(void *data, int available);
+static void virtio_snd_process_cmdq(VirtIOSound *s);
+static void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream);
+static void virtio_snd_pcm_in_cb(void *data, int available);
+
+static uint32_t supported_formats = BIT(VIRTIO_SND_PCM_FMT_S8)
+ | BIT(VIRTIO_SND_PCM_FMT_U8)
+ | BIT(VIRTIO_SND_PCM_FMT_S16)
+ | BIT(VIRTIO_SND_PCM_FMT_U16)
+ | BIT(VIRTIO_SND_PCM_FMT_S32)
+ | BIT(VIRTIO_SND_PCM_FMT_U32)
+ | BIT(VIRTIO_SND_PCM_FMT_FLOAT);
+
+static uint32_t supported_rates = BIT(VIRTIO_SND_PCM_RATE_5512)
+ | BIT(VIRTIO_SND_PCM_RATE_8000)
+ | BIT(VIRTIO_SND_PCM_RATE_11025)
+ | BIT(VIRTIO_SND_PCM_RATE_16000)
+ | BIT(VIRTIO_SND_PCM_RATE_22050)
+ | BIT(VIRTIO_SND_PCM_RATE_32000)
+ | BIT(VIRTIO_SND_PCM_RATE_44100)
+ | BIT(VIRTIO_SND_PCM_RATE_48000)
+ | BIT(VIRTIO_SND_PCM_RATE_64000)
+ | BIT(VIRTIO_SND_PCM_RATE_88200)
+ | BIT(VIRTIO_SND_PCM_RATE_96000)
+ | BIT(VIRTIO_SND_PCM_RATE_176400)
+ | BIT(VIRTIO_SND_PCM_RATE_192000)
+ | BIT(VIRTIO_SND_PCM_RATE_384000);
+
+static const VMStateDescription vmstate_virtio_snd_device = {
+ .name = TYPE_VIRTIO_SND,
+ .version_id = VIRTIO_SOUND_VM_VERSION,
+ .minimum_version_id = VIRTIO_SOUND_VM_VERSION,
+};
+
+static const VMStateDescription vmstate_virtio_snd = {
+ .name = TYPE_VIRTIO_SND,
+ .minimum_version_id = VIRTIO_SOUND_VM_VERSION,
+ .version_id = VIRTIO_SOUND_VM_VERSION,
+ .fields = (VMStateField[]) {
+ VMSTATE_VIRTIO_DEVICE,
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static Property virtio_snd_properties[] = {
+ DEFINE_AUDIO_PROPERTIES(VirtIOSound, card),
+ DEFINE_PROP_UINT32("jacks", VirtIOSound, snd_conf.jacks,
+ VIRTIO_SOUND_JACK_DEFAULT),
+ DEFINE_PROP_UINT32("streams", VirtIOSound, snd_conf.streams,
+ VIRTIO_SOUND_STREAM_DEFAULT),
+ DEFINE_PROP_UINT32("chmaps", VirtIOSound, snd_conf.chmaps,
+ VIRTIO_SOUND_CHMAP_DEFAULT),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void
+virtio_snd_get_config(VirtIODevice *vdev, uint8_t *config)
+{
+ VirtIOSound *s = VIRTIO_SND(vdev);
+ virtio_snd_config *sndconfig =
+ (virtio_snd_config *)config;
+ trace_virtio_snd_get_config(vdev,
+ s->snd_conf.jacks,
+ s->snd_conf.streams,
+ s->snd_conf.chmaps);
+
+ memcpy(sndconfig, &s->snd_conf, sizeof(s->snd_conf));
+ cpu_to_le32s(&sndconfig->jacks);
+ cpu_to_le32s(&sndconfig->streams);
+ cpu_to_le32s(&sndconfig->chmaps);
+
+}
+
+static void
+virtio_snd_set_config(VirtIODevice *vdev, const uint8_t *config)
+{
+ VirtIOSound *s = VIRTIO_SND(vdev);
+ const virtio_snd_config *sndconfig =
+ (const virtio_snd_config *)config;
+
+
+ trace_virtio_snd_set_config(vdev,
+ s->snd_conf.jacks,
+ sndconfig->jacks,
+ s->snd_conf.streams,
+ sndconfig->streams,
+ s->snd_conf.chmaps,
+ sndconfig->chmaps);
+
+ memcpy(&s->snd_conf, sndconfig, sizeof(virtio_snd_config));
+ le32_to_cpus(&s->snd_conf.jacks);
+ le32_to_cpus(&s->snd_conf.streams);
+ le32_to_cpus(&s->snd_conf.chmaps);
+
+}
+
+static void
+virtio_snd_pcm_buffer_free(VirtIOSoundPCMBuffer *buffer)
+{
+ g_free(buffer->elem);
+ g_free(buffer);
+}
+
+static void
+virtio_snd_ctrl_cmd_free(virtio_snd_ctrl_command *cmd)
+{
+ g_free(cmd->elem);
+ g_free(cmd);
+}
+
+/*
+ * Get a specific stream from the virtio sound card device.
+ * Returns NULL if @stream_id is invalid or not allocated.
+ *
+ * @s: VirtIOSound device
+ * @stream_id: stream id
+ */
+static VirtIOSoundPCMStream *virtio_snd_pcm_get_stream(VirtIOSound *s,
+ uint32_t stream_id)
+{
+ return stream_id >= s->snd_conf.streams ? NULL :
+ s->pcm->streams[stream_id];
+}
+
+/*
+ * Get params for a specific stream.
+ *
+ * @s: VirtIOSound device
+ * @stream_id: stream id
+ */
+static virtio_snd_pcm_set_params *virtio_snd_pcm_get_params(VirtIOSound *s,
+ uint32_t stream_id)
+{
+ return stream_id >= s->snd_conf.streams ? NULL
+ : &s->pcm->pcm_params[stream_id];
+}
+
+/*
+ * Handle the VIRTIO_SND_R_PCM_INFO request.
+ * The function writes the info structs to the request element.
+ *
+ * @s: VirtIOSound device
+ * @cmd: The request command queue element from VirtIOSound cmdq field
+ */
+static void virtio_snd_handle_pcm_info(VirtIOSound *s,
+ virtio_snd_ctrl_command *cmd)
+{
+ uint32_t stream_id, start_id, count, size;
+ virtio_snd_pcm_info val;
+ virtio_snd_query_info req;
+ VirtIOSoundPCMStream *stream = NULL;
+ g_autofree virtio_snd_pcm_info *pcm_info = NULL;
+ size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
+ cmd->elem->out_num,
+ 0,
+ &req,
+ sizeof(virtio_snd_query_info));
+
+ if (msg_sz != sizeof(virtio_snd_query_info)) {
+ /*
+ * TODO: do we need to set DEVICE_NEEDS_RESET?
+ */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: virtio-snd command size incorrect %zu vs \
+ %zu\n", __func__, msg_sz, sizeof(virtio_snd_query_info));
+ cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
+ return;
+ }
+
+ start_id = le32_to_cpu(req.start_id);
+ count = le32_to_cpu(req.count);
+ size = le32_to_cpu(req.size);
+
+ if (iov_size(cmd->elem->in_sg, cmd->elem->in_num) <
+ sizeof(virtio_snd_hdr) + size * count) {
+ /*
+ * TODO: do we need to set DEVICE_NEEDS_RESET?
+ */
+ error_report("pcm info: buffer too small, got: %zu, needed: %zu",
+ iov_size(cmd->elem->in_sg, cmd->elem->in_num),
+ sizeof(virtio_snd_pcm_info));
+ cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
+ return;
+ }
+
+ pcm_info = g_new0(virtio_snd_pcm_info, count);
+ for (uint32_t i = 0; i < count; i++) {
+ stream_id = i + start_id;
+ trace_virtio_snd_handle_pcm_info(stream_id);
+ stream = virtio_snd_pcm_get_stream(s, stream_id);
+ if (!stream) {
+ error_report("Invalid stream id: %"PRIu32, stream_id);
+ cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
+ return;
+ }
+ val = stream->info;
+ val.hdr.hda_fn_nid = cpu_to_le32(val.hdr.hda_fn_nid);
+ val.features = cpu_to_le32(val.features);
+ val.formats = cpu_to_le64(val.formats);
+ val.rates = cpu_to_le64(val.rates);
+ /*
+ * 5.14.6.6.2.1 Device Requirements: Stream Information The device MUST
+ * NOT set undefined feature, format, rate and direction values. The
+ * device MUST initialize the padding bytes to 0.
+ */
+ pcm_info[i] = val;
+ memset(&pcm_info[i].padding, 0, 5);
+ }
+
+ cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
+ iov_from_buf(cmd->elem->in_sg,
+ cmd->elem->in_num,
+ sizeof(virtio_snd_hdr),
+ pcm_info,
+ sizeof(virtio_snd_pcm_info) * count);
+}
+
+/*
+ * Set the given stream params.
+ * Called by both virtio_snd_handle_pcm_set_params and during device
+ * initialization.
+ * Returns the response status code. (VIRTIO_SND_S_*).
+ *
+ * @s: VirtIOSound device
+ * @params: The PCM params as defined in the virtio specification
+ */
+static
+uint32_t virtio_snd_set_pcm_params(VirtIOSound *s,
+ uint32_t stream_id,
+ virtio_snd_pcm_set_params *params)
+{
+ virtio_snd_pcm_set_params *st_params;
+
+ if (stream_id >= s->snd_conf.streams || s->pcm->pcm_params == NULL) {
+ /*
+ * TODO: do we need to set DEVICE_NEEDS_RESET?
+ */
+ virtio_error(VIRTIO_DEVICE(s), "Streams have not been initialized.\n");
+ return cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
+ }
+
+ st_params = virtio_snd_pcm_get_params(s, stream_id);
+
+ if (params->channels < 1 || params->channels > AUDIO_MAX_CHANNELS) {
+ error_report("Number of channels is not supported.");
+ return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
+ }
+ if (!(supported_formats & BIT(params->format))) {
+ error_report("Stream format is not supported.");
+ return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
+ }
+ if (!(supported_rates & BIT(params->rate))) {
+ error_report("Stream rate is not supported.");
+ return cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
+ }
+
+ st_params->buffer_bytes = le32_to_cpu(params->buffer_bytes);
+ st_params->period_bytes = le32_to_cpu(params->period_bytes);
+ st_params->features = le32_to_cpu(params->features);
+ /* the following are uint8_t, so there's no need to bswap the values. */
+ st_params->channels = params->channels;
+ st_params->format = params->format;
+ st_params->rate = params->rate;
+
+ return cpu_to_le32(VIRTIO_SND_S_OK);
+}
+
+/*
+ * Handles the VIRTIO_SND_R_PCM_SET_PARAMS request.
+ *
+ * @s: VirtIOSound device
+ * @cmd: The request command queue element from VirtIOSound cmdq field
+ */
+static void virtio_snd_handle_pcm_set_params(VirtIOSound *s,
+ virtio_snd_ctrl_command *cmd)
+{
+ virtio_snd_pcm_set_params req = { 0 };
+ uint32_t stream_id;
+ size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
+ cmd->elem->out_num,
+ 0,
+ &req,
+ sizeof(virtio_snd_pcm_set_params));
+
+ if (msg_sz != sizeof(virtio_snd_pcm_set_params)) {
+ /*
+ * TODO: do we need to set DEVICE_NEEDS_RESET?
+ */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: virtio-snd command size incorrect %zu vs \
+ %zu\n", __func__, msg_sz, sizeof(virtio_snd_pcm_set_params));
+ cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
+ return;
+ }
+ stream_id = le32_to_cpu(req.hdr.stream_id);
+ trace_virtio_snd_handle_pcm_set_params(stream_id);
+ cmd->resp.code = virtio_snd_set_pcm_params(s, stream_id, &req);
+}
+
+/*
+ * Get a QEMU Audiosystem compatible format value from a VIRTIO_SND_PCM_FMT_*
+ */
+static AudioFormat virtio_snd_get_qemu_format(uint32_t format)
+{
+ #define CASE(FMT) \
+ case VIRTIO_SND_PCM_FMT_##FMT: \
+ return AUDIO_FORMAT_##FMT;
+
+ switch (format) {
+ CASE(U8)
+ CASE(S8)
+ CASE(U16)
+ CASE(S16)
+ CASE(U32)
+ CASE(S32)
+ case VIRTIO_SND_PCM_FMT_FLOAT:
+ return AUDIO_FORMAT_F32;
+ default:
+ g_assert_not_reached();
+ }
+
+ #undef CASE
+}
+
+/*
+ * Get a QEMU Audiosystem compatible frequency value from a
+ * VIRTIO_SND_PCM_RATE_*
+ */
+static uint32_t virtio_snd_get_qemu_freq(uint32_t rate)
+{
+ #define CASE(RATE) \
+ case VIRTIO_SND_PCM_RATE_##RATE: \
+ return RATE;
+
+ switch (rate) {
+ CASE(5512)
+ CASE(8000)
+ CASE(11025)
+ CASE(16000)
+ CASE(22050)
+ CASE(32000)
+ CASE(44100)
+ CASE(48000)
+ CASE(64000)
+ CASE(88200)
+ CASE(96000)
+ CASE(176400)
+ CASE(192000)
+ CASE(384000)
+ default:
+ g_assert_not_reached();
+ }
+
+ #undef CASE
+}
+
+/*
+ * Get QEMU Audiosystem compatible audsettings from virtio based pcm stream
+ * params.
+ */
+static void virtio_snd_get_qemu_audsettings(audsettings *as,
+ virtio_snd_pcm_set_params *params)
+{
+ as->nchannels = MIN(AUDIO_MAX_CHANNELS, params->channels);
+ as->fmt = virtio_snd_get_qemu_format(params->format);
+ as->freq = virtio_snd_get_qemu_freq(params->rate);
+ as->endianness = target_words_bigendian() ? 1 : 0;
+}
+
+/*
+ * Close a stream and free all its resources.
+ *
+ * @stream: VirtIOSoundPCMStream *stream
+ */
+static void virtio_snd_pcm_close(VirtIOSoundPCMStream *stream)
+{
+ if (stream) {
+ virtio_snd_pcm_flush(stream);
+ if (stream->info.direction == VIRTIO_SND_D_OUTPUT) {
+ AUD_close_out(&stream->pcm->snd->card, stream->voice.out);
+ stream->voice.out = NULL;
+ } else if (stream->info.direction == VIRTIO_SND_D_INPUT) {
+ AUD_close_in(&stream->pcm->snd->card, stream->voice.in);
+ stream->voice.in = NULL;
+ }
+ }
+}
+
+/*
+ * Prepares a VirtIOSound card stream.
+ * Returns the response status code. (VIRTIO_SND_S_*).
+ *
+ * @s: VirtIOSound device
+ * @stream_id: stream id
+ */
+static uint32_t virtio_snd_pcm_prepare(VirtIOSound *s, uint32_t stream_id)
+{
+ audsettings as;
+ virtio_snd_pcm_set_params *params;
+ VirtIOSoundPCMStream *stream;
+
+ if (s->pcm->streams == NULL ||
+ s->pcm->pcm_params == NULL ||
+ stream_id >= s->snd_conf.streams) {
+ return cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
+ }
+
+ params = virtio_snd_pcm_get_params(s, stream_id);
+ if (params == NULL) {
+ return cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
+ }
+
+ stream = virtio_snd_pcm_get_stream(s, stream_id);
+ if (stream == NULL) {
+ stream = g_new0(VirtIOSoundPCMStream, 1);
+ stream->active = false;
+ stream->id = stream_id;
+ stream->pcm = s->pcm;
+ stream->s = s;
+ qemu_mutex_init(&stream->queue_mutex);
+ QSIMPLEQ_INIT(&stream->queue);
+ QSIMPLEQ_INIT(&stream->invalid);
+
+ /*
+ * stream_id >= s->snd_conf.streams was checked before so this is
+ * in-bounds
+ */
+ s->pcm->streams[stream_id] = stream;
+ }
+
+ virtio_snd_get_qemu_audsettings(&as, params);
+ stream->info.direction = stream_id < s->snd_conf.streams / 2 +
+ (s->snd_conf.streams & 1) ? VIRTIO_SND_D_OUTPUT : VIRTIO_SND_D_INPUT;
+ stream->info.hdr.hda_fn_nid = VIRTIO_SOUND_HDA_FN_NID;
+ stream->info.features = 0;
+ stream->info.channels_min = 1;
+ stream->info.channels_max = as.nchannels;
+ stream->info.formats = supported_formats;
+ stream->info.rates = supported_rates;
+ stream->params = *params;
+
+ stream->positions[0] = VIRTIO_SND_CHMAP_FL;
+ stream->positions[1] = VIRTIO_SND_CHMAP_FR;
+ stream->as = as;
+
+ if (stream->info.direction == VIRTIO_SND_D_OUTPUT) {
+ stream->voice.out = AUD_open_out(&s->card,
+ stream->voice.out,
+ "virtio-sound.out",
+ stream,
+ virtio_snd_pcm_out_cb,
+ &as);
+ AUD_set_volume_out(stream->voice.out, 0, 255, 255);
+ } else {
+ stream->voice.in = AUD_open_in(&s->card,
+ stream->voice.in,
+ "virtio-sound.in",
+ stream,
+ virtio_snd_pcm_in_cb,
+ &as);
+ AUD_set_volume_in(stream->voice.in, 0, 255, 255);
+ }
+
+ return cpu_to_le32(VIRTIO_SND_S_OK);
+}
+
+static const char *print_code(uint32_t code)
+{
+ #define CASE(CODE) \
+ case VIRTIO_SND_R_##CODE: \
+ return "VIRTIO_SND_R_"#CODE
+
+ switch (code) {
+ CASE(JACK_INFO);
+ CASE(JACK_REMAP);
+ CASE(PCM_INFO);
+ CASE(PCM_SET_PARAMS);
+ CASE(PCM_PREPARE);
+ CASE(PCM_RELEASE);
+ CASE(PCM_START);
+ CASE(PCM_STOP);
+ CASE(CHMAP_INFO);
+ default:
+ return "invalid code";
+ }
+
+ #undef CASE
+};
+
+/*
+ * Handles VIRTIO_SND_R_PCM_PREPARE.
+ *
+ * @s: VirtIOSound device
+ * @cmd: The request command queue element from VirtIOSound cmdq field
+ */
+static void virtio_snd_handle_pcm_prepare(VirtIOSound *s,
+ virtio_snd_ctrl_command *cmd)
+{
+ uint32_t stream_id;
+ size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
+ cmd->elem->out_num,
+ sizeof(virtio_snd_hdr),
+ &stream_id,
+ sizeof(stream_id));
+
+ stream_id = le32_to_cpu(stream_id);
+ cmd->resp.code = msg_sz == sizeof(stream_id)
+ ? virtio_snd_pcm_prepare(s, stream_id)
+ : cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
+}
+
+/*
+ * Handles VIRTIO_SND_R_PCM_START.
+ *
+ * @s: VirtIOSound device
+ * @cmd: The request command queue element from VirtIOSound cmdq field
+ * @start: whether to start or stop the device
+ */
+static void virtio_snd_handle_pcm_start_stop(VirtIOSound *s,
+ virtio_snd_ctrl_command *cmd,
+ bool start)
+{
+ VirtIOSoundPCMStream *stream;
+ virtio_snd_pcm_hdr req;
+ uint32_t stream_id;
+ size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
+ cmd->elem->out_num,
+ 0,
+ &req,
+ sizeof(virtio_snd_pcm_hdr));
+
+ if (msg_sz != sizeof(virtio_snd_pcm_hdr)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: virtio-snd command size incorrect %zu vs \
+ %zu\n", __func__, msg_sz, sizeof(virtio_snd_pcm_hdr));
+ cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
+ return;
+ }
+
+ stream_id = le32_to_cpu(req.stream_id);
+ cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
+ trace_virtio_snd_handle_pcm_start_stop(start ? "VIRTIO_SND_R_PCM_START" :
+ "VIRTIO_SND_R_PCM_STOP", stream_id);
+
+ stream = virtio_snd_pcm_get_stream(s, stream_id);
+ if (stream) {
+ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+ stream->active = start;
+ }
+ if (stream->info.direction == VIRTIO_SND_D_OUTPUT) {
+ AUD_set_active_out(stream->voice.out, start);
+ } else {
+ AUD_set_active_in(stream->voice.in, start);
+ }
+ } else {
+ error_report("Invalid stream id: %"PRIu32, stream_id);
+ cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
+ return;
+ }
+ stream->active = start;
+}
+
+/*
+ * Returns the number of I/O messages that are being processed.
+ *
+ * @stream: VirtIOSoundPCMStream
+ */
+static size_t virtio_snd_pcm_get_io_msgs_count(VirtIOSoundPCMStream *stream)
+{
+ VirtIOSoundPCMBuffer *buffer, *next;
+ size_t count = 0;
+
+ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+ QSIMPLEQ_FOREACH_SAFE(buffer, &stream->queue, entry, next) {
+ count += 1;
+ }
+ QSIMPLEQ_FOREACH_SAFE(buffer, &stream->invalid, entry, next) {
+ count += 1;
+ }
+ }
+ return count;
+}
+
+/*
+ * Handles VIRTIO_SND_R_PCM_RELEASE.
+ *
+ * @s: VirtIOSound device
+ * @cmd: The request command queue element from VirtIOSound cmdq field
+ */
+static void virtio_snd_handle_pcm_release(VirtIOSound *s,
+ virtio_snd_ctrl_command *cmd)
+{
+ uint32_t stream_id;
+ VirtIOSoundPCMStream *stream;
+ size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
+ cmd->elem->out_num,
+ sizeof(virtio_snd_hdr),
+ &stream_id,
+ sizeof(stream_id));
+
+ if (msg_sz != sizeof(stream_id)) {
+ /*
+ * TODO: do we need to set DEVICE_NEEDS_RESET?
+ */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: virtio-snd command size incorrect %zu vs \
+ %zu\n", __func__, msg_sz, sizeof(stream_id));
+ cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
+ return;
+ }
+
+ stream_id = le32_to_cpu(stream_id);
+ trace_virtio_snd_handle_pcm_release(stream_id);
+ stream = virtio_snd_pcm_get_stream(s, stream_id);
+ if (stream == NULL) {
+ /*
+ * TODO: do we need to set DEVICE_NEEDS_RESET?
+ */
+ error_report("already released stream %"PRIu32, stream_id);
+ virtio_error(VIRTIO_DEVICE(s),
+ "already released stream %"PRIu32,
+ stream_id);
+ cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
+ return;
+ }
+
+ if (virtio_snd_pcm_get_io_msgs_count(stream)) {
+ /*
+ * virtio-v1.2-csd01, 5.14.6.6.5.1,
+ * Device Requirements: Stream Release
+ *
+ * - The device MUST complete all pending I/O messages for the
+ * specified stream ID.
+ * - The device MUST NOT complete the control request while there
+ * are pending I/O messages for the specified stream ID.
+ */
+ trace_virtio_snd_pcm_stream_flush(stream_id);
+ virtio_snd_pcm_flush(stream);
+ }
+
+ cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
+}
+
+/*
+ * The actual processing done in virtio_snd_process_cmdq().
+ *
+ * @s: VirtIOSound device
+ * @cmd: control command request
+ */
+static inline void
+process_cmd(VirtIOSound *s, virtio_snd_ctrl_command *cmd)
+{
+ uint32_t code;
+ size_t msg_sz = iov_to_buf(cmd->elem->out_sg,
+ cmd->elem->out_num,
+ 0,
+ &cmd->ctrl,
+ sizeof(virtio_snd_hdr));
+
+ if (msg_sz != sizeof(virtio_snd_hdr)) {
+ /*
+ * TODO: do we need to set DEVICE_NEEDS_RESET?
+ */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: virtio-snd command size incorrect %zu vs \
+ %zu\n", __func__, msg_sz, sizeof(virtio_snd_hdr));
+ return;
+ }
+
+ code = le32_to_cpu(cmd->ctrl.code);
+
+ trace_virtio_snd_handle_code(code, print_code(code));
+
+ switch (code) {
+ case VIRTIO_SND_R_JACK_INFO:
+ case VIRTIO_SND_R_JACK_REMAP:
+ qemu_log_mask(LOG_UNIMP,
+ "virtio_snd: jack functionality is unimplemented.\n");
+ cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
+ break;
+ case VIRTIO_SND_R_PCM_INFO:
+ virtio_snd_handle_pcm_info(s, cmd);
+ break;
+ case VIRTIO_SND_R_PCM_START:
+ virtio_snd_handle_pcm_start_stop(s, cmd, true);
+ break;
+ case VIRTIO_SND_R_PCM_STOP:
+ virtio_snd_handle_pcm_start_stop(s, cmd, false);
+ break;
+ case VIRTIO_SND_R_PCM_SET_PARAMS:
+ virtio_snd_handle_pcm_set_params(s, cmd);
+ break;
+ case VIRTIO_SND_R_PCM_PREPARE:
+ virtio_snd_handle_pcm_prepare(s, cmd);
+ break;
+ case VIRTIO_SND_R_PCM_RELEASE:
+ virtio_snd_handle_pcm_release(s, cmd);
+ break;
+ case VIRTIO_SND_R_CHMAP_INFO:
+ qemu_log_mask(LOG_UNIMP,
+ "virtio_snd: chmap info functionality is unimplemented.\n");
+ trace_virtio_snd_handle_chmap_info();
+ cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_NOT_SUPP);
+ break;
+ default:
+ /* error */
+ error_report("virtio snd header not recognized: %"PRIu32, code);
+ cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
+ }
+
+ iov_from_buf(cmd->elem->in_sg,
+ cmd->elem->in_num,
+ 0,
+ &cmd->resp,
+ sizeof(virtio_snd_hdr));
+ virtqueue_push(cmd->vq, cmd->elem, sizeof(virtio_snd_hdr));
+ virtio_notify(VIRTIO_DEVICE(s), cmd->vq);
+}
+
+/*
+ * Consume all elements in command queue.
+ *
+ * @s: VirtIOSound device
+ */
+static void virtio_snd_process_cmdq(VirtIOSound *s)
+{
+ virtio_snd_ctrl_command *cmd;
+
+ if (unlikely(qatomic_read(&s->processing_cmdq))) {
+ return;
+ }
+
+ WITH_QEMU_LOCK_GUARD(&s->cmdq_mutex) {
+ qatomic_set(&s->processing_cmdq, true);
+ while (!QTAILQ_EMPTY(&s->cmdq)) {
+ cmd = QTAILQ_FIRST(&s->cmdq);
+
+ /* process command */
+ process_cmd(s, cmd);
+
+ QTAILQ_REMOVE(&s->cmdq, cmd, next);
+
+ virtio_snd_ctrl_cmd_free(cmd);
+ }
+ qatomic_set(&s->processing_cmdq, false);
+ }
+}
+
+/*
+ * The control message handler. Pops an element from the control virtqueue,
+ * and stores them to VirtIOSound's cmdq queue and finally calls
+ * virtio_snd_process_cmdq() for processing.
+ *
+ * @vdev: VirtIOSound device
+ * @vq: Control virtqueue
+ */
+static void virtio_snd_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOSound *s = VIRTIO_SND(vdev);
+ VirtQueueElement *elem;
+ virtio_snd_ctrl_command *cmd;
+
+ trace_virtio_snd_handle_ctrl(vdev, vq);
+
+ if (!virtio_queue_ready(vq)) {
+ return;
+ }
+
+ elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
+ while (elem) {
+ cmd = g_new0(virtio_snd_ctrl_command, 1);
+ cmd->elem = elem;
+ cmd->vq = vq;
+ cmd->resp.code = cpu_to_le32(VIRTIO_SND_S_OK);
+ QTAILQ_INSERT_TAIL(&s->cmdq, cmd, next);
+ elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
+ }
+
+ virtio_snd_process_cmdq(s);
+}
+
+/*
+ * The event virtqueue handler.
+ * Not implemented yet.
+ *
+ * @vdev: VirtIOSound device
+ * @vq: event vq
+ */
+static void virtio_snd_handle_event(VirtIODevice *vdev, VirtQueue *vq)
+{
+ qemu_log_mask(LOG_UNIMP, "virtio_snd: event queue is unimplemented.\n");
+ trace_virtio_snd_handle_event();
+}
+
+static inline void empty_invalid_queue(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOSoundPCMBuffer *buffer = NULL;
+ VirtIOSoundPCMStream *stream = NULL;
+ virtio_snd_pcm_status resp = { 0 };
+ VirtIOSound *vsnd = VIRTIO_SND(vdev);
+ bool any = false;
+
+ for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) {
+ stream = vsnd->pcm->streams[i];
+ if (stream) {
+ any = false;
+ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+ while (!QSIMPLEQ_EMPTY(&stream->invalid)) {
+ buffer = QSIMPLEQ_FIRST(&stream->invalid);
+ if (buffer->vq != vq) {
+ break;
+ }
+ any = true;
+ resp.status = cpu_to_le32(VIRTIO_SND_S_BAD_MSG);
+ iov_from_buf(buffer->elem->in_sg,
+ buffer->elem->in_num,
+ 0,
+ &resp,
+ sizeof(virtio_snd_pcm_status));
+ virtqueue_push(vq,
+ buffer->elem,
+ sizeof(virtio_snd_pcm_status));
+ QSIMPLEQ_REMOVE_HEAD(&stream->invalid, entry);
+ virtio_snd_pcm_buffer_free(buffer);
+ }
+ if (any) {
+ /*
+ * Notify vq about virtio_snd_pcm_status responses.
+ * Buffer responses must be notified separately later.
+ */
+ virtio_notify(vdev, vq);
+ }
+ }
+ }
+ }
+}
+
+/*
+ * The tx virtqueue handler. Makes the buffers available to their respective
+ * streams for consumption.
+ *
+ * @vdev: VirtIOSound device
+ * @vq: tx virtqueue
+ */
+static void virtio_snd_handle_tx_xfer(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOSound *s = VIRTIO_SND(vdev);
+ VirtIOSoundPCMStream *stream = NULL;
+ VirtIOSoundPCMBuffer *buffer;
+ VirtQueueElement *elem;
+ size_t msg_sz, size;
+ virtio_snd_pcm_xfer hdr;
+ uint32_t stream_id;
+ /*
+ * If any of the I/O messages are invalid, put them in stream->invalid and
+ * return them after the for loop.
+ */
+ bool must_empty_invalid_queue = false;
+
+ if (!virtio_queue_ready(vq)) {
+ return;
+ }
+ trace_virtio_snd_handle_tx_xfer();
+
+ for (;;) {
+ elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
+ if (!elem) {
+ break;
+ }
+ /* get the message hdr object */
+ msg_sz = iov_to_buf(elem->out_sg,
+ elem->out_num,
+ 0,
+ &hdr,
+ sizeof(virtio_snd_pcm_xfer));
+ if (msg_sz != sizeof(virtio_snd_pcm_xfer)) {
+ goto tx_err;
+ }
+ stream_id = le32_to_cpu(hdr.stream_id);
+
+ if (stream_id >= s->snd_conf.streams
+ || s->pcm->streams[stream_id] == NULL) {
+ goto tx_err;
+ }
+
+ stream = s->pcm->streams[stream_id];
+ if (stream->info.direction != VIRTIO_SND_D_OUTPUT) {
+ goto tx_err;
+ }
+
+ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+ size = iov_size(elem->out_sg, elem->out_num) - msg_sz;
+
+ buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size);
+ buffer->elem = elem;
+ buffer->populated = false;
+ buffer->vq = vq;
+ buffer->size = size;
+ buffer->offset = 0;
+
+ QSIMPLEQ_INSERT_TAIL(&stream->queue, buffer, entry);
+ }
+ continue;
+
+tx_err:
+ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+ must_empty_invalid_queue = true;
+ buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer));
+ buffer->elem = elem;
+ buffer->vq = vq;
+ QSIMPLEQ_INSERT_TAIL(&stream->invalid, buffer, entry);
+ }
+ }
+
+ if (must_empty_invalid_queue) {
+ empty_invalid_queue(vdev, vq);
+ }
+}
+
+/*
+ * The rx virtqueue handler. Makes the buffers available to their respective
+ * streams for consumption.
+ *
+ * @vdev: VirtIOSound device
+ * @vq: rx virtqueue
+ */
+static void virtio_snd_handle_rx_xfer(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOSound *s = VIRTIO_SND(vdev);
+ VirtIOSoundPCMStream *stream = NULL;
+ VirtIOSoundPCMBuffer *buffer;
+ VirtQueueElement *elem;
+ size_t msg_sz, size;
+ virtio_snd_pcm_xfer hdr;
+ uint32_t stream_id;
+ /*
+ * if any of the I/O messages are invalid, put them in stream->invalid and
+ * return them after the for loop.
+ */
+ bool must_empty_invalid_queue = false;
+
+ if (!virtio_queue_ready(vq)) {
+ return;
+ }
+ trace_virtio_snd_handle_rx_xfer();
+
+ for (;;) {
+ elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
+ if (!elem) {
+ break;
+ }
+ /* get the message hdr object */
+ msg_sz = iov_to_buf(elem->out_sg,
+ elem->out_num,
+ 0,
+ &hdr,
+ sizeof(virtio_snd_pcm_xfer));
+ if (msg_sz != sizeof(virtio_snd_pcm_xfer)) {
+ goto rx_err;
+ }
+ stream_id = le32_to_cpu(hdr.stream_id);
+
+ if (stream_id >= s->snd_conf.streams
+ || !s->pcm->streams[stream_id]) {
+ goto rx_err;
+ }
+
+ stream = s->pcm->streams[stream_id];
+ if (stream == NULL || stream->info.direction != VIRTIO_SND_D_INPUT) {
+ goto rx_err;
+ }
+ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+ size = iov_size(elem->in_sg, elem->in_num) -
+ sizeof(virtio_snd_pcm_status);
+ buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer) + size);
+ buffer->elem = elem;
+ buffer->vq = vq;
+ buffer->size = 0;
+ buffer->offset = 0;
+ QSIMPLEQ_INSERT_TAIL(&stream->queue, buffer, entry);
+ }
+ continue;
+
+rx_err:
+ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+ must_empty_invalid_queue = true;
+ buffer = g_malloc0(sizeof(VirtIOSoundPCMBuffer));
+ buffer->elem = elem;
+ buffer->vq = vq;
+ QSIMPLEQ_INSERT_TAIL(&stream->invalid, buffer, entry);
+ }
+ }
+
+ if (must_empty_invalid_queue) {
+ empty_invalid_queue(vdev, vq);
+ }
+}
+
+static uint64_t get_features(VirtIODevice *vdev, uint64_t features,
+ Error **errp)
+{
+ /*
+ * virtio-v1.2-csd01, 5.14.3,
+ * Feature Bits
+ * None currently defined.
+ */
+ VirtIOSound *s = VIRTIO_SND(vdev);
+ features |= s->features;
+
+ trace_virtio_snd_get_features(vdev, features);
+
+ return features;
+}
+
+static void
+virtio_snd_vm_state_change(void *opaque, bool running,
+ RunState state)
+{
+ if (running) {
+ trace_virtio_snd_vm_state_running();
+ } else {
+ trace_virtio_snd_vm_state_stopped();
+ }
+}
+
+static void virtio_snd_realize(DeviceState *dev, Error **errp)
+{
+ ERRP_GUARD();
+ VirtIOSound *vsnd = VIRTIO_SND(dev);
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ virtio_snd_pcm_set_params default_params = { 0 };
+ uint32_t status;
+
+ vsnd->pcm = NULL;
+ vsnd->vmstate =
+ qemu_add_vm_change_state_handler(virtio_snd_vm_state_change, vsnd);
+
+ trace_virtio_snd_realize(vsnd);
+
+ vsnd->pcm = g_new0(VirtIOSoundPCM, 1);
+ vsnd->pcm->snd = vsnd;
+ vsnd->pcm->streams =
+ g_new0(VirtIOSoundPCMStream *, vsnd->snd_conf.streams);
+ vsnd->pcm->pcm_params =
+ g_new0(virtio_snd_pcm_set_params, vsnd->snd_conf.streams);
+
+ virtio_init(vdev, VIRTIO_ID_SOUND, sizeof(virtio_snd_config));
+ virtio_add_feature(&vsnd->features, VIRTIO_F_VERSION_1);
+
+ /* set number of jacks and streams */
+ if (vsnd->snd_conf.jacks > 8) {
+ error_setg(errp,
+ "Invalid number of jacks: %"PRIu32,
+ vsnd->snd_conf.jacks);
+ return;
+ }
+ if (vsnd->snd_conf.streams < 1 || vsnd->snd_conf.streams > 10) {
+ error_setg(errp,
+ "Invalid number of streams: %"PRIu32,
+ vsnd->snd_conf.streams);
+ return;
+ }
+
+ if (vsnd->snd_conf.chmaps > VIRTIO_SND_CHMAP_MAX_SIZE) {
+ error_setg(errp,
+ "Invalid number of channel maps: %"PRIu32,
+ vsnd->snd_conf.chmaps);
+ return;
+ }
+
+ AUD_register_card("virtio-sound", &vsnd->card, errp);
+
+ /* set default params for all streams */
+ default_params.features = 0;
+ default_params.buffer_bytes = cpu_to_le32(8192);
+ default_params.period_bytes = cpu_to_le32(2048);
+ default_params.channels = 2;
+ default_params.format = VIRTIO_SND_PCM_FMT_S16;
+ default_params.rate = VIRTIO_SND_PCM_RATE_48000;
+ vsnd->queues[VIRTIO_SND_VQ_CONTROL] =
+ virtio_add_queue(vdev, 64, virtio_snd_handle_ctrl);
+ vsnd->queues[VIRTIO_SND_VQ_EVENT] =
+ virtio_add_queue(vdev, 64, virtio_snd_handle_event);
+ vsnd->queues[VIRTIO_SND_VQ_TX] =
+ virtio_add_queue(vdev, 64, virtio_snd_handle_tx_xfer);
+ vsnd->queues[VIRTIO_SND_VQ_RX] =
+ virtio_add_queue(vdev, 64, virtio_snd_handle_rx_xfer);
+ qemu_mutex_init(&vsnd->cmdq_mutex);
+ QTAILQ_INIT(&vsnd->cmdq);
+
+ for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) {
+ status = virtio_snd_set_pcm_params(vsnd, i, &default_params);
+ if (status != cpu_to_le32(VIRTIO_SND_S_OK)) {
+ error_setg(errp,
+ "Can't initalize stream params, device responded with %s.",
+ print_code(status));
+ return;
+ }
+ status = virtio_snd_pcm_prepare(vsnd, i);
+ if (status != cpu_to_le32(VIRTIO_SND_S_OK)) {
+ error_setg(errp,
+ "Can't prepare streams, device responded with %s.",
+ print_code(status));
+ return;
+ }
+ }
+}
+
+static inline void return_tx_buffer(VirtIOSoundPCMStream *stream,
+ VirtIOSoundPCMBuffer *buffer)
+{
+ virtio_snd_pcm_status resp = { 0 };
+ resp.status = cpu_to_le32(VIRTIO_SND_S_OK);
+ resp.latency_bytes = cpu_to_le32((uint32_t)buffer->size);
+ iov_from_buf(buffer->elem->in_sg,
+ buffer->elem->in_num,
+ 0,
+ &resp,
+ sizeof(virtio_snd_pcm_status));
+ virtqueue_push(buffer->vq,
+ buffer->elem,
+ sizeof(virtio_snd_pcm_status));
+ virtio_notify(VIRTIO_DEVICE(stream->s), buffer->vq);
+ QSIMPLEQ_REMOVE(&stream->queue,
+ buffer,
+ VirtIOSoundPCMBuffer,
+ entry);
+ virtio_snd_pcm_buffer_free(buffer);
+}
+
+/*
+ * AUD_* output callback.
+ *
+ * @data: VirtIOSoundPCMStream stream
+ * @available: number of bytes that can be written with AUD_write()
+ */
+static void virtio_snd_pcm_out_cb(void *data, int available)
+{
+ VirtIOSoundPCMStream *stream = data;
+ VirtIOSoundPCMBuffer *buffer;
+ size_t size;
+
+ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+ while (!QSIMPLEQ_EMPTY(&stream->queue)) {
+ buffer = QSIMPLEQ_FIRST(&stream->queue);
+ if (!virtio_queue_ready(buffer->vq)) {
+ return;
+ }
+ if (!stream->active) {
+ /* Stream has stopped, so do not perform AUD_write. */
+ return_tx_buffer(stream, buffer);
+ continue;
+ }
+ if (!buffer->populated) {
+ iov_to_buf(buffer->elem->out_sg,
+ buffer->elem->out_num,
+ sizeof(virtio_snd_pcm_xfer),
+ buffer->data,
+ buffer->size);
+ buffer->populated = true;
+ }
+ for (;;) {
+ size = AUD_write(stream->voice.out,
+ buffer->data + buffer->offset,
+ MIN(buffer->size, available));
+ assert(size <= MIN(buffer->size, available));
+ if (size == 0) {
+ /* break out of both loops */
+ available = 0;
+ break;
+ }
+ buffer->size -= size;
+ buffer->offset += size;
+ available -= size;
+ if (buffer->size < 1) {
+ return_tx_buffer(stream, buffer);
+ break;
+ }
+ if (!available) {
+ break;
+ }
+ }
+ if (!available) {
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * Flush all buffer data from this input stream's queue into the driver's
+ * virtual queue.
+ *
+ * @stream: VirtIOSoundPCMStream *stream
+ */
+static inline void return_rx_buffer(VirtIOSoundPCMStream *stream,
+ VirtIOSoundPCMBuffer *buffer)
+{
+ virtio_snd_pcm_status resp = { 0 };
+ resp.status = cpu_to_le32(VIRTIO_SND_S_OK);
+ resp.latency_bytes = 0;
+ /* Copy data -if any- to guest */
+ iov_from_buf(buffer->elem->in_sg,
+ buffer->elem->in_num,
+ 0,
+ buffer->data,
+ buffer->size);
+ iov_from_buf(buffer->elem->in_sg,
+ buffer->elem->in_num,
+ buffer->size,
+ &resp,
+ sizeof(virtio_snd_pcm_status));
+ virtqueue_push(buffer->vq,
+ buffer->elem,
+ sizeof(virtio_snd_pcm_status) + buffer->size);
+ virtio_notify(VIRTIO_DEVICE(stream->s), buffer->vq);
+ QSIMPLEQ_REMOVE(&stream->queue,
+ buffer,
+ VirtIOSoundPCMBuffer,
+ entry);
+ virtio_snd_pcm_buffer_free(buffer);
+}
+
+
+/*
+ * AUD_* input callback.
+ *
+ * @data: VirtIOSoundPCMStream stream
+ * @available: number of bytes that can be read with AUD_read()
+ */
+static void virtio_snd_pcm_in_cb(void *data, int available)
+{
+ VirtIOSoundPCMStream *stream = data;
+ VirtIOSoundPCMBuffer *buffer;
+ size_t size;
+
+ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+ while (!QSIMPLEQ_EMPTY(&stream->queue)) {
+ buffer = QSIMPLEQ_FIRST(&stream->queue);
+ if (!virtio_queue_ready(buffer->vq)) {
+ return;
+ }
+ if (!stream->active) {
+ /* Stream has stopped, so do not perform AUD_read. */
+ return_rx_buffer(stream, buffer);
+ continue;
+ }
+
+ for (;;) {
+ size = AUD_read(stream->voice.in,
+ buffer->data + buffer->size,
+ MIN(available, (stream->params.period_bytes -
+ buffer->size)));
+ if (!size) {
+ available = 0;
+ break;
+ }
+ buffer->size += size;
+ available -= size;
+ if (buffer->size >= stream->params.period_bytes) {
+ return_rx_buffer(stream, buffer);
+ break;
+ }
+ if (!available) {
+ break;
+ }
+ }
+ if (!available) {
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * Flush all buffer data from this output stream's queue into the driver's
+ * virtual queue.
+ *
+ * @stream: VirtIOSoundPCMStream *stream
+ */
+static inline void virtio_snd_pcm_flush(VirtIOSoundPCMStream *stream)
+{
+ VirtIOSoundPCMBuffer *buffer;
+ void (*cb)(VirtIOSoundPCMStream *, VirtIOSoundPCMBuffer *) =
+ (stream->info.direction == VIRTIO_SND_D_OUTPUT) ? return_tx_buffer :
+ return_rx_buffer;
+
+ WITH_QEMU_LOCK_GUARD(&stream->queue_mutex) {
+ while (!QSIMPLEQ_EMPTY(&stream->queue)) {
+ buffer = QSIMPLEQ_FIRST(&stream->queue);
+ cb(stream, buffer);
+ }
+ }
+}
+
+static void virtio_snd_unrealize(DeviceState *dev)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIOSound *vsnd = VIRTIO_SND(dev);
+ VirtIOSoundPCMStream *stream;
+
+ qemu_del_vm_change_state_handler(vsnd->vmstate);
+ trace_virtio_snd_unrealize(vsnd);
+
+ if (vsnd->pcm) {
+ if (vsnd->pcm->streams) {
+ for (uint32_t i = 0; i < vsnd->snd_conf.streams; i++) {
+ stream = vsnd->pcm->streams[i];
+ if (stream) {
+ virtio_snd_process_cmdq(stream->s);
+ virtio_snd_pcm_close(stream);
+ qemu_mutex_destroy(&stream->queue_mutex);
+ g_free(stream);
+ }
+ }
+ g_free(vsnd->pcm->streams);
+ }
+ g_free(vsnd->pcm->pcm_params);
+ g_free(vsnd->pcm);
+ vsnd->pcm = NULL;
+ }
+ AUD_remove_card(&vsnd->card);
+ qemu_mutex_destroy(&vsnd->cmdq_mutex);
+ virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_CONTROL]);
+ virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_EVENT]);
+ virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_TX]);
+ virtio_delete_queue(vsnd->queues[VIRTIO_SND_VQ_RX]);
+ virtio_cleanup(vdev);
+}
+
+
+static void virtio_snd_reset(VirtIODevice *vdev)
+{
+ VirtIOSound *s = VIRTIO_SND(vdev);
+ virtio_snd_ctrl_command *cmd;
+
+ WITH_QEMU_LOCK_GUARD(&s->cmdq_mutex) {
+ while (!QTAILQ_EMPTY(&s->cmdq)) {
+ cmd = QTAILQ_FIRST(&s->cmdq);
+ QTAILQ_REMOVE(&s->cmdq, cmd, next);
+ virtio_snd_ctrl_cmd_free(cmd);
+ }
+ }
+}
+
+static void virtio_snd_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+
+
+ set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
+ device_class_set_props(dc, virtio_snd_properties);
+
+ dc->vmsd = &vmstate_virtio_snd;
+ vdc->vmsd = &vmstate_virtio_snd_device;
+ vdc->realize = virtio_snd_realize;
+ vdc->unrealize = virtio_snd_unrealize;
+ vdc->get_config = virtio_snd_get_config;
+ vdc->set_config = virtio_snd_set_config;
+ vdc->get_features = get_features;
+ vdc->reset = virtio_snd_reset;
+ vdc->legacy_features = 0;
+}
+
+static const TypeInfo virtio_snd_types[] = {
+ {
+ .name = TYPE_VIRTIO_SND,
+ .parent = TYPE_VIRTIO_DEVICE,
+ .instance_size = sizeof(VirtIOSound),
+ .class_init = virtio_snd_class_init,
+ }
+};
+
+DEFINE_TYPES(virtio_snd_types)
diff --git a/hw/cxl/cxl-cdat.c b/hw/cxl/cxl-cdat.c
index d246d6885b..639a2db3e1 100644
--- a/hw/cxl/cxl-cdat.c
+++ b/hw/cxl/cxl-cdat.c
@@ -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 */
diff --git a/hw/cxl/cxl-component-utils.c b/hw/cxl/cxl-component-utils.c
index f3bbf0fd13..d0245cc55d 100644
--- a/hw/cxl/cxl-component-utils.c
+++ b/hw/cxl/cxl-component-utils.c
@@ -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) {
- qemu_log_mask(LOG_UNIMP,
- "CXL 8 byte cache mem registers not implemented\n");
+ 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;
}
- 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);
+ case 8:
+ qemu_log_mask(LOG_UNIMP,
+ "CXL 8 byte cache mem registers not implemented\n");
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;
}
diff --git a/hw/cxl/cxl-device-utils.c b/hw/cxl/cxl-device-utils.c
index bd68328032..61a3c4dc2e 100644
--- a/hw/cxl/cxl-device-utils.c
+++ b/hw/cxl/cxl-device-utils.c
@@ -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)
diff --git a/hw/cxl/cxl-events.c b/hw/cxl/cxl-events.c
index 3ddd6369ad..bee6dfaf14 100644
--- a/hw/cxl/cxl-events.c
+++ b/hw/cxl/cxl-events.c
@@ -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;
diff --git a/hw/cxl/cxl-mailbox-utils.c b/hw/cxl/cxl-mailbox-utils.c
index 434ccc5f6e..b365575097 100644
--- a/hw/cxl/cxl-mailbox-utils.c
+++ b/hw/cxl/cxl-mailbox-utils.c
@@ -8,13 +8,17 @@
*/
#include "qemu/osdep.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/msix.h"
#include "hw/cxl/cxl.h"
#include "hw/cxl/cxl_events.h"
#include "hw/pci/pci.h"
+#include "hw/pci-bridge/cxl_upstream_port.h"
#include "qemu/cutils.h"
#include "qemu/log.h"
#include "qemu/units.h"
#include "qemu/uuid.h"
+#include "sysemu/hostmem.h"
#define CXL_CAPACITY_MULTIPLIER (256 * MiB)
@@ -44,6 +48,9 @@
*/
enum {
+ INFOSTAT = 0x00,
+ #define IS_IDENTIFY 0x1
+ #define BACKGROUND_OPERATION_STATUS 0x2
EVENTS = 0x01,
#define GET_RECORDS 0x0
#define CLEAR_RECORDS 0x1
@@ -63,27 +70,151 @@ enum {
#define GET_PARTITION_INFO 0x0
#define GET_LSA 0x2
#define SET_LSA 0x3
+ SANITIZE = 0x44,
+ #define OVERWRITE 0x0
+ #define SECURE_ERASE 0x1
+ PERSISTENT_MEM = 0x45,
+ #define GET_SECURITY_STATE 0x0
MEDIA_AND_POISON = 0x43,
#define GET_POISON_LIST 0x0
#define INJECT_POISON 0x1
#define CLEAR_POISON 0x2
+ PHYSICAL_SWITCH = 0x51,
+ #define IDENTIFY_SWITCH_DEVICE 0x0
+ #define GET_PHYSICAL_PORT_STATE 0x1
+ TUNNEL = 0x53,
+ #define MANAGEMENT_COMMAND 0x0
};
-struct cxl_cmd;
-typedef CXLRetCode (*opcode_handler)(struct cxl_cmd *cmd,
- CXLDeviceState *cxl_dstate, uint16_t *len);
-struct cxl_cmd {
- const char *name;
- opcode_handler handler;
- ssize_t in;
- uint16_t effect; /* Reported in CEL */
- uint8_t *payload;
-};
+/* CCI Message Format CXL r3.0 Figure 7-19 */
+typedef struct CXLCCIMessage {
+ uint8_t category;
+#define CXL_CCI_CAT_REQ 0
+#define CXL_CCI_CAT_RSP 1
+ uint8_t tag;
+ uint8_t resv1;
+ uint8_t command;
+ uint8_t command_set;
+ uint8_t pl_length[3];
+ uint16_t rc;
+ uint16_t vendor_specific;
+ uint8_t payload[];
+} QEMU_PACKED CXLCCIMessage;
+
+/* This command is only defined to an MLD FM Owned LD or an MHD */
+static CXLRetCode cmd_tunnel_management_cmd(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
+{
+ PCIDevice *tunnel_target;
+ CXLCCI *target_cci;
+ struct {
+ uint8_t port_or_ld_id;
+ uint8_t target_type;
+ uint16_t size;
+ CXLCCIMessage ccimessage;
+ } QEMU_PACKED *in;
+ struct {
+ uint16_t resp_len;
+ uint8_t resv[2];
+ CXLCCIMessage ccimessage;
+ } QEMU_PACKED *out;
+ size_t pl_length, length_out;
+ bool bg_started;
+ int rc;
+
+ if (cmd->in < sizeof(*in)) {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+ in = (void *)payload_in;
+ out = (void *)payload_out;
+
+ /* Enough room for minimum sized message - no payload */
+ if (in->size < sizeof(in->ccimessage)) {
+ return CXL_MBOX_INVALID_PAYLOAD_LENGTH;
+ }
+ /* Length of input payload should be in->size + a wrapping tunnel header */
+ if (in->size != len_in - offsetof(typeof(*out), ccimessage)) {
+ return CXL_MBOX_INVALID_PAYLOAD_LENGTH;
+ }
+ if (in->ccimessage.category != CXL_CCI_CAT_REQ) {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+
+ if (in->target_type != 0) {
+ qemu_log_mask(LOG_UNIMP,
+ "Tunneled Command sent to non existent FM-LD");
+ return CXL_MBOX_INVALID_INPUT;
+ }
+
+ /*
+ * Target of a tunnel unfortunately depends on type of CCI readint
+ * the message.
+ * If in a switch, then it's the port number.
+ * If in an MLD it is the ld number.
+ * If in an MHD target type indicate where we are going.
+ */
+ if (object_dynamic_cast(OBJECT(cci->d), TYPE_CXL_TYPE3)) {
+ CXLType3Dev *ct3d = CXL_TYPE3(cci->d);
+ if (in->port_or_ld_id != 0) {
+ /* Only pretending to have one for now! */
+ return CXL_MBOX_INVALID_INPUT;
+ }
+ target_cci = &ct3d->ld0_cci;
+ } else if (object_dynamic_cast(OBJECT(cci->d), TYPE_CXL_USP)) {
+ CXLUpstreamPort *usp = CXL_USP(cci->d);
+
+ tunnel_target = pcie_find_port_by_pn(&PCI_BRIDGE(usp)->sec_bus,
+ in->port_or_ld_id);
+ if (!tunnel_target) {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+ tunnel_target =
+ pci_bridge_get_sec_bus(PCI_BRIDGE(tunnel_target))->devices[0];
+ if (!tunnel_target) {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+ if (object_dynamic_cast(OBJECT(tunnel_target), TYPE_CXL_TYPE3)) {
+ CXLType3Dev *ct3d = CXL_TYPE3(tunnel_target);
+ /* Tunneled VDMs always land on FM Owned LD */
+ target_cci = &ct3d->vdm_fm_owned_ld_mctp_cci;
+ } else {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+ } else {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+
+ pl_length = in->ccimessage.pl_length[2] << 16 |
+ in->ccimessage.pl_length[1] << 8 | in->ccimessage.pl_length[0];
+ rc = cxl_process_cci_message(target_cci,
+ in->ccimessage.command_set,
+ in->ccimessage.command,
+ pl_length, in->ccimessage.payload,
+ &length_out, out->ccimessage.payload,
+ &bg_started);
+ /* Payload should be in place. Rest of CCI header and needs filling */
+ out->resp_len = length_out + sizeof(CXLCCIMessage);
+ st24_le_p(out->ccimessage.pl_length, length_out);
+ out->ccimessage.rc = rc;
+ out->ccimessage.category = CXL_CCI_CAT_RSP;
+ out->ccimessage.command = in->ccimessage.command;
+ out->ccimessage.command_set = in->ccimessage.command_set;
+ out->ccimessage.tag = in->ccimessage.tag;
+ *len_out = length_out + sizeof(*out);
+
+ return CXL_MBOX_SUCCESS;
+}
-static CXLRetCode cmd_events_get_records(struct cxl_cmd *cmd,
- CXLDeviceState *cxlds,
- uint16_t *len)
+static CXLRetCode cmd_events_get_records(const struct cxl_cmd *cmd,
+ uint8_t *payload_in, size_t len_in,
+ uint8_t *payload_out, size_t *len_out,
+ CXLCCI *cci)
{
+ CXLDeviceState *cxlds = &CXL_TYPE3(cci->d)->cxl_dstate;
CXLGetEventPayload *pl;
uint8_t log_type;
int max_recs;
@@ -92,9 +223,9 @@ static CXLRetCode cmd_events_get_records(struct cxl_cmd *cmd,
return CXL_MBOX_INVALID_INPUT;
}
- log_type = *((uint8_t *)cmd->payload);
+ log_type = payload_in[0];
- pl = (CXLGetEventPayload *)cmd->payload;
+ pl = (CXLGetEventPayload *)payload_out;
memset(pl, 0, sizeof(*pl));
max_recs = (cxlds->payload_size - CXL_EVENT_PAYLOAD_HDR_SIZE) /
@@ -103,28 +234,36 @@ static CXLRetCode cmd_events_get_records(struct cxl_cmd *cmd,
max_recs = 0xFFFF;
}
- return cxl_event_get_records(cxlds, pl, log_type, max_recs, len);
+ return cxl_event_get_records(cxlds, pl, log_type, max_recs, len_out);
}
-static CXLRetCode cmd_events_clear_records(struct cxl_cmd *cmd,
- CXLDeviceState *cxlds,
- uint16_t *len)
+static CXLRetCode cmd_events_clear_records(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
{
+ CXLDeviceState *cxlds = &CXL_TYPE3(cci->d)->cxl_dstate;
CXLClearEventPayload *pl;
- pl = (CXLClearEventPayload *)cmd->payload;
- *len = 0;
+ pl = (CXLClearEventPayload *)payload_in;
+ *len_out = 0;
return cxl_event_clear_records(cxlds, pl);
}
-static CXLRetCode cmd_events_get_interrupt_policy(struct cxl_cmd *cmd,
- CXLDeviceState *cxlds,
- uint16_t *len)
+static CXLRetCode cmd_events_get_interrupt_policy(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
{
+ CXLDeviceState *cxlds = &CXL_TYPE3(cci->d)->cxl_dstate;
CXLEventInterruptPolicy *policy;
CXLEventLog *log;
- policy = (CXLEventInterruptPolicy *)cmd->payload;
+ policy = (CXLEventInterruptPolicy *)payload_out;
memset(policy, 0, sizeof(*policy));
log = &cxlds->event_logs[CXL_EVENT_TYPE_INFO];
@@ -153,22 +292,26 @@ static CXLRetCode cmd_events_get_interrupt_policy(struct cxl_cmd *cmd,
policy->dyn_cap_settings = CXL_INT_MSI_MSIX;
}
- *len = sizeof(*policy);
+ *len_out = sizeof(*policy);
return CXL_MBOX_SUCCESS;
}
-static CXLRetCode cmd_events_set_interrupt_policy(struct cxl_cmd *cmd,
- CXLDeviceState *cxlds,
- uint16_t *len)
+static CXLRetCode cmd_events_set_interrupt_policy(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
{
+ CXLDeviceState *cxlds = &CXL_TYPE3(cci->d)->cxl_dstate;
CXLEventInterruptPolicy *policy;
CXLEventLog *log;
- if (*len < CXL_EVENT_INT_SETTING_MIN_LEN) {
+ if (len_in < CXL_EVENT_INT_SETTING_MIN_LEN) {
return CXL_MBOX_INVALID_PAYLOAD_LENGTH;
}
- policy = (CXLEventInterruptPolicy *)cmd->payload;
+ policy = (CXLEventInterruptPolicy *)payload_in;
log = &cxlds->event_logs[CXL_EVENT_TYPE_INFO];
log->irq_enabled = (policy->info_settings & CXL_EVENT_INT_MODE_MASK) ==
@@ -187,7 +330,7 @@ static CXLRetCode cmd_events_set_interrupt_policy(struct cxl_cmd *cmd,
CXL_INT_MSI_MSIX;
/* DCD is optional */
- if (*len < sizeof(*policy)) {
+ if (len_in < sizeof(*policy)) {
return CXL_MBOX_SUCCESS;
}
@@ -195,15 +338,286 @@ static CXLRetCode cmd_events_set_interrupt_policy(struct cxl_cmd *cmd,
log->irq_enabled = (policy->dyn_cap_settings & CXL_EVENT_INT_MODE_MASK) ==
CXL_INT_MSI_MSIX;
- *len = sizeof(*policy);
+ *len_out = 0;
+ return CXL_MBOX_SUCCESS;
+}
+
+/* CXL r3.0 section 8.2.9.1.1: Identify (Opcode 0001h) */
+static CXLRetCode cmd_infostat_identify(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
+{
+ PCIDeviceClass *class = PCI_DEVICE_GET_CLASS(cci->d);
+ struct {
+ uint16_t pcie_vid;
+ uint16_t pcie_did;
+ uint16_t pcie_subsys_vid;
+ uint16_t pcie_subsys_id;
+ uint64_t sn;
+ uint8_t max_message_size;
+ uint8_t component_type;
+ } QEMU_PACKED *is_identify;
+ QEMU_BUILD_BUG_ON(sizeof(*is_identify) != 18);
+
+ is_identify = (void *)payload_out;
+ memset(is_identify, 0, sizeof(*is_identify));
+ is_identify->pcie_vid = class->vendor_id;
+ is_identify->pcie_did = class->device_id;
+ if (object_dynamic_cast(OBJECT(cci->d), TYPE_CXL_USP)) {
+ is_identify->sn = CXL_USP(cci->d)->sn;
+ /* Subsystem info not defined for a USP */
+ is_identify->pcie_subsys_vid = 0;
+ is_identify->pcie_subsys_id = 0;
+ is_identify->component_type = 0x0; /* Switch */
+ } else if (object_dynamic_cast(OBJECT(cci->d), TYPE_CXL_TYPE3)) {
+ PCIDevice *pci_dev = PCI_DEVICE(cci->d);
+
+ is_identify->sn = CXL_TYPE3(cci->d)->sn;
+ /*
+ * We can't always use class->subsystem_vendor_id as
+ * it is not set if the defaults are used.
+ */
+ is_identify->pcie_subsys_vid =
+ pci_get_word(pci_dev->config + PCI_SUBSYSTEM_VENDOR_ID);
+ is_identify->pcie_subsys_id =
+ pci_get_word(pci_dev->config + PCI_SUBSYSTEM_ID);
+ is_identify->component_type = 0x3; /* Type 3 */
+ }
+
+ /* TODO: Allow this to vary across different CCIs */
+ is_identify->max_message_size = 9; /* 512 bytes - MCTP_CXL_MAILBOX_BYTES */
+ *len_out = sizeof(*is_identify);
+ return CXL_MBOX_SUCCESS;
+}
+
+static void cxl_set_dsp_active_bm(PCIBus *b, PCIDevice *d,
+ void *private)
+{
+ uint8_t *bm = private;
+ if (object_dynamic_cast(OBJECT(d), TYPE_CXL_DSP)) {
+ uint8_t port = PCIE_PORT(d)->port;
+ bm[port / 8] |= 1 << (port % 8);
+ }
+}
+
+/* CXL r3 8.2.9.1.1 */
+static CXLRetCode cmd_identify_switch_device(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
+{
+ PCIEPort *usp = PCIE_PORT(cci->d);
+ PCIBus *bus = &PCI_BRIDGE(cci->d)->sec_bus;
+ int num_phys_ports = pcie_count_ds_ports(bus);
+
+ struct cxl_fmapi_ident_switch_dev_resp_pl {
+ uint8_t ingress_port_id;
+ uint8_t rsvd;
+ uint8_t num_physical_ports;
+ uint8_t num_vcss;
+ uint8_t active_port_bitmask[0x20];
+ uint8_t active_vcs_bitmask[0x20];
+ uint16_t total_vppbs;
+ uint16_t bound_vppbs;
+ uint8_t num_hdm_decoders_per_usp;
+ } QEMU_PACKED *out;
+ QEMU_BUILD_BUG_ON(sizeof(*out) != 0x49);
+
+ out = (struct cxl_fmapi_ident_switch_dev_resp_pl *)payload_out;
+ *out = (struct cxl_fmapi_ident_switch_dev_resp_pl) {
+ .num_physical_ports = num_phys_ports + 1, /* 1 USP */
+ .num_vcss = 1, /* Not yet support multiple VCS - potentialy tricky */
+ .active_vcs_bitmask[0] = 0x1,
+ .total_vppbs = num_phys_ports + 1,
+ .bound_vppbs = num_phys_ports + 1,
+ .num_hdm_decoders_per_usp = 4,
+ };
+
+ /* Depends on the CCI type */
+ if (object_dynamic_cast(OBJECT(cci->intf), TYPE_PCIE_PORT)) {
+ out->ingress_port_id = PCIE_PORT(cci->intf)->port;
+ } else {
+ /* MCTP? */
+ out->ingress_port_id = 0;
+ }
+
+ pci_for_each_device_under_bus(bus, cxl_set_dsp_active_bm,
+ out->active_port_bitmask);
+ out->active_port_bitmask[usp->port / 8] |= (1 << usp->port % 8);
+
+ *len_out = sizeof(*out);
+
+ return CXL_MBOX_SUCCESS;
+}
+
+/* CXL r3.0 Section 7.6.7.1.2: Get Physical Port State (Opcode 5101h) */
+static CXLRetCode cmd_get_physical_port_state(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
+{
+ /* CXL r3.0 Table 7-18: Get Physical Port State Request Payload */
+ struct cxl_fmapi_get_phys_port_state_req_pl {
+ uint8_t num_ports;
+ uint8_t ports[];
+ } QEMU_PACKED *in;
+
+ /*
+ * CXL r3.0 Table 7-20: Get Physical Port State Port Information Block
+ * Format
+ */
+ struct cxl_fmapi_port_state_info_block {
+ uint8_t port_id;
+ uint8_t config_state;
+ uint8_t connected_device_cxl_version;
+ uint8_t rsv1;
+ uint8_t connected_device_type;
+ uint8_t port_cxl_version_bitmask;
+ uint8_t max_link_width;
+ uint8_t negotiated_link_width;
+ uint8_t supported_link_speeds_vector;
+ uint8_t max_link_speed;
+ uint8_t current_link_speed;
+ uint8_t ltssm_state;
+ uint8_t first_lane_num;
+ uint16_t link_state;
+ uint8_t supported_ld_count;
+ } QEMU_PACKED;
+
+ /* CXL r3.0 Table 7-19: Get Physical Port State Response Payload */
+ struct cxl_fmapi_get_phys_port_state_resp_pl {
+ uint8_t num_ports;
+ uint8_t rsv1[3];
+ struct cxl_fmapi_port_state_info_block ports[];
+ } QEMU_PACKED *out;
+ PCIBus *bus = &PCI_BRIDGE(cci->d)->sec_bus;
+ PCIEPort *usp = PCIE_PORT(cci->d);
+ size_t pl_size;
+ int i;
+
+ in = (struct cxl_fmapi_get_phys_port_state_req_pl *)payload_in;
+ out = (struct cxl_fmapi_get_phys_port_state_resp_pl *)payload_out;
+
+ /* Check if what was requested can fit */
+ if (sizeof(*out) + sizeof(*out->ports) * in->num_ports > cci->payload_max) {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+
+ /* For success there should be a match for each requested */
+ out->num_ports = in->num_ports;
+
+ for (i = 0; i < in->num_ports; i++) {
+ struct cxl_fmapi_port_state_info_block *port;
+ /* First try to match on downstream port */
+ PCIDevice *port_dev;
+ uint16_t lnkcap, lnkcap2, lnksta;
+
+ port = &out->ports[i];
+
+ port_dev = pcie_find_port_by_pn(bus, in->ports[i]);
+ if (port_dev) { /* DSP */
+ PCIDevice *ds_dev = pci_bridge_get_sec_bus(PCI_BRIDGE(port_dev))
+ ->devices[0];
+ port->config_state = 3;
+ if (ds_dev) {
+ if (object_dynamic_cast(OBJECT(ds_dev), TYPE_CXL_TYPE3)) {
+ port->connected_device_type = 5; /* Assume MLD for now */
+ } else {
+ port->connected_device_type = 1;
+ }
+ } else {
+ port->connected_device_type = 0;
+ }
+ port->supported_ld_count = 3;
+ } else if (usp->port == in->ports[i]) { /* USP */
+ port_dev = PCI_DEVICE(usp);
+ port->config_state = 4;
+ port->connected_device_type = 0;
+ } else {
+ return CXL_MBOX_INVALID_INPUT;
+ }
+
+ port->port_id = in->ports[i];
+ /* Information on status of this port in lnksta, lnkcap */
+ if (!port_dev->exp.exp_cap) {
+ return CXL_MBOX_INTERNAL_ERROR;
+ }
+ lnksta = port_dev->config_read(port_dev,
+ port_dev->exp.exp_cap + PCI_EXP_LNKSTA,
+ sizeof(lnksta));
+ lnkcap = port_dev->config_read(port_dev,
+ port_dev->exp.exp_cap + PCI_EXP_LNKCAP,
+ sizeof(lnkcap));
+ lnkcap2 = port_dev->config_read(port_dev,
+ port_dev->exp.exp_cap + PCI_EXP_LNKCAP2,
+ sizeof(lnkcap2));
+
+ port->max_link_width = (lnkcap & PCI_EXP_LNKCAP_MLW) >> 4;
+ port->negotiated_link_width = (lnksta & PCI_EXP_LNKSTA_NLW) >> 4;
+ /* No definition for SLS field in linux/pci_regs.h */
+ port->supported_link_speeds_vector = (lnkcap2 & 0xFE) >> 1;
+ port->max_link_speed = lnkcap & PCI_EXP_LNKCAP_SLS;
+ port->current_link_speed = lnksta & PCI_EXP_LNKSTA_CLS;
+ /* TODO: Track down if we can get the rest of the info */
+ port->ltssm_state = 0x7;
+ port->first_lane_num = 0;
+ port->link_state = 0;
+ port->port_cxl_version_bitmask = 0x2;
+ port->connected_device_cxl_version = 0x2;
+ }
+
+ pl_size = sizeof(*out) + sizeof(*out->ports) * in->num_ports;
+ *len_out = pl_size;
+
+ return CXL_MBOX_SUCCESS;
+}
+
+/* CXL r3.0 8.2.9.1.2 */
+static CXLRetCode cmd_infostat_bg_op_sts(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
+{
+ struct {
+ uint8_t status;
+ uint8_t rsvd;
+ uint16_t opcode;
+ uint16_t returncode;
+ uint16_t vendor_ext_status;
+ } QEMU_PACKED *bg_op_status;
+ QEMU_BUILD_BUG_ON(sizeof(*bg_op_status) != 8);
+
+ bg_op_status = (void *)payload_out;
+ memset(bg_op_status, 0, sizeof(*bg_op_status));
+ bg_op_status->status = cci->bg.complete_pct << 1;
+ if (cci->bg.runtime > 0) {
+ bg_op_status->status |= 1U << 0;
+ }
+ bg_op_status->opcode = cci->bg.opcode;
+ bg_op_status->returncode = cci->bg.ret_code;
+ *len_out = sizeof(*bg_op_status);
+
return CXL_MBOX_SUCCESS;
}
/* 8.2.9.2.1 */
-static CXLRetCode cmd_firmware_update_get_info(struct cxl_cmd *cmd,
- CXLDeviceState *cxl_dstate,
- uint16_t *len)
+static CXLRetCode cmd_firmware_update_get_info(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
{
+ CXLDeviceState *cxl_dstate = &CXL_TYPE3(cci->d)->cxl_dstate;
struct {
uint8_t slots_supported;
uint8_t slot_info;
@@ -221,7 +635,7 @@ static CXLRetCode cmd_firmware_update_get_info(struct cxl_cmd *cmd,
return CXL_MBOX_INTERNAL_ERROR;
}
- fw_info = (void *)cmd->payload;
+ fw_info = (void *)payload_out;
memset(fw_info, 0, sizeof(*fw_info));
fw_info->slots_supported = 2;
@@ -229,34 +643,43 @@ static CXLRetCode cmd_firmware_update_get_info(struct cxl_cmd *cmd,
fw_info->caps = 0;
pstrcpy(fw_info->fw_rev1, sizeof(fw_info->fw_rev1), "BWFW VERSION 0");
- *len = sizeof(*fw_info);
+ *len_out = sizeof(*fw_info);
return CXL_MBOX_SUCCESS;
}
/* 8.2.9.3.1 */
-static CXLRetCode cmd_timestamp_get(struct cxl_cmd *cmd,
- CXLDeviceState *cxl_dstate,
- uint16_t *len)
+static CXLRetCode cmd_timestamp_get(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
{
+ CXLDeviceState *cxl_dstate = &CXL_TYPE3(cci->d)->cxl_dstate;
uint64_t final_time = cxl_device_get_timestamp(cxl_dstate);
- stq_le_p(cmd->payload, final_time);
- *len = 8;
+ stq_le_p(payload_out, final_time);
+ *len_out = 8;
return CXL_MBOX_SUCCESS;
}
/* 8.2.9.3.2 */
-static CXLRetCode cmd_timestamp_set(struct cxl_cmd *cmd,
- CXLDeviceState *cxl_dstate,
- uint16_t *len)
+static CXLRetCode cmd_timestamp_set(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
{
+ CXLDeviceState *cxl_dstate = &CXL_TYPE3(cci->d)->cxl_dstate;
+
cxl_dstate->timestamp.set = true;
cxl_dstate->timestamp.last_set = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
- cxl_dstate->timestamp.host_set = le64_to_cpu(*(uint64_t *)cmd->payload);
+ cxl_dstate->timestamp.host_set = le64_to_cpu(*(uint64_t *)payload_in);
- *len = 0;
+ *len_out = 0;
return CXL_MBOX_SUCCESS;
}
@@ -267,9 +690,12 @@ static const QemuUUID cel_uuid = {
};
/* 8.2.9.4.1 */
-static CXLRetCode cmd_logs_get_supported(struct cxl_cmd *cmd,
- CXLDeviceState *cxl_dstate,
- uint16_t *len)
+static CXLRetCode cmd_logs_get_supported(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
{
struct {
uint16_t entries;
@@ -278,27 +704,32 @@ static CXLRetCode cmd_logs_get_supported(struct cxl_cmd *cmd,
QemuUUID uuid;
uint32_t size;
} log_entries[1];
- } QEMU_PACKED *supported_logs = (void *)cmd->payload;
+ } QEMU_PACKED *supported_logs = (void *)payload_out;
QEMU_BUILD_BUG_ON(sizeof(*supported_logs) != 0x1c);
supported_logs->entries = 1;
supported_logs->log_entries[0].uuid = cel_uuid;
- supported_logs->log_entries[0].size = 4 * cxl_dstate->cel_size;
+ supported_logs->log_entries[0].size = 4 * cci->cel_size;
- *len = sizeof(*supported_logs);
+ *len_out = sizeof(*supported_logs);
return CXL_MBOX_SUCCESS;
}
/* 8.2.9.4.2 */
-static CXLRetCode cmd_logs_get_log(struct cxl_cmd *cmd,
- CXLDeviceState *cxl_dstate,
- uint16_t *len)
+static CXLRetCode cmd_logs_get_log(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
{
struct {
QemuUUID uuid;
uint32_t offset;
uint32_t length;
- } QEMU_PACKED QEMU_ALIGNED(16) *get_log = (void *)cmd->payload;
+ } QEMU_PACKED QEMU_ALIGNED(16) *get_log;
+
+ get_log = (void *)payload_in;
/*
* 8.2.9.4.2
@@ -313,7 +744,7 @@ static CXLRetCode cmd_logs_get_log(struct cxl_cmd *cmd,
* the only possible failure would be if the mailbox itself isn't big
* enough.
*/
- if (get_log->offset + get_log->length > cxl_dstate->payload_size) {
+ if (get_log->offset + get_log->length > cci->payload_max) {
return CXL_MBOX_INVALID_INPUT;
}
@@ -322,18 +753,20 @@ static CXLRetCode cmd_logs_get_log(struct cxl_cmd *cmd,
}
/* Store off everything to local variables so we can wipe out the payload */
- *len = get_log->length;
+ *len_out = get_log->length;
- memmove(cmd->payload, cxl_dstate->cel_log + get_log->offset,
- get_log->length);
+ memmove(payload_out, cci->cel_log + get_log->offset, get_log->length);
return CXL_MBOX_SUCCESS;
}
/* 8.2.9.5.1.1 */
-static CXLRetCode cmd_identify_memory_device(struct cxl_cmd *cmd,
- CXLDeviceState *cxl_dstate,
- uint16_t *len)
+static CXLRetCode cmd_identify_memory_device(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
{
struct {
char fw_revision[0x10];
@@ -352,43 +785,50 @@ static CXLRetCode cmd_identify_memory_device(struct cxl_cmd *cmd,
uint8_t qos_telemetry_caps;
} QEMU_PACKED *id;
QEMU_BUILD_BUG_ON(sizeof(*id) != 0x43);
-
- CXLType3Dev *ct3d = container_of(cxl_dstate, CXLType3Dev, cxl_dstate);
+ CXLType3Dev *ct3d = CXL_TYPE3(cci->d);
CXLType3Class *cvc = CXL_TYPE3_GET_CLASS(ct3d);
+ CXLDeviceState *cxl_dstate = &ct3d->cxl_dstate;
if ((!QEMU_IS_ALIGNED(cxl_dstate->vmem_size, CXL_CAPACITY_MULTIPLIER)) ||
(!QEMU_IS_ALIGNED(cxl_dstate->pmem_size, CXL_CAPACITY_MULTIPLIER))) {
return CXL_MBOX_INTERNAL_ERROR;
}
- id = (void *)cmd->payload;
+ id = (void *)payload_out;
memset(id, 0, sizeof(*id));
snprintf(id->fw_revision, 0x10, "BWFW VERSION %02d", 0);
- stq_le_p(&id->total_capacity, cxl_dstate->mem_size / CXL_CAPACITY_MULTIPLIER);
- stq_le_p(&id->persistent_capacity, cxl_dstate->pmem_size / CXL_CAPACITY_MULTIPLIER);
- stq_le_p(&id->volatile_capacity, cxl_dstate->vmem_size / CXL_CAPACITY_MULTIPLIER);
+ stq_le_p(&id->total_capacity,
+ cxl_dstate->mem_size / CXL_CAPACITY_MULTIPLIER);
+ stq_le_p(&id->persistent_capacity,
+ cxl_dstate->pmem_size / CXL_CAPACITY_MULTIPLIER);
+ stq_le_p(&id->volatile_capacity,
+ cxl_dstate->vmem_size / CXL_CAPACITY_MULTIPLIER);
stl_le_p(&id->lsa_size, cvc->get_lsa_size(ct3d));
/* 256 poison records */
st24_le_p(id->poison_list_max_mer, 256);
/* No limit - so limited by main poison record limit */
stw_le_p(&id->inject_poison_limit, 0);
- *len = sizeof(*id);
+ *len_out = sizeof(*id);
return CXL_MBOX_SUCCESS;
}
-static CXLRetCode cmd_ccls_get_partition_info(struct cxl_cmd *cmd,
- CXLDeviceState *cxl_dstate,
- uint16_t *len)
+static CXLRetCode cmd_ccls_get_partition_info(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
{
+ CXLDeviceState *cxl_dstate = &CXL_TYPE3(cci->d)->cxl_dstate;
struct {
uint64_t active_vmem;
uint64_t active_pmem;
uint64_t next_vmem;
uint64_t next_pmem;
- } QEMU_PACKED *part_info = (void *)cmd->payload;
+ } QEMU_PACKED *part_info = (void *)payload_out;
QEMU_BUILD_BUG_ON(sizeof(*part_info) != 0x20);
if ((!QEMU_IS_ALIGNED(cxl_dstate->vmem_size, CXL_CAPACITY_MULTIPLIER)) ||
@@ -396,82 +836,207 @@ static CXLRetCode cmd_ccls_get_partition_info(struct cxl_cmd *cmd,
return CXL_MBOX_INTERNAL_ERROR;
}
- stq_le_p(&part_info->active_vmem, cxl_dstate->vmem_size / CXL_CAPACITY_MULTIPLIER);
+ stq_le_p(&part_info->active_vmem,
+ cxl_dstate->vmem_size / CXL_CAPACITY_MULTIPLIER);
/*
* When both next_vmem and next_pmem are 0, there is no pending change to
* partitioning.
*/
stq_le_p(&part_info->next_vmem, 0);
- stq_le_p(&part_info->active_pmem, cxl_dstate->pmem_size / CXL_CAPACITY_MULTIPLIER);
+ stq_le_p(&part_info->active_pmem,
+ cxl_dstate->pmem_size / CXL_CAPACITY_MULTIPLIER);
stq_le_p(&part_info->next_pmem, 0);
- *len = sizeof(*part_info);
+ *len_out = sizeof(*part_info);
return CXL_MBOX_SUCCESS;
}
-static CXLRetCode cmd_ccls_get_lsa(struct cxl_cmd *cmd,
- CXLDeviceState *cxl_dstate,
- uint16_t *len)
+static CXLRetCode cmd_ccls_get_lsa(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
{
struct {
uint32_t offset;
uint32_t length;
} QEMU_PACKED *get_lsa;
- CXLType3Dev *ct3d = container_of(cxl_dstate, CXLType3Dev, cxl_dstate);
+ CXLType3Dev *ct3d = CXL_TYPE3(cci->d);
CXLType3Class *cvc = CXL_TYPE3_GET_CLASS(ct3d);
uint32_t offset, length;
- get_lsa = (void *)cmd->payload;
+ get_lsa = (void *)payload_in;
offset = get_lsa->offset;
length = get_lsa->length;
if (offset + length > cvc->get_lsa_size(ct3d)) {
- *len = 0;
+ *len_out = 0;
return CXL_MBOX_INVALID_INPUT;
}
- *len = cvc->get_lsa(ct3d, get_lsa, length, offset);
+ *len_out = cvc->get_lsa(ct3d, payload_out, length, offset);
return CXL_MBOX_SUCCESS;
}
-static CXLRetCode cmd_ccls_set_lsa(struct cxl_cmd *cmd,
- CXLDeviceState *cxl_dstate,
- uint16_t *len)
+static CXLRetCode cmd_ccls_set_lsa(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
{
struct set_lsa_pl {
uint32_t offset;
uint32_t rsvd;
uint8_t data[];
} QEMU_PACKED;
- struct set_lsa_pl *set_lsa_payload = (void *)cmd->payload;
- CXLType3Dev *ct3d = container_of(cxl_dstate, CXLType3Dev, cxl_dstate);
+ struct set_lsa_pl *set_lsa_payload = (void *)payload_in;
+ CXLType3Dev *ct3d = CXL_TYPE3(cci->d);
CXLType3Class *cvc = CXL_TYPE3_GET_CLASS(ct3d);
const size_t hdr_len = offsetof(struct set_lsa_pl, data);
- uint16_t plen = *len;
- *len = 0;
- if (!plen) {
+ *len_out = 0;
+ if (!len_in) {
return CXL_MBOX_SUCCESS;
}
- if (set_lsa_payload->offset + plen > cvc->get_lsa_size(ct3d) + hdr_len) {
+ if (set_lsa_payload->offset + len_in > cvc->get_lsa_size(ct3d) + hdr_len) {
return CXL_MBOX_INVALID_INPUT;
}
- plen -= hdr_len;
+ len_in -= hdr_len;
- cvc->set_lsa(ct3d, set_lsa_payload->data, plen, set_lsa_payload->offset);
+ cvc->set_lsa(ct3d, set_lsa_payload->data, len_in, set_lsa_payload->offset);
return CXL_MBOX_SUCCESS;
}
+/* Perform the actual device zeroing */
+static void __do_sanitization(CXLType3Dev *ct3d)
+{
+ MemoryRegion *mr;
+
+ if (ct3d->hostvmem) {
+ mr = host_memory_backend_get_memory(ct3d->hostvmem);
+ if (mr) {
+ void *hostmem = memory_region_get_ram_ptr(mr);
+ memset(hostmem, 0, memory_region_size(mr));
+ }
+ }
+
+ if (ct3d->hostpmem) {
+ mr = host_memory_backend_get_memory(ct3d->hostpmem);
+ if (mr) {
+ void *hostmem = memory_region_get_ram_ptr(mr);
+ memset(hostmem, 0, memory_region_size(mr));
+ }
+ }
+ if (ct3d->lsa) {
+ mr = host_memory_backend_get_memory(ct3d->lsa);
+ if (mr) {
+ void *lsa = memory_region_get_ram_ptr(mr);
+ memset(lsa, 0, memory_region_size(mr));
+ }
+ }
+}
+
+/*
+ * CXL 3.0 spec section 8.2.9.8.5.1 - Sanitize.
+ *
+ * Once the Sanitize command has started successfully, the device shall be
+ * placed in the media disabled state. If the command fails or is interrupted
+ * by a reset or power failure, it shall remain in the media disabled state
+ * until a successful Sanitize command has been completed. During this state:
+ *
+ * 1. Memory writes to the device will have no effect, and all memory reads
+ * will return random values (no user data returned, even for locations that
+ * the failed Sanitize operation didn’t sanitize yet).
+ *
+ * 2. Mailbox commands shall still be processed in the disabled state, except
+ * that commands that access Sanitized areas shall fail with the Media Disabled
+ * error code.
+ */
+static CXLRetCode cmd_sanitize_overwrite(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
+{
+ CXLType3Dev *ct3d = CXL_TYPE3(cci->d);
+ uint64_t total_mem; /* in Mb */
+ int secs;
+
+ total_mem = (ct3d->cxl_dstate.vmem_size + ct3d->cxl_dstate.pmem_size) >> 20;
+ if (total_mem <= 512) {
+ secs = 4;
+ } else if (total_mem <= 1024) {
+ secs = 8;
+ } else if (total_mem <= 2 * 1024) {
+ secs = 15;
+ } else if (total_mem <= 4 * 1024) {
+ secs = 30;
+ } else if (total_mem <= 8 * 1024) {
+ secs = 60;
+ } else if (total_mem <= 16 * 1024) {
+ secs = 2 * 60;
+ } else if (total_mem <= 32 * 1024) {
+ secs = 4 * 60;
+ } else if (total_mem <= 64 * 1024) {
+ secs = 8 * 60;
+ } else if (total_mem <= 128 * 1024) {
+ secs = 15 * 60;
+ } else if (total_mem <= 256 * 1024) {
+ secs = 30 * 60;
+ } else if (total_mem <= 512 * 1024) {
+ secs = 60 * 60;
+ } else if (total_mem <= 1024 * 1024) {
+ secs = 120 * 60;
+ } else {
+ secs = 240 * 60; /* max 4 hrs */
+ }
+
+ /* EBUSY other bg cmds as of now */
+ cci->bg.runtime = secs * 1000UL;
+ *len_out = 0;
+
+ cxl_dev_disable_media(&ct3d->cxl_dstate);
+
+ if (secs > 2) {
+ /* sanitize when done */
+ return CXL_MBOX_BG_STARTED;
+ } else {
+ __do_sanitization(ct3d);
+ cxl_dev_enable_media(&ct3d->cxl_dstate);
+
+ return CXL_MBOX_SUCCESS;
+ }
+}
+
+static CXLRetCode cmd_get_security_state(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
+{
+ uint32_t *state = (uint32_t *)payload_out;
+
+ *state = 0;
+ *len_out = 4;
+ return CXL_MBOX_SUCCESS;
+}
/*
* This is very inefficient, but good enough for now!
* Also the payload will always fit, so no need to handle the MORE flag and
* make this stateful. We may want to allow longer poison lists to aid
* testing that kernel functionality.
*/
-static CXLRetCode cmd_media_get_poison_list(struct cxl_cmd *cmd,
- CXLDeviceState *cxl_dstate,
- uint16_t *len)
+static CXLRetCode cmd_media_get_poison_list(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
{
struct get_poison_list_pl {
uint64_t pa;
@@ -491,9 +1056,9 @@ static CXLRetCode cmd_media_get_poison_list(struct cxl_cmd *cmd,
} QEMU_PACKED records[];
} QEMU_PACKED;
- struct get_poison_list_pl *in = (void *)cmd->payload;
- struct get_poison_list_out_pl *out = (void *)cmd->payload;
- CXLType3Dev *ct3d = container_of(cxl_dstate, CXLType3Dev, cxl_dstate);
+ struct get_poison_list_pl *in = (void *)payload_in;
+ struct get_poison_list_out_pl *out = (void *)payload_out;
+ CXLType3Dev *ct3d = CXL_TYPE3(cci->d);
uint16_t record_count = 0, i = 0;
uint64_t query_start, query_length;
CXLPoisonList *poison_list = &ct3d->poison_list;
@@ -541,21 +1106,24 @@ static CXLRetCode cmd_media_get_poison_list(struct cxl_cmd *cmd,
stq_le_p(&out->overflow_timestamp, ct3d->poison_list_overflow_ts);
}
stw_le_p(&out->count, record_count);
- *len = out_pl_len;
+ *len_out = out_pl_len;
return CXL_MBOX_SUCCESS;
}
-static CXLRetCode cmd_media_inject_poison(struct cxl_cmd *cmd,
- CXLDeviceState *cxl_dstate,
- uint16_t *len_unused)
+static CXLRetCode cmd_media_inject_poison(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
{
- CXLType3Dev *ct3d = container_of(cxl_dstate, CXLType3Dev, cxl_dstate);
+ CXLType3Dev *ct3d = CXL_TYPE3(cci->d);
CXLPoisonList *poison_list = &ct3d->poison_list;
CXLPoison *ent;
struct inject_poison_pl {
uint64_t dpa;
};
- struct inject_poison_pl *in = (void *)cmd->payload;
+ struct inject_poison_pl *in = (void *)payload_in;
uint64_t dpa = ldq_le_p(&in->dpa);
CXLPoison *p;
@@ -580,15 +1148,20 @@ static CXLRetCode cmd_media_inject_poison(struct cxl_cmd *cmd,
*/
QLIST_INSERT_HEAD(poison_list, p, node);
ct3d->poison_list_cnt++;
+ *len_out = 0;
return CXL_MBOX_SUCCESS;
}
-static CXLRetCode cmd_media_clear_poison(struct cxl_cmd *cmd,
- CXLDeviceState *cxl_dstate,
- uint16_t *len_unused)
+static CXLRetCode cmd_media_clear_poison(const struct cxl_cmd *cmd,
+ uint8_t *payload_in,
+ size_t len_in,
+ uint8_t *payload_out,
+ size_t *len_out,
+ CXLCCI *cci)
{
- CXLType3Dev *ct3d = container_of(cxl_dstate, CXLType3Dev, cxl_dstate);
+ CXLType3Dev *ct3d = CXL_TYPE3(cci->d);
+ CXLDeviceState *cxl_dstate = &ct3d->cxl_dstate;
CXLPoisonList *poison_list = &ct3d->poison_list;
CXLType3Class *cvc = CXL_TYPE3_GET_CLASS(ct3d);
struct clear_poison_pl {
@@ -598,7 +1171,7 @@ static CXLRetCode cmd_media_clear_poison(struct cxl_cmd *cmd,
CXLPoison *ent;
uint64_t dpa;
- struct clear_poison_pl *in = (void *)cmd->payload;
+ struct clear_poison_pl *in = (void *)payload_in;
dpa = ldq_le_p(&in->dpa);
if (dpa + CXL_CACHE_LINE_SIZE > cxl_dstate->mem_size) {
@@ -659,6 +1232,7 @@ static CXLRetCode cmd_media_clear_poison(struct cxl_cmd *cmd,
}
/* Any fragments have been added, free original entry */
g_free(ent);
+ *len_out = 0;
return CXL_MBOX_SUCCESS;
}
@@ -667,8 +1241,10 @@ static CXLRetCode cmd_media_clear_poison(struct cxl_cmd *cmd,
#define IMMEDIATE_DATA_CHANGE (1 << 2)
#define IMMEDIATE_POLICY_CHANGE (1 << 3)
#define IMMEDIATE_LOG_CHANGE (1 << 4)
+#define SECURITY_STATE_CHANGE (1 << 5)
+#define BACKGROUND_OPERATION (1 << 6)
-static struct cxl_cmd cxl_cmd_set[256][256] = {
+static const struct cxl_cmd cxl_cmd_set[256][256] = {
[EVENTS][GET_RECORDS] = { "EVENTS_GET_RECORDS",
cmd_events_get_records, 1, 0 },
[EVENTS][CLEAR_RECORDS] = { "EVENTS_CLEAR_RECORDS",
@@ -681,8 +1257,10 @@ static struct cxl_cmd cxl_cmd_set[256][256] = {
[FIRMWARE_UPDATE][GET_INFO] = { "FIRMWARE_UPDATE_GET_INFO",
cmd_firmware_update_get_info, 0, 0 },
[TIMESTAMP][GET] = { "TIMESTAMP_GET", cmd_timestamp_get, 0, 0 },
- [TIMESTAMP][SET] = { "TIMESTAMP_SET", cmd_timestamp_set, 8, IMMEDIATE_POLICY_CHANGE },
- [LOGS][GET_SUPPORTED] = { "LOGS_GET_SUPPORTED", cmd_logs_get_supported, 0, 0 },
+ [TIMESTAMP][SET] = { "TIMESTAMP_SET", cmd_timestamp_set,
+ 8, IMMEDIATE_POLICY_CHANGE },
+ [LOGS][GET_SUPPORTED] = { "LOGS_GET_SUPPORTED", cmd_logs_get_supported,
+ 0, 0 },
[LOGS][GET_LOG] = { "LOGS_GET_LOG", cmd_logs_get_log, 0x18, 0 },
[IDENTIFY][MEMORY_DEVICE] = { "IDENTIFY_MEMORY_DEVICE",
cmd_identify_memory_device, 0, 0 },
@@ -691,6 +1269,10 @@ static struct cxl_cmd cxl_cmd_set[256][256] = {
[CCLS][GET_LSA] = { "CCLS_GET_LSA", cmd_ccls_get_lsa, 8, 0 },
[CCLS][SET_LSA] = { "CCLS_SET_LSA", cmd_ccls_set_lsa,
~0, IMMEDIATE_CONFIG_CHANGE | IMMEDIATE_DATA_CHANGE },
+ [SANITIZE][OVERWRITE] = { "SANITIZE_OVERWRITE", cmd_sanitize_overwrite, 0,
+ IMMEDIATE_DATA_CHANGE | SECURITY_STATE_CHANGE | BACKGROUND_OPERATION },
+ [PERSISTENT_MEM][GET_SECURITY_STATE] = { "GET_SECURITY_STATE",
+ cmd_get_security_state, 0, 0 },
[MEDIA_AND_POISON][GET_POISON_LIST] = { "MEDIA_AND_POISON_GET_POISON_LIST",
cmd_media_get_poison_list, 16, 0 },
[MEDIA_AND_POISON][INJECT_POISON] = { "MEDIA_AND_POISON_INJECT_POISON",
@@ -699,63 +1281,231 @@ static struct cxl_cmd cxl_cmd_set[256][256] = {
cmd_media_clear_poison, 72, 0 },
};
-void cxl_process_mailbox(CXLDeviceState *cxl_dstate)
+static const struct cxl_cmd cxl_cmd_set_sw[256][256] = {
+ [INFOSTAT][IS_IDENTIFY] = { "IDENTIFY", cmd_infostat_identify, 0, 0 },
+ [INFOSTAT][BACKGROUND_OPERATION_STATUS] = { "BACKGROUND_OPERATION_STATUS",
+ cmd_infostat_bg_op_sts, 0, 0 },
+ [TIMESTAMP][GET] = { "TIMESTAMP_GET", cmd_timestamp_get, 0, 0 },
+ [TIMESTAMP][SET] = { "TIMESTAMP_SET", cmd_timestamp_set, 0,
+ IMMEDIATE_POLICY_CHANGE },
+ [LOGS][GET_SUPPORTED] = { "LOGS_GET_SUPPORTED", cmd_logs_get_supported, 0,
+ 0 },
+ [LOGS][GET_LOG] = { "LOGS_GET_LOG", cmd_logs_get_log, 0x18, 0 },
+ [PHYSICAL_SWITCH][IDENTIFY_SWITCH_DEVICE] = { "IDENTIFY_SWITCH_DEVICE",
+ cmd_identify_switch_device, 0, 0 },
+ [PHYSICAL_SWITCH][GET_PHYSICAL_PORT_STATE] = { "SWITCH_PHYSICAL_PORT_STATS",
+ cmd_get_physical_port_state, ~0, 0 },
+ [TUNNEL][MANAGEMENT_COMMAND] = { "TUNNEL_MANAGEMENT_COMMAND",
+ cmd_tunnel_management_cmd, ~0, 0 },
+};
+
+/*
+ * While the command is executing in the background, the device should
+ * update the percentage complete in the Background Command Status Register
+ * at least once per second.
+ */
+
+#define CXL_MBOX_BG_UPDATE_FREQ 1000UL
+
+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)
{
- uint16_t ret = CXL_MBOX_SUCCESS;
- struct cxl_cmd *cxl_cmd;
- uint64_t status_reg;
+ int ret;
+ const struct cxl_cmd *cxl_cmd;
opcode_handler h;
- uint64_t command_reg = cxl_dstate->mbox_reg_state64[R_CXL_DEV_MAILBOX_CMD];
- uint8_t set = FIELD_EX64(command_reg, CXL_DEV_MAILBOX_CMD, COMMAND_SET);
- uint8_t cmd = FIELD_EX64(command_reg, CXL_DEV_MAILBOX_CMD, COMMAND);
- uint16_t len = FIELD_EX64(command_reg, CXL_DEV_MAILBOX_CMD, LENGTH);
- cxl_cmd = &cxl_cmd_set[set][cmd];
+ *len_out = 0;
+ cxl_cmd = &cci->cxl_cmd_set[set][cmd];
h = cxl_cmd->handler;
- if (h) {
- if (len == cxl_cmd->in || cxl_cmd->in == ~0) {
- cxl_cmd->payload = cxl_dstate->mbox_reg_state +
- A_CXL_DEV_CMD_PAYLOAD;
- ret = (*h)(cxl_cmd, cxl_dstate, &len);
- assert(len <= cxl_dstate->payload_size);
- } else {
- ret = CXL_MBOX_INVALID_PAYLOAD_LENGTH;
- }
- } else {
+ if (!h) {
qemu_log_mask(LOG_UNIMP, "Command %04xh not implemented\n",
set << 8 | cmd);
- ret = CXL_MBOX_UNSUPPORTED;
+ return CXL_MBOX_UNSUPPORTED;
}
- /* Set the return code */
- status_reg = FIELD_DP64(0, CXL_DEV_MAILBOX_STS, ERRNO, ret);
+ if (len_in != cxl_cmd->in && cxl_cmd->in != ~0) {
+ return CXL_MBOX_INVALID_PAYLOAD_LENGTH;
+ }
- /* Set the return length */
- command_reg = FIELD_DP64(command_reg, CXL_DEV_MAILBOX_CMD, COMMAND_SET, 0);
- command_reg = FIELD_DP64(command_reg, CXL_DEV_MAILBOX_CMD, COMMAND, 0);
- command_reg = FIELD_DP64(command_reg, CXL_DEV_MAILBOX_CMD, LENGTH, len);
+ /* Only one bg command at a time */
+ if ((cxl_cmd->effect & BACKGROUND_OPERATION) &&
+ cci->bg.runtime > 0) {
+ return CXL_MBOX_BUSY;
+ }
- cxl_dstate->mbox_reg_state64[R_CXL_DEV_MAILBOX_CMD] = command_reg;
- cxl_dstate->mbox_reg_state64[R_CXL_DEV_MAILBOX_STS] = status_reg;
+ /* forbid any selected commands while overwriting */
+ if (sanitize_running(cci)) {
+ if (h == cmd_events_get_records ||
+ h == cmd_ccls_get_partition_info ||
+ h == cmd_ccls_set_lsa ||
+ h == cmd_ccls_get_lsa ||
+ h == cmd_logs_get_log ||
+ h == cmd_media_get_poison_list ||
+ h == cmd_media_inject_poison ||
+ h == cmd_media_clear_poison ||
+ h == cmd_sanitize_overwrite) {
+ return CXL_MBOX_MEDIA_DISABLED;
+ }
+ }
- /* Tell the host we're done */
- ARRAY_FIELD_DP32(cxl_dstate->mbox_reg_state32, CXL_DEV_MAILBOX_CTRL,
- DOORBELL, 0);
+ ret = (*h)(cxl_cmd, pl_in, len_in, pl_out, len_out, cci);
+ if ((cxl_cmd->effect & BACKGROUND_OPERATION) &&
+ ret == CXL_MBOX_BG_STARTED) {
+ *bg_started = true;
+ } else {
+ *bg_started = false;
+ }
+
+ /* Set bg and the return code */
+ if (*bg_started) {
+ uint64_t now;
+
+ cci->bg.opcode = (set << 8) | cmd;
+
+ cci->bg.complete_pct = 0;
+ cci->bg.ret_code = 0;
+
+ now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
+ cci->bg.starttime = now;
+ timer_mod(cci->bg.timer, now + CXL_MBOX_BG_UPDATE_FREQ);
+ }
+
+ return ret;
}
-void cxl_initialize_mailbox(CXLDeviceState *cxl_dstate)
+static void bg_timercb(void *opaque)
{
+ CXLCCI *cci = opaque;
+ uint64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
+ uint64_t total_time = cci->bg.starttime + cci->bg.runtime;
+
+ assert(cci->bg.runtime > 0);
+
+ if (now >= total_time) { /* we are done */
+ uint16_t ret = CXL_MBOX_SUCCESS;
+
+ cci->bg.complete_pct = 100;
+ cci->bg.ret_code = ret;
+ if (ret == CXL_MBOX_SUCCESS) {
+ switch (cci->bg.opcode) {
+ case 0x4400: /* sanitize */
+ {
+ CXLType3Dev *ct3d = CXL_TYPE3(cci->d);
+
+ __do_sanitization(ct3d);
+ cxl_dev_enable_media(&ct3d->cxl_dstate);
+ }
+ break;
+ case 0x4304: /* TODO: scan media */
+ break;
+ default:
+ __builtin_unreachable();
+ break;
+ }
+ }
+
+ qemu_log("Background command %04xh finished: %s\n",
+ cci->bg.opcode,
+ ret == CXL_MBOX_SUCCESS ? "success" : "aborted");
+ } else {
+ /* estimate only */
+ cci->bg.complete_pct = 100 * now / total_time;
+ timer_mod(cci->bg.timer, now + CXL_MBOX_BG_UPDATE_FREQ);
+ }
+
+ if (cci->bg.complete_pct == 100) {
+ /* TODO: generalize to switch CCI */
+ CXLType3Dev *ct3d = CXL_TYPE3(cci->d);
+ CXLDeviceState *cxl_dstate = &ct3d->cxl_dstate;
+ PCIDevice *pdev = PCI_DEVICE(cci->d);
+
+ cci->bg.starttime = 0;
+ /* registers are updated, allow new bg-capable cmds */
+ cci->bg.runtime = 0;
+
+ if (msix_enabled(pdev)) {
+ msix_notify(pdev, cxl_dstate->mbox_msi_n);
+ } else if (msi_enabled(pdev)) {
+ msi_notify(pdev, cxl_dstate->mbox_msi_n);
+ }
+ }
+}
+
+void cxl_init_cci(CXLCCI *cci, size_t payload_max)
+{
+ cci->payload_max = payload_max;
for (int set = 0; set < 256; set++) {
for (int cmd = 0; cmd < 256; cmd++) {
- if (cxl_cmd_set[set][cmd].handler) {
- struct cxl_cmd *c = &cxl_cmd_set[set][cmd];
+ if (cci->cxl_cmd_set[set][cmd].handler) {
+ const struct cxl_cmd *c = &cci->cxl_cmd_set[set][cmd];
struct cel_log *log =
- &cxl_dstate->cel_log[cxl_dstate->cel_size];
+ &cci->cel_log[cci->cel_size];
log->opcode = (set << 8) | cmd;
log->effect = c->effect;
- cxl_dstate->cel_size++;
+ cci->cel_size++;
}
}
}
+ cci->bg.complete_pct = 0;
+ cci->bg.starttime = 0;
+ cci->bg.runtime = 0;
+ cci->bg.timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
+ bg_timercb, cci);
+}
+
+void cxl_initialize_mailbox_swcci(CXLCCI *cci, DeviceState *intf,
+ DeviceState *d, size_t payload_max)
+{
+ cci->cxl_cmd_set = cxl_cmd_set_sw;
+ cci->d = d;
+ cci->intf = intf;
+ cxl_init_cci(cci, payload_max);
+}
+
+void cxl_initialize_mailbox_t3(CXLCCI *cci, DeviceState *d, size_t payload_max)
+{
+ cci->cxl_cmd_set = cxl_cmd_set;
+ cci->d = d;
+
+ /* No separation for PCI MB as protocol handled in PCI device */
+ cci->intf = d;
+ cxl_init_cci(cci, payload_max);
+}
+
+static const struct cxl_cmd cxl_cmd_set_t3_ld[256][256] = {
+ [INFOSTAT][IS_IDENTIFY] = { "IDENTIFY", cmd_infostat_identify, 0, 0 },
+ [LOGS][GET_SUPPORTED] = { "LOGS_GET_SUPPORTED", cmd_logs_get_supported, 0,
+ 0 },
+ [LOGS][GET_LOG] = { "LOGS_GET_LOG", cmd_logs_get_log, 0x18, 0 },
+};
+
+void cxl_initialize_t3_ld_cci(CXLCCI *cci, DeviceState *d, DeviceState *intf,
+ size_t payload_max)
+{
+ cci->cxl_cmd_set = cxl_cmd_set_t3_ld;
+ cci->d = d;
+ cci->intf = intf;
+ cxl_init_cci(cci, payload_max);
+}
+
+static const struct cxl_cmd cxl_cmd_set_t3_fm_owned_ld_mctp[256][256] = {
+ [INFOSTAT][IS_IDENTIFY] = { "IDENTIFY", cmd_infostat_identify, 0, 0},
+ [LOGS][GET_SUPPORTED] = { "LOGS_GET_SUPPORTED", cmd_logs_get_supported, 0,
+ 0 },
+ [LOGS][GET_LOG] = { "LOGS_GET_LOG", cmd_logs_get_log, 0x18, 0 },
+ [TIMESTAMP][GET] = { "TIMESTAMP_GET", cmd_timestamp_get, 0, 0 },
+ [TUNNEL][MANAGEMENT_COMMAND] = { "TUNNEL_MANAGEMENT_COMMAND",
+ cmd_tunnel_management_cmd, ~0, 0 },
+};
+
+void cxl_initialize_t3_fm_owned_ld_mctpcci(CXLCCI *cci, DeviceState *d,
+ DeviceState *intf,
+ size_t payload_max)
+{
+ cci->cxl_cmd_set = cxl_cmd_set_t3_fm_owned_ld_mctp;
+ cci->d = d;
+ cci->intf = intf;
+ cxl_init_cci(cci, payload_max);
}
diff --git a/hw/cxl/meson.build b/hw/cxl/meson.build
index e261ff3881..ea0aebf6e3 100644
--- a/hw/cxl/meson.build
+++ b/hw/cxl/meson.build
@@ -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',
diff --git a/hw/cxl/switch-mailbox-cci.c b/hw/cxl/switch-mailbox-cci.c
new file mode 100644
index 0000000000..ba399c6240
--- /dev/null
+++ b/hw/cxl/switch-mailbox-cci.c
@@ -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);
diff --git a/hw/mem/cxl_type3.c b/hw/mem/cxl_type3.c
index c02be4ce45..52647b4ac7 100644
--- a/hw/mem/cxl_type3.c
+++ b/hw/mem/cxl_type3.c
@@ -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);
diff --git a/hw/mem/cxl_type3_stubs.c b/hw/mem/cxl_type3_stubs.c
index 8ba5d3d1f7..3e1851e32b 100644
--- a/hw/mem/cxl_type3_stubs.c
+++ b/hw/mem/cxl_type3_stubs.c
@@ -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,
diff --git a/hw/pci-bridge/cxl_downstream.c b/hw/pci-bridge/cxl_downstream.c
index 5a2b749c8e..405a133eef 100644
--- a/hw/pci-bridge/cxl_downstream.c
+++ b/hw/pci-bridge/cxl_downstream.c
@@ -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 },
diff --git a/hw/pci-bridge/cxl_root_port.c b/hw/pci-bridge/cxl_root_port.c
index 7dfd20aa67..8f97697631 100644
--- a/hw/pci-bridge/cxl_root_port.c
+++ b/hw/pci-bridge/cxl_root_port.c
@@ -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,
diff --git a/hw/pci-bridge/cxl_upstream.c b/hw/pci-bridge/cxl_upstream.c
index a57806fb31..36737189c6 100644
--- a/hw/pci-bridge/cxl_upstream.c
+++ b/hw/pci-bridge/cxl_upstream.c
@@ -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,
diff --git a/hw/virtio/vhost-user-fs.c b/hw/virtio/vhost-user-fs.c
index 49d699ffc2..eb91723855 100644
--- a/hw/virtio/vhost-user-fs.c
+++ b/hw/virtio/vhost-user-fs.c
@@ -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[] = {
diff --git a/hw/virtio/vhost-user.c b/hw/virtio/vhost-user.c
index 7b42ae8aae..f214df804b 100644
--- a/hw/virtio/vhost-user.c
+++ b/hw/virtio/vhost-user.c
@@ -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,
};
diff --git a/hw/virtio/vhost.c b/hw/virtio/vhost.c
index 9c9ae7109e..2c9ac79468 100644
--- a/hw/virtio/vhost.c
+++ b/hw/virtio/vhost.c
@@ -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;
+}
diff --git a/include/hw/audio/virtio-snd.h b/include/hw/audio/virtio-snd.h
new file mode 100644
index 0000000000..c3767f442b
--- /dev/null
+++ b/include/hw/audio/virtio-snd.h
@@ -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
diff --git a/include/hw/cxl/cxl.h b/include/hw/cxl/cxl.h
index 4944725849..75e47b6864 100644
--- a/include/hw/cxl/cxl.h
+++ b/include/hw/cxl/cxl.h
@@ -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
diff --git a/include/hw/cxl/cxl_component.h b/include/hw/cxl/cxl_component.h
index 3c795a6278..5227a8e833 100644
--- a/include/hw/cxl/cxl_component.h
+++ b/include/hw/cxl/cxl_component.h
@@ -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 {
diff --git a/include/hw/cxl/cxl_device.h b/include/hw/cxl/cxl_device.h
index 51cd0d9ce3..61b7f897f7 100644
--- a/include/hw/cxl/cxl_device.h
+++ b/include/hw/cxl/cxl_device.h
@@ -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);
diff --git a/include/hw/cxl/cxl_events.h b/include/hw/cxl/cxl_events.h
index 089ba2091f..d778487b7e 100644
--- a/include/hw/cxl/cxl_events.h
+++ b/include/hw/cxl/cxl_events.h
@@ -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;
diff --git a/include/hw/cxl/cxl_pci.h b/include/hw/cxl/cxl_pci.h
index 407be95b9e..ddf01a543b 100644
--- a/include/hw/cxl/cxl_pci.h
+++ b/include/hw/cxl/cxl_pci.h
@@ -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
diff --git a/include/hw/pci-bridge/cxl_upstream_port.h b/include/hw/pci-bridge/cxl_upstream_port.h
new file mode 100644
index 0000000000..12635139f6
--- /dev/null
+++ b/include/hw/pci-bridge/cxl_upstream_port.h
@@ -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 */
diff --git a/include/hw/virtio/vhost-backend.h b/include/hw/virtio/vhost-backend.h
index 96ccc18cd3..a86d103f82 100644
--- a/include/hw/virtio/vhost-backend.h
+++ b/include/hw/virtio/vhost-backend.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,
diff --git a/include/hw/virtio/vhost-user.h b/include/hw/virtio/vhost-user.h
index 20b69d8e85..d7c09ffd34 100644
--- a/include/hw/virtio/vhost-user.h
+++ b/include/hw/virtio/vhost-user.h
@@ -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
};
diff --git a/include/hw/virtio/vhost.h b/include/hw/virtio/vhost.h
index 5e8183f64a..05d7204a08 100644
--- a/include/hw/virtio/vhost.h
+++ b/include/hw/virtio/vhost.h
@@ -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
diff --git a/net/vhost-vdpa.c b/net/vhost-vdpa.c
index 7a226c93bc..d0614d7954 100644
--- a/net/vhost-vdpa.c
+++ b/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,
};
/*
diff --git a/system/qdev-monitor.c b/system/qdev-monitor.c
index 1b8005ae55..a13db763e5 100644
--- a/system/qdev-monitor.c
+++ b/system/qdev-monitor.c
@@ -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 },
diff --git a/tests/avocado/acpi-bits.py b/tests/avocado/acpi-bits.py
index eca13dc518..68b9e98d4e 100644
--- a/tests/avocado/acpi-bits.py
+++ b/tests/avocado/acpi-bits.py
@@ -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 typing import (
)
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 @@ class AcpiBitsTest(QemuBaseTest): #pylint: disable=too-many-instance-attributes
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 @@ class AcpiBitsTest(QemuBaseTest): #pylint: disable=too-many-instance-attributes
# 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')
+
+ # enable console logging
+ self._vm.set_console()
+ self._vm.launch()
- 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)
+ 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()
- self._vm.launch()
# 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()
diff --git a/tests/data/acpi/q35/APIC.core-count b/tests/data/acpi/q35/APIC.core-count
new file mode 100644
index 0000000000..d9d7ca9a89
--- /dev/null
+++ b/tests/data/acpi/q35/APIC.core-count
Binary files differ
diff --git a/tests/data/acpi/q35/APIC.core-count2 b/tests/data/acpi/q35/APIC.core-count2
index f5da2eb1e8..4f24284434 100644
--- a/tests/data/acpi/q35/APIC.core-count2
+++ b/tests/data/acpi/q35/APIC.core-count2
Binary files differ
diff --git a/tests/data/acpi/q35/APIC.thread-count b/tests/data/acpi/q35/APIC.thread-count
new file mode 100644
index 0000000000..c27e87fcf1
--- /dev/null
+++ b/tests/data/acpi/q35/APIC.thread-count
Binary files differ
diff --git a/tests/data/acpi/q35/APIC.thread-count2 b/tests/data/acpi/q35/APIC.thread-count2
new file mode 100644
index 0000000000..ac200ab7aa
--- /dev/null
+++ b/tests/data/acpi/q35/APIC.thread-count2
Binary files differ
diff --git a/tests/data/acpi/q35/APIC.type4-count b/tests/data/acpi/q35/APIC.type4-count
new file mode 100644
index 0000000000..ab60a6ef06
--- /dev/null
+++ b/tests/data/acpi/q35/APIC.type4-count
Binary files differ
diff --git a/tests/data/acpi/q35/DSDT.core-count b/tests/data/acpi/q35/DSDT.core-count
new file mode 100644
index 0000000000..a24b04cbdb
--- /dev/null
+++ b/tests/data/acpi/q35/DSDT.core-count
Binary files differ
diff --git a/tests/data/acpi/q35/DSDT.core-count2 b/tests/data/acpi/q35/DSDT.core-count2
index b47891ec10..3a0cb8c581 100644
--- a/tests/data/acpi/q35/DSDT.core-count2
+++ b/tests/data/acpi/q35/DSDT.core-count2
Binary files differ
diff --git a/tests/data/acpi/q35/DSDT.thread-count b/tests/data/acpi/q35/DSDT.thread-count
new file mode 100644
index 0000000000..a24b04cbdb
--- /dev/null
+++ b/tests/data/acpi/q35/DSDT.thread-count
Binary files differ
diff --git a/tests/data/acpi/q35/DSDT.thread-count2 b/tests/data/acpi/q35/DSDT.thread-count2
new file mode 100644
index 0000000000..3a0cb8c581
--- /dev/null
+++ b/tests/data/acpi/q35/DSDT.thread-count2
Binary files differ
diff --git a/tests/data/acpi/q35/DSDT.type4-count b/tests/data/acpi/q35/DSDT.type4-count
new file mode 100644
index 0000000000..edc23198cd
--- /dev/null
+++ b/tests/data/acpi/q35/DSDT.type4-count
Binary files differ
diff --git a/tests/data/acpi/q35/FACP.core-count b/tests/data/acpi/q35/FACP.core-count
new file mode 100644
index 0000000000..31fa5dd19c
--- /dev/null
+++ b/tests/data/acpi/q35/FACP.core-count
Binary files differ
diff --git a/tests/data/acpi/q35/FACP.thread-count b/tests/data/acpi/q35/FACP.thread-count
new file mode 100644
index 0000000000..31fa5dd19c
--- /dev/null
+++ b/tests/data/acpi/q35/FACP.thread-count
Binary files differ
diff --git a/tests/data/acpi/q35/FACP.thread-count2 b/tests/data/acpi/q35/FACP.thread-count2
new file mode 100644
index 0000000000..31fa5dd19c
--- /dev/null
+++ b/tests/data/acpi/q35/FACP.thread-count2
Binary files differ
diff --git a/tests/data/acpi/q35/FACP.type4-count b/tests/data/acpi/q35/FACP.type4-count
new file mode 100644
index 0000000000..31fa5dd19c
--- /dev/null
+++ b/tests/data/acpi/q35/FACP.type4-count
Binary files differ
diff --git a/tests/qtest/bios-tables-test.c b/tests/qtest/bios-tables-test.c
index 9f4bc15aab..71af5cf69f 100644
--- a/tests/qtest/bios-tables-test.c
+++ b/tests/qtest/bios-tables-test.c
@@ -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 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 275", &data);
+ 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);
diff --git a/tests/unit/test-smp-parse.c b/tests/unit/test-smp-parse.c
index fdc39a846c..24972666a7 100644
--- a/tests/unit/test-smp-parse.c
+++ b/tests/unit/test-smp-parse.c
@@ -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;
}