History

virtio_ring: fix stale descriptor flags after a failed packed add

In a packed ring the AVAIL and USED bits sit in the descriptor itself, so writing them makes that descriptor available. Those bit combinations flip meaning on every round of the ring, tracked by a wrap counter, so invalidating or validating a descriptor means inverting both bits. Commit 1ce9e6055fa0 ("virtio_ring: introduce packed ring support") has virtqueue_add_packed() make every descriptor of a chain available as it maps the chain, and write the head last. The device consumes the ring in order and stops at a head that is not available yet, so it never reaches the rest. When vring_map_one_sg() fails partway, unmap_release unmaps the segments and restores avail_used_flags, but the descriptors it wrote to in the ring stay marked with AVAIL and USED bits. The head is now the only entry that keeps the device from consuming these stale entries. For example, the ring would look like this now. Z - pre-previous command A - previous command B - aborted command C - current command [A1 DONE] [A2 DONE] <C1 EMPTY> [B2] [B3] [Z1 DONE] When the driver now attempts to issue the C command, the next add starts at the same head as B. If C spans less descriptors than B, there is no end marker because AVAIL and USED bits were still in place. And that means the device will start interpreting these stale entries (B2/B3) as another command entry, which then blocks the queue. This effect typically happens in swiotlb configurations under memory pressure, because vring_map_one_sg() can then fail with larger I/O requests which then leads to command abortions. There are broadly 2 ways to avoid leaving those flags behind: 1) Defer those flags too until the chain is complete. 2) Rewrite those flags for the previous wrap counter. Implement the second option in both packed add paths. The first option traverses the chain a second time on every successful add. The second option invalidates all added descriptors when any add fails. With this patch applied, a packed virtqueue keeps completing requests after a failed add.

Alexander Graf committed 2 months ago

Commit 75d276e1 parent df290801 file · +34 −4Browse files

Unified · Split

drivers/virtio/virtio_ring.c+34−4
@@ -1670,7 +1670,7 @@ static inline int virtqueue_add_packed(struct vring_virtqueue *vq, · 1669 unmodified lines
16701670 struct scatterlist *sg;
16711671 unsigned int i, n, c, descs_used, err_idx, len;
16721672 __le16 head_flags, flags;
1673− u16 head, id, prev, curr, avail_used_flags;
1673+ u16 head, id, prev, curr, avail_used_flags, unpub_flags;
16741674 int err;
16751675
16761676 START_USE(vq);
@@ -1798,15 +1798,30 @@ unmap_release: · 121 unmodified lines
17981798 curr = vq->free_head;
17991799
18001800 vq->packed.avail_used_flags = avail_used_flags;
1801+ unpub_flags = avail_used_flags ^ (1 << VRING_PACKED_DESC_F_AVAIL |
1802+ 1 << VRING_PACKED_DESC_F_USED);
18011803
18021804 for (n = 0; n < total_sg; n++) {
18031805 if (i == err_idx)
18041806 break;
1807+ /*
1808+ * The mapping loop made every descriptor but the head
1809+ * available. Stamp the previous wrap counter's AVAIL and USED
1810+ * bits on those, so that a later and shorter chain at this head
1811+ * does not leave one of them available beyond its own last
1812+ * descriptor. Marking them used instead would hand
1813+ * is_used_desc_packed() a completion we never made.
1814+ */
1815+ if (i != head)
1816+ desc[i].flags = cpu_to_le16(unpub_flags);
18051817 vring_unmap_extra_packed(vq, &vq->packed.desc_extra[curr]);
18061818 curr = vq->packed.desc_extra[curr].next;
18071819 i++;
1808− if (i >= vq->packed.vring.num)
1820+ if (i >= vq->packed.vring.num) {
18091821 i = 0;
1822+ unpub_flags ^= 1 << VRING_PACKED_DESC_F_AVAIL |
1823+ 1 << VRING_PACKED_DESC_F_USED;
1824+ }
18101825 }
18111826
18121827 END_USE(vq);
@@ -1828,7 +1843,7 @@ static inline int virtqueue_add_packed_in_order(struct vring_virtqueue *vq, · 15 unmodified lines
18281843 struct scatterlist *sg;
18291844 unsigned int i, n, sg_count, err_idx, total_in_len = 0;
18301845 __le16 head_flags, flags;
1831− u16 head, avail_used_flags;
1846+ u16 head, avail_used_flags, unpub_flags;
18321847 bool avail_wrap_counter;
18331848 int err;
18341849
@@ -1955,14 +1970,29 @@ unmap_release: · 120 unmodified lines
19551970 i = head;
19561971 vq->packed.avail_used_flags = avail_used_flags;
19571972 vq->packed.avail_wrap_counter = avail_wrap_counter;
1973+ unpub_flags = avail_used_flags ^ (1 << VRING_PACKED_DESC_F_AVAIL |
1974+ 1 << VRING_PACKED_DESC_F_USED);
19581975
19591976 for (n = 0; n < total_sg; n++) {
19601977 if (i == err_idx)
19611978 break;
1979+ /*
1980+ * The mapping loop made every descriptor but the head
1981+ * available. Stamp the previous wrap counter's AVAIL and USED
1982+ * bits on those, so that a later and shorter chain at this head
1983+ * does not leave one of them available beyond its own last
1984+ * descriptor. Marking them used instead would hand
1985+ * is_used_desc_packed() a completion we never made.
1986+ */
1987+ if (i != head)
1988+ desc[i].flags = cpu_to_le16(unpub_flags);
19621989 vring_unmap_extra_packed(vq, &vq->packed.desc_extra[i]);
19631990 i++;
1964− if (i >= vq->packed.vring.num)
1991+ if (i >= vq->packed.vring.num) {
19651992 i = 0;
1993+ unpub_flags ^= 1 << VRING_PACKED_DESC_F_AVAIL |
1994+ 1 << VRING_PACKED_DESC_F_USED;
1995+ }
19661996 }
19671997
19681998 END_USE(vq);

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t