1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
|
// Copyright 2022 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build amd64 || arm64
// +build amd64 arm64
package xdp
import (
"gvisor.dev/gvisor/pkg/atomicbitops"
)
// The FillQueue is how a process tells the kernel which buffers are available
// to be filled by incoming packets.
//
// FillQueue is not thread-safe and requires external synchronization
type FillQueue struct {
// mem is the mmap'd area shared with the kernel. Many other fields of
// this struct point into mem.
mem []byte
// ring is the actual ring buffer. It is a list of frame addresses
// ready for incoming packets.
//
// len(ring) must be a power of 2.
ring []uint64
// mask is used whenever indexing into ring. It is always len(ring)-1.
// It prevents index out of bounds errors while allowing the producer
// and consumer pointers to repeatedly "overflow" and loop back around
// the ring.
mask uint32
// producer points to the shared atomic value that indicates the last
// produced descriptor. Only we update this value.
producer *atomicbitops.Uint32
// consumer points to the shared atomic value that indicates the last
// consumed descriptor. Only the kernel updates this value.
consumer *atomicbitops.Uint32
// flags points to the shared atomic value that holds flags for the
// queue.
flags *atomicbitops.Uint32
// Cached values are used to avoid relatively expensive atomic
// operations. They are used, incremented, and decremented multiple
// times with non-atomic operations, and then "batch-updated" by
// reading or writing atomically to synchronize with the kernel.
// cachedProducer is used to atomically write *producer.
cachedProducer uint32
// cachedConsumer is updated when we atomically read *consumer.
// cachedConsumer is actually len(ring) larger than the real consumer
// value. See free() for details.
cachedConsumer uint32
}
// free returns the number of free descriptors in the fill queue.
func (fq *FillQueue) free(toReserve uint32) uint32 {
// Try to find free descriptors without incurring an atomic operation.
//
// cachedConsumer is always len(fq.ring) larger than the real consumer
// value. This lets us, in the common case, compute the number of free
// descriptors simply via fq.cachedConsumer - fq.cachedProducer without
// also adding len(fq.ring).
if available := fq.cachedConsumer - fq.cachedProducer; available >= toReserve {
return available
}
// If we didn't already have enough descriptors available, check
// whether the kernel has returned some to us.
fq.cachedConsumer = fq.consumer.Load()
fq.cachedConsumer += uint32(len(fq.ring))
return fq.cachedConsumer - fq.cachedProducer
}
// Notify updates the producer such that it is visible to the kernel.
func (fq *FillQueue) Notify() {
fq.producer.Store(fq.cachedProducer)
}
// Set sets the fill queue's descriptor at index to addr.
func (fq *FillQueue) Set(index uint32, addr uint64) {
// Use mask to avoid overflowing and loop back around the ring.
fq.ring[index&fq.mask] = addr
}
// FillAll posts as many empty buffers as possible for the kernel to fill, then
// notifies the kernel.
//
// +checklocks:umem.mu
func (fq *FillQueue) FillAll(umem *UMEM) {
// Figure out how many buffers and queue slots are available.
available := fq.free(umem.nFreeFrames)
if available == 0 {
return
}
if available > umem.nFreeFrames {
available = umem.nFreeFrames
}
// Fill the queue as much as possible and notify ther kernel.
index := fq.cachedProducer
fq.cachedProducer += available
for i := uint32(0); i < available; i++ {
fq.Set(index+i, umem.AllocFrame())
}
fq.Notify()
}
|