On 7/6/23 21:42, David Marchand wrote:
> At some point in OVS history, some virtio features were announced as
> supported (ECN and UFO virtio features).
>
> The userspace TSO code, which has been added later, does not support
> those features and tries to disable them.
>
> This breaks OVS upgrades: if an existing VM already negotiated such
> features, their lack on reconnection to an upgraded OVS triggers a
> vhost socket disconnection by Qemu.
> This results in an endless loop because Qemu then retries with the same
> set of virtio features.
>
> This patch proposes to try and detect those vhost socket disconnection
> and fallback restoring the old virtio features (and disabling TSO for this
> vhost port).
>
> Acked-by: Mike Pattrick <[email protected]>
> Acked-by: Simon Horman <[email protected]>
> Signed-off-by: David Marchand <[email protected]>
> ---
> Changelog since v4:
> - I kept acks as the logic behind the state machine did not change much,
> - fixed indent of enumeration in documentation,
> - used status:<field> in documentation instead of grep -o,
> - renamed "disabled_tso" as "userspace-tso",
> - switched to a state machine with flags,
> - removed note on byte padding in netdev_dpdk struct,
Thanks, David! A few nits inline.
Best regards, Ilya Maximets.
>
> Changelog since v3:
> - updated documentation now that the interface offloads status is reported
> in ovsdb,
> - fixed one coding style issue,
>
> Changelog since v2:
> - reported workaround presence in the ovsdb port status field and
> updated documentation accordingly,
> - tried to use "better" names, to distinguish ECN virtio feature from
> TSO OVS netdev feature,
>
> Changelog since v1:
> - added a note in the documentation,
> - fixed vhost unregister trigger (so that both disabling and re-enabling
> TSO is handled),
> - cleared netdev features when disabling TSO,
> - changed level and ratelimited log message on vhost socket disconnect,
>
> ---
> Documentation/topics/userspace-tso.rst | 26 ++++++-
> lib/netdev-dpdk.c | 98 ++++++++++++++++++++++++--
> 2 files changed, 118 insertions(+), 6 deletions(-)
>
> diff --git a/Documentation/topics/userspace-tso.rst
> b/Documentation/topics/userspace-tso.rst
> index 5a43c2e86b..c4b15f2604 100644
> --- a/Documentation/topics/userspace-tso.rst
> +++ b/Documentation/topics/userspace-tso.rst
> @@ -68,7 +68,7 @@ as follows.
> connection is established, `TSO` is thus advertised to the guest as an
> available feature:
>
> -QEMU Command Line Parameter::
> +1. QEMU Command Line Parameter::
>
> $ sudo $QEMU_DIR/x86_64-softmmu/qemu-system-x86_64 \
> ...
> @@ -77,12 +77,34 @@ QEMU Command Line Parameter::
> ...
>
> 2. Ethtool. Assuming that the guest's OS also supports `TSO`, ethtool can be
> -used to enable same::
> + used to enable same::
>
> $ ethtool -K eth0 sg on # scatter-gather is a prerequisite for TSO
> $ ethtool -K eth0 tso on
> $ ethtool -k eth0
>
> +**Note:** Enabling this feature impacts the virtio features exposed by the
> DPDK
> +vHost User backend to a guest. If a guest was already connected to OvS before
> +enabling TSO and restarting OvS, this guest ports won't have TSO available::
> +
> + $ ovs-vsctl get interface vhost0 status:tx_tcp_seg_offload
> + "false"
> +
> +To help diagnose the issue, those ports have some additional information in
> +their status field in ovsdb::
> +
> + $ ovs-vsctl get interface vhost0 status:userspace-tso
> + disabled
> +
> +To restore TSO for this guest ports, this guest QEMU process must be stopped,
> +then started again. OvS will then report::
> +
> + $ ovs-vsctl get interface vhost0 status:tx_tcp_seg_offload
> + "true"
> +
> + $ ovs-vsctl get interface vhost0 status:userspace-tso
> + ovs-vsctl: no key "userspace-tso" in Interface record "vhost0" column
> status
> +
> ~~~~~~~~~~~
> Limitations
> ~~~~~~~~~~~
> diff --git a/lib/netdev-dpdk.c b/lib/netdev-dpdk.c
> index 63dac689e3..ae28eaa6b6 100644
> --- a/lib/netdev-dpdk.c
> +++ b/lib/netdev-dpdk.c
> @@ -418,6 +418,18 @@ enum dpdk_hw_ol_features {
> NETDEV_TX_TSO_OFFLOAD = 1 << 7,
> };
>
> +/* Flags for the netdev_dpdk virtio_features_state field.
> + * This is used for the virtio features recovery mechanism linked to TSO
> + * support. */
> +#define OVS_VIRTIO_F_CLEAN (UINT8_C(1) << 0)
> +#define OVS_VIRTIO_F_WORKAROUND (UINT8_C(1) << 1)
> +#define OVS_VIRTIO_F_NEGOTIATED (UINT8_C(1) << 2)
> +#define OVS_VIRTIO_F_RECONF_PENDING (UINT8_C(1) << 3)
> +#define OVS_VIRTIO_F_CLEAN_NEGOTIATED (OVS_VIRTIO_F_CLEAN \
> + | OVS_VIRTIO_F_NEGOTIATED)
> +#define OVS_VIRTIO_F_WORKAROUND_NEGOTIATED (OVS_VIRTIO_F_WORKAROUND \
> + | OVS_VIRTIO_F_NEGOTIATED)
Maybe break the lines before the '(' ? Might be easier to read:
#define OVS_VIRTIO_F_CLEAN_NEGOTIATED \
(OVS_VIRTIO_F_CLEAN | OVS_VIRTIO_F_NEGOTIATED)
#define OVS_VIRTIO_F_WORKAROUND_NEGOTIATED \
(OVS_VIRTIO_F_WORKAROUND | OVS_VIRTIO_F_NEGOTIATED)
(not sure about extra spaces)
> +
> /*
> * In order to avoid confusion in variables names, following naming
> convention
> * should be used, if possible:
> @@ -474,7 +486,11 @@ struct netdev_dpdk {
> bool vhost_reconfigured;
>
> atomic_uint8_t vhost_tx_retries_max;
> - /* 2 pad bytes here. */
> +
> + /* Flags for virtio features recovery mechanism. */
> + uint8_t virtio_features_state;
> +
> + /* 1 pad byte here. */
> );
>
> PADDED_MEMBERS(CACHE_LINE_SIZE,
> @@ -1359,6 +1375,7 @@ common_construct(struct netdev *netdev, dpdk_port_t
> port_no,
> dev->requested_lsc_interrupt_mode = 0;
> ovsrcu_index_init(&dev->vid, -1);
> dev->vhost_reconfigured = false;
> + dev->virtio_features_state = OVS_VIRTIO_F_CLEAN;
> dev->attached = false;
> dev->started = false;
> dev->reset_needed = false;
> @@ -3883,6 +3900,12 @@ netdev_dpdk_vhost_user_get_status(const struct netdev
> *netdev,
> xasprintf("%d", vring.size));
> }
>
> + if (userspace_tso_enabled()
> + && dev->virtio_features_state & OVS_VIRTIO_F_WORKAROUND) {
> +
> + smap_add_format(args, "userspace-tso", "disabled");
> + }
> +
> ovs_mutex_unlock(&dev->mutex);
> return 0;
> }
> @@ -4245,6 +4268,10 @@ new_device(int vid)
> newnode = dev->socket_id;
> }
>
> + if (!(dev->virtio_features_state & OVS_VIRTIO_F_NEGOTIATED)) {
> + dev->virtio_features_state |= OVS_VIRTIO_F_NEGOTIATED;
> + }
Can we do this unconditionally?
> +
> if (dev->requested_n_txq < qp_num
> || dev->requested_n_rxq < qp_num
> || dev->requested_socket_id != newnode
> @@ -4268,7 +4295,9 @@ new_device(int vid)
> dev->hw_ol_features |= NETDEV_TX_SCTP_CKSUM_OFFLOAD;
> }
>
> - if (userspace_tso_enabled()) {
> + if (userspace_tso_enabled()
> + && dev->virtio_features_state & OVS_VIRTIO_F_CLEAN) {
If this needed? There is no harm in using these features on Tx if negotiated.
> +
> if (features & (1ULL << VIRTIO_NET_F_GUEST_TSO4)
> && features & (1ULL << VIRTIO_NET_F_GUEST_TSO6)) {
>
> @@ -4524,6 +4553,41 @@ destroy_connection(int vid)
> dev->requested_n_txq = qp_num;
> netdev_request_reconfigure(&dev->up);
> }
> +
> + if (!(dev->virtio_features_state & OVS_VIRTIO_F_NEGOTIATED)) {
> + /* The socket disconnected before reaching new_device. It
> + * likely means that the guest did not agree with the virtio
> + * features. */
> + VLOG_WARN_RL(&rl, "Connection on socket '%s' closed during "
> + "initialization.", dev->vhost_id);
> + }
> + if (!(dev->virtio_features_state & OVS_VIRTIO_F_RECONF_PENDING))
> {
> + switch (dev->virtio_features_state) {
> + case OVS_VIRTIO_F_CLEAN:
> + dev->virtio_features_state = OVS_VIRTIO_F_WORKAROUND;
> + break;
An empty line between cases.
> + case OVS_VIRTIO_F_WORKAROUND:
> + dev->virtio_features_state = OVS_VIRTIO_F_CLEAN;
> + break;
> + case OVS_VIRTIO_F_CLEAN_NEGOTIATED:
> + /* The virtio features were clean and got accepted by the
> + * guest. We expect it will be the case in the future and
> + * change nothing. */
> + break;
> + case OVS_VIRTIO_F_WORKAROUND_NEGOTIATED:
> + /* Let's try to go with clean virtio features on the next
> + * connection. */
> + dev->virtio_features_state = OVS_VIRTIO_F_CLEAN;
> + break;
> + default:
> + OVS_NOT_REACHED();
> + }
> + if (!(dev->virtio_features_state & OVS_VIRTIO_F_NEGOTIATED))
> {
> + dev->virtio_features_state |=
> OVS_VIRTIO_F_RECONF_PENDING;
> + netdev_request_reconfigure(&dev->up);
> + }
> + }
> +
> ovs_mutex_unlock(&dev->mutex);
> exists = true;
> break;
> @@ -5454,10 +5518,34 @@ static int
> netdev_dpdk_vhost_client_reconfigure(struct netdev *netdev)
> {
> struct netdev_dpdk *dev = netdev_dpdk_cast(netdev);
> + bool unregister = false;
> + bool enable_tso;
> + char *vhost_id;
> int err;
>
> ovs_mutex_lock(&dev->mutex);
>
> + /* This vhost-user port was registered to the vhost library already, but
> + * a socket disconnection happened and configuration must be re-evaluated
> + * wrt dev->virtio_features_state. */
This comment is related to the 'if' statement below and not to the 'enable_tso'
variable. Also, it's written as if the condition is true, which is a bit
misleading when a comment is outside.
> + enable_tso = userspace_tso_enabled()
> + && dev->virtio_features_state & OVS_VIRTIO_F_CLEAN;
Should this be moved under the 'Configure vHost client mode' condition?
> + if (dev->vhost_driver_flags & RTE_VHOST_USER_CLIENT && dev->vhost_id
> + && dev->virtio_features_state & OVS_VIRTIO_F_RECONF_PENDING) {
> +
> + dev->vhost_driver_flags &= ~RTE_VHOST_USER_CLIENT;
> + vhost_id = dev->vhost_id;
> + unregister = true;
> + }
> +
> + ovs_mutex_unlock(&dev->mutex);
> +
> + if (unregister) {
> + dpdk_vhost_driver_unregister(dev, vhost_id);
> + }
> +
> + ovs_mutex_lock(&dev->mutex);
> +
> /* Configure vHost client mode if requested and if the following criteria
> * are met:
> * 1. Device hasn't been registered yet.
> @@ -5467,6 +5555,8 @@ netdev_dpdk_vhost_client_reconfigure(struct netdev
> *netdev)
> uint64_t virtio_unsup_features = 0;
> uint64_t vhost_flags = 0;
>
> + dev->virtio_features_state &= ~OVS_VIRTIO_F_RECONF_PENDING;
> +
> /* Register client-mode device. */
> vhost_flags |= RTE_VHOST_USER_CLIENT;
>
> @@ -5487,7 +5577,7 @@ netdev_dpdk_vhost_client_reconfigure(struct netdev
> *netdev)
> }
>
> /* Enable External Buffers if TCP Segmentation Offload is enabled. */
> - if (userspace_tso_enabled()) {
> + if (enable_tso) {
> vhost_flags |= RTE_VHOST_USER_EXTBUF_SUPPORT;
> }
>
> @@ -5512,7 +5602,7 @@ netdev_dpdk_vhost_client_reconfigure(struct netdev
> *netdev)
> goto unlock;
> }
>
> - if (userspace_tso_enabled()) {
> + if (enable_tso) {
> virtio_unsup_features = 1ULL << VIRTIO_NET_F_HOST_ECN
> | 1ULL << VIRTIO_NET_F_HOST_UFO;
> VLOG_DBG("%s: TSO enabled on vhost port",
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev