blob: e94c004ca65c97932516c75e113c83b3a682e03a [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package netdevice
import (
"fmt"
"math"
"math/bits"
)
// entries provides basic logic used in link devices to operate on queues of
// entries that can be in one of three states:
// - queued: entries describing buffers already operated on, queued to be sent
// to the driver (populated buffers for Tx, already processed buffers for Rx).
// - in-flight: entries describing buffers currently owned by the driver, not
// yet returned.
// - ready: entries describing buffers retrieved from the driver, but not yet
// operated on (free unpopulated buffers for Tx, buffers containing inbound
// traffic for Rx).
type entries struct {
// sent, queued, readied are indices modulo (capacity << 1). They
// implement a ring buffer with 3 regions:
//
// - sent:queued: "queued" entries
// - queued:readied: "ready" entries
// - readied:sent: "in-flight" entries
//
// boundary conditions:
// - readied == sent: all entries are "in-flight" (implies == queued).
// - sent == queued: 0 queued entries.
// - queued == readied: 0 ready entries.
sent, queued, readied uint16
storage []uint16
// extraCapacity keeps track of extra capacity allocated because storage is
// rounded up to the next power of 2 to speed up the math. This extra
// capacity is always notionally in the "in-flight" region of the buffer and
// is never used by the netdevice client to write valid descriptors into.
extraCapacity uint16
}
// init initializes entries with a given capacity. After Init, all the
// entries are in the "in-flight" state.
func (e *entries) init(capacity uint16) {
originalCapacity := capacity
if capacity != 0 {
// Round up to power of 2.
capacity = 1 << bits.Len16(capacity-1)
}
if capacity > (math.MaxUint16>>1)+1 {
panic(fmt.Sprintf("invalid capacity %d, must be smaller than 2^15", capacity))
}
*e = entries{
storage: make([]uint16, capacity),
extraCapacity: capacity - originalCapacity,
}
}
// mask masks an index to the range [0, capacity).
func (e *entries) mask(val uint16) uint16 {
return val & (uint16(len(e.storage)) - 1)
}
// mask2 masks an index to the range [0, capacity*2).
func (e *entries) mask2(val uint16) uint16 {
return val & ((uint16(len(e.storage)) << 1) - 1)
}
// incrementSent marks delta entries as moved from "queued" to "in-flight".
// Delta must be limited to the number of queued entries, otherwise entries may
// become inconsistent.
func (e *entries) incrementSent(delta uint16) {
e.sent = e.mask2(e.sent + delta)
}
// incrementQueued marks delta entries as moved from "ready" to "queued".
// Delta must be limited to the number of ready entries, otherwise entries may
// become inconsistent.
func (e *entries) incrementQueued(delta uint16) {
e.queued = e.mask2(e.queued + delta)
}
// incrementReadied marks delta entries as moved from "in-flight" to "ready".
// Delta must be limited to the number of in-flight buffers, which is the
// size of the range returned by GetInFlightRange.
func (e *entries) incrementReadied(delta uint16) {
e.readied = e.mask2(e.readied + delta)
}
// haveQueued returns true if there are entries in the "queued" state.
func (e *entries) haveQueued() bool {
return e.sent != e.queued
}
// haveReadied returns true if there are entries in the "ready" state.
func (e *entries) haveReadied() bool {
return e.queued != e.readied
}
// unFlight returns the number of buffers entries in flight (owned by the
// driver).
func (e *entries) inFlight() uint16 {
var inFlight uint16
if readied, sent := e.getInFlightRange(); readied < sent {
inFlight = sent - readied
} else {
inFlight = uint16(len(e.storage)) - (readied - sent)
}
// The extra capacity is always kept in the "inFlight" state so we must
// subtract it out when reporting out the numbers.
return inFlight - e.extraCapacity
}
// getInFlightRange returns the range of indices for entries in "in-flight"
// state that can be move to readied. The end of the range is always exclusive.
// If range start that is larger than or equal to the range end, it must be
// interpreted as two ranges: (start:) and (:end) as opposed to (start:end).
func (e *entries) getInFlightRange() (uint16, uint16) {
if readied, sent := e.mask(e.readied), e.mask(e.sent); readied == sent && e.sent != e.readied {
return uint16(len(e.storage)), 0
} else {
return readied, sent
}
}
// getQueuedRange returns the range of indices for entries in "queued" state.
// The end of the range is always exclusive. If range start that is larger than
// or equal to the range end, it must be interpreted as two ranges: (start:) and
// (:end) as opposed to (start:end).
func (e *entries) getQueuedRange() (uint16, uint16) {
if e.sent == e.queued {
return uint16(len(e.storage)), 0
}
return e.mask(e.sent), e.mask(e.queued)
}
// getReadied returns the first readied entry. Only valid if HaveReadied is
// true.
func (e *entries) getReadied() uint16 {
return e.storage[e.mask(e.queued)]
}
// addReadied copies the contents of a slice into as many available "in-flight"
// entries as possible, returning the number of copied items.
func (e *entries) addReadied(src []uint16) int {
if readied, sent := e.getInFlightRange(); readied < sent {
return copy(e.storage[readied:sent], src)
} else {
n := copy(e.storage[readied:], src)
n += copy(e.storage[:sent], src[n:])
return n
}
}
// getQueued copies as many queued entries as possible into the slice, returning
// the number of copied items.
func (e *entries) getQueued(dst []uint16) int {
if sent, queued := e.getQueuedRange(); sent < queued {
return copy(dst, e.storage[sent:queued])
} else {
n := copy(dst, e.storage[sent:])
n += copy(dst[n:], e.storage[:queued])
return n
}
}
// GoString implements fmt.GoStringer.
func (e entries) GoString() string {
return fmt.Sprintf("%T{cap=%d, sent=%d, queued=%d, readied=%d}", e, len(e.storage), e.sent, e.queued, e.readied)
}