github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/xdp/completionqueue.go (about) 1 // Copyright 2022 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //go:build amd64 || arm64 16 // +build amd64 arm64 17 18 package xdp 19 20 import ( 21 "github.com/nicocha30/gvisor-ligolo/pkg/atomicbitops" 22 ) 23 24 // The CompletionQueue is how the kernel tells a process which buffers have 25 // been transmitted and can be reused. 26 // 27 // CompletionQueue is not thread-safe and requires external synchronization 28 type CompletionQueue struct { 29 // mem is the mmap'd area shared with the kernel. Many other fields of 30 // this struct point into mem. 31 mem []byte 32 33 // ring is the actual ring buffer. It is a list of frame addresses 34 // ready to be reused. 35 // 36 // len(ring) must be a power of 2. 37 ring []uint64 38 39 // mask is used whenever indexing into ring. It is always len(ring)-1. 40 // It prevents index out of bounds errors while allowing the producer 41 // and consumer pointers to repeatedly "overflow" and loop back around 42 // the ring. 43 mask uint32 44 45 // producer points to the shared atomic value that indicates the last 46 // produced descriptor. Only the kernel updates this value. 47 producer *atomicbitops.Uint32 48 49 // consumer points to the shared atomic value that indicates the last 50 // consumed descriptor. Only we update this value. 51 consumer *atomicbitops.Uint32 52 53 // flags points to the shared atomic value that holds flags for the 54 // queue. 55 flags *atomicbitops.Uint32 56 57 // Cached values are used to avoid relatively expensive atomic 58 // operations. They are used, incremented, and decremented multiple 59 // times with non-atomic operations, and then "batch-updated" by 60 // reading or writing atomically to synchronize with the kernel. 61 62 // cachedProducer is updated when we atomically read *producer. 63 cachedProducer uint32 64 // cachedConsumer is used to atomically write *consumer. 65 cachedConsumer uint32 66 } 67 68 // Peek returns the number of buffers available to reuse as well as the index 69 // at which they start. Peek will only return a buffer once, so callers must 70 // process any received buffers. 71 func (cq *CompletionQueue) Peek() (nAvailable, index uint32) { 72 // Get the number of available buffers and update cachedConsumer to 73 // reflect that we're going to consume them. 74 entries := cq.free() 75 index = cq.cachedConsumer 76 cq.cachedConsumer += entries 77 return entries, index 78 } 79 80 func (cq *CompletionQueue) free() uint32 { 81 // Return any buffers we know about without incurring an atomic 82 // operation if possible. 83 entries := cq.cachedProducer - cq.cachedConsumer 84 // If we're not aware of any completed packets, refresh the producer 85 // pointer to see whether the kernel enqueued anything. 86 if entries == 0 { 87 cq.cachedProducer = cq.producer.Load() 88 entries = cq.cachedProducer - cq.cachedConsumer 89 } 90 return entries 91 } 92 93 // Release notifies the kernel that we have consumed nDone packets. 94 func (cq *CompletionQueue) Release(nDone uint32) { 95 // We don't have to use an atomic add because only we update this; the 96 // kernel just reads it. 97 cq.consumer.Store(cq.consumer.RacyLoad() + nDone) 98 } 99 100 // Get gets the descriptor at index. 101 func (cq *CompletionQueue) Get(index uint32) uint64 { 102 // Use mask to avoid overflowing and loop back around the ring. 103 return cq.ring[index&cq.mask] 104 } 105 106 // FreeAll dequeues as many buffers as possible from the queue and returns them 107 // to the UMEM. 108 // 109 // +checklocks:umem.mu 110 func (cq *CompletionQueue) FreeAll(umem *UMEM) { 111 available, index := cq.Peek() 112 if available < 1 { 113 return 114 } 115 for i := uint32(0); i < available; i++ { 116 umem.FreeFrame(cq.Get(index + i)) 117 } 118 cq.Release(available) 119 }