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
1670 struct scatterlist *sg;1670 struct scatterlist *sg;
1671 unsigned int i, n, c, descs_used, err_idx, len;1671 unsigned int i, n, c, descs_used, err_idx, len;
1672 __le16 head_flags, flags;1672 __le16 head_flags, flags;
1673 u16 head, id, prev, curr, avail_used_flags;1673 u16 head, id, prev, curr, avail_used_flags, unpub_flags;
1674 int err;1674 int err;
1675 1675
1676 START_USE(vq);1676 START_USE(vq);
@@ -1798,15 +1798,30 @@ unmap_release: · 121 unmodified lines
1798 curr = vq->free_head;1798 curr = vq->free_head;
1799 1799
1800 vq->packed.avail_used_flags = avail_used_flags;1800 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);
1801 1803
1802 for (n = 0; n < total_sg; n++) {1804 for (n = 0; n < total_sg; n++) {
1803 if (i == err_idx)1805 if (i == err_idx)
1804 break;1806 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);
1805 vring_unmap_extra_packed(vq, &vq->packed.desc_extra[curr]);1817 vring_unmap_extra_packed(vq, &vq->packed.desc_extra[curr]);
1806 curr = vq->packed.desc_extra[curr].next;1818 curr = vq->packed.desc_extra[curr].next;
1807 i++;1819 i++;
1808 if (i >= vq->packed.vring.num)1820 if (i >= vq->packed.vring.num) {
1809 i = 0;1821 i = 0;
1822 unpub_flags ^= 1 << VRING_PACKED_DESC_F_AVAIL |
1823 1 << VRING_PACKED_DESC_F_USED;
1824 }
1810 }1825 }
1811 1826
1812 END_USE(vq);1827 END_USE(vq);
@@ -1828,7 +1843,7 @@ static inline int virtqueue_add_packed_in_order(struct vring_virtqueue *vq, · 15 unmodified lines
1828 struct scatterlist *sg;1843 struct scatterlist *sg;
1829 unsigned int i, n, sg_count, err_idx, total_in_len = 0;1844 unsigned int i, n, sg_count, err_idx, total_in_len = 0;
1830 __le16 head_flags, flags;1845 __le16 head_flags, flags;
1831 u16 head, avail_used_flags;1846 u16 head, avail_used_flags, unpub_flags;
1832 bool avail_wrap_counter;1847 bool avail_wrap_counter;
1833 int err;1848 int err;
1834 1849
@@ -1955,14 +1970,29 @@ unmap_release: · 120 unmodified lines
1955 i = head;1970 i = head;
1956 vq->packed.avail_used_flags = avail_used_flags;1971 vq->packed.avail_used_flags = avail_used_flags;
1957 vq->packed.avail_wrap_counter = avail_wrap_counter;1972 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);
1958 1975
1959 for (n = 0; n < total_sg; n++) {1976 for (n = 0; n < total_sg; n++) {
1960 if (i == err_idx)1977 if (i == err_idx)
1961 break;1978 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);
1962 vring_unmap_extra_packed(vq, &vq->packed.desc_extra[i]);1989 vring_unmap_extra_packed(vq, &vq->packed.desc_extra[i]);
1963 i++;1990 i++;
1964 if (i >= vq->packed.vring.num)1991 if (i >= vq->packed.vring.num) {
1965 i = 0;1992 i = 0;
1993 unpub_flags ^= 1 << VRING_PACKED_DESC_F_AVAIL |
1994 1 << VRING_PACKED_DESC_F_USED;
1995 }
1966 }1996 }
1967 1997
1968 END_USE(vq);1998 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