blob: a2a5576d55629426bfd2264c511a35ef02968426 [file] [log] [blame]
// Copyright 2016 The Netstack 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 sharedmem
import (
"math"
"syscall"
"github.com/google/netstack/tcpip/link/sharedmem/queue"
)
const (
nilID = math.MaxUint64
)
// tx holds all state associated with a tx queue.
type tx struct {
data []byte
q queue.Tx
ids idManager
bufs bufferManager
}
// init initializes all state needed by the tx queue based on the information
// provided.
//
// The caller always retains ownership of all file descriptors passed in. The
// queue implementation will duplicate any that it may need in the future.
func (t *tx) init(mtu uint32, c *QueueConfig) error {
// Map in all buffers.
txPipe, err := getBuffer(c.TxPipeFD)
if err != nil {
return err
}
rxPipe, err := getBuffer(c.RxPipeFD)
if err != nil {
syscall.Munmap(txPipe)
return err
}
data, err := getBuffer(c.DataFD)
if err != nil {
syscall.Munmap(txPipe)
syscall.Munmap(rxPipe)
return err
}
// Initialize state based on buffers.
t.q.Init(txPipe, rxPipe)
t.ids.init()
t.bufs.init(0, len(data), int(mtu))
t.data = data
return nil
}
// cleanup releases all resources allocated during init(). It must only be
// called if init() has previously succeeded.
func (t *tx) cleanup() {
a, b := t.q.Bytes()
syscall.Munmap(a)
syscall.Munmap(b)
syscall.Munmap(t.data)
}
// transmit sends a packet made up of up to two buffers. Returns a boolean that
// specifies whether the packet was successfully transmitted.
func (t *tx) transmit(a, b []byte) bool {
// Pull completions from the tx queue and add their buffers back to the
// pool so that we can reuse them.
for {
id, ok := t.q.CompletedPacket()
if !ok {
break
}
if buf := t.ids.remove(id); buf != nil {
t.bufs.free(buf)
}
}
bSize := t.bufs.entrySize
total := uint32(len(a) + len(b))
bufCount := (total + bSize - 1) / bSize
// Allocate enough buffers to hold all the data.
var buf *queue.TxBuffer
for i := bufCount; i != 0; i-- {
b := t.bufs.alloc()
if b == nil {
// Failed to get all buffers. Return to the pool
// whatever we had managed to get.
if buf != nil {
t.bufs.free(buf)
}
return false
}
b.Next = buf
buf = b
}
// Copy data into allocated buffers.
nBuf := buf
var dBuf []byte
for _, data := range [][]byte{a, b} {
for len(data) > 0 {
if len(dBuf) == 0 {
dBuf = t.data[nBuf.Offset:][:nBuf.Size]
nBuf = nBuf.Next
}
n := copy(dBuf, data)
data = data[n:]
dBuf = dBuf[n:]
}
}
// Get an id for this packet and send it out.
id := t.ids.add(buf)
if !t.q.Enqueue(id, total, bufCount, buf) {
t.ids.remove(id)
t.bufs.free(buf)
return false
}
return true
}
// getBuffer returns a memory region mapped to the full contents of the given
// file descriptor.
func getBuffer(fd int) ([]byte, error) {
var s syscall.Stat_t
if err := syscall.Fstat(fd, &s); err != nil {
return nil, err
}
// Check that size doesn't overflow an int.
if s.Size > int64(^uint(0)>>1) {
return nil, syscall.EDOM
}
return syscall.Mmap(fd, 0, int(s.Size), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED|syscall.MAP_FILE)
}
// idDescriptor is used by idManager to either point to a tx buffer (in case
// the ID is assigned) or to the next free element (if the id is not assigned).
type idDescriptor struct {
buf *queue.TxBuffer
nextFree uint64
}
// idManager is a manager of tx buffer identifiers. It assigns unique IDs to
// tx buffers that are added to it; the IDs can only be reused after they have
// been removed.
//
// The ID assignments are stored so that the tx buffers can be retrieved from
// the IDs previously assigned to them.
type idManager struct {
// ids is a slice containing all tx buffers. The ID is the index into
// this slice.
ids []idDescriptor
// freeList a list of free IDs.
freeList uint64
}
// init initializes the id manager.
func (m *idManager) init() {
m.freeList = nilID
}
// add assigns an ID to the given tx buffer.
func (m *idManager) add(b *queue.TxBuffer) uint64 {
if i := m.freeList; i != nilID {
// There is an id available in the free list, just use it.
m.ids[i].buf = b
m.freeList = m.ids[i].nextFree
return i
}
// We need to expand the id descriptor.
m.ids = append(m.ids, idDescriptor{buf: b})
return uint64(len(m.ids) - 1)
}
// remove retrieves the tx buffer associated with the given ID, and removes the
// ID from the assigned table so that it can be reused in the future.
func (m *idManager) remove(i uint64) *queue.TxBuffer {
if i >= uint64(len(m.ids)) {
return nil
}
desc := &m.ids[i]
b := desc.buf
if b == nil {
// The provided id is not currently assigned.
return nil
}
desc.buf = nil
desc.nextFree = m.freeList
m.freeList = i
return b
}
// bufferManager manages a buffer region broken up into smaller, equally sized
// buffers. Smaller buffers can be allocated and freed.
type bufferManager struct {
freeList *queue.TxBuffer
curOffset uint64
limit uint64
entrySize uint32
}
// init initializes the buffer manager.
func (b *bufferManager) init(initialOffset, size, entrySize int) {
b.freeList = nil
b.curOffset = uint64(initialOffset)
b.limit = uint64(initialOffset + size/entrySize*entrySize)
b.entrySize = uint32(entrySize)
}
// alloc allocates a buffer from the manager, if one is available.
func (b *bufferManager) alloc() *queue.TxBuffer {
if b.freeList != nil {
// There is a descriptor ready for reuse in the free list.
d := b.freeList
b.freeList = d.Next
d.Next = nil
return d
}
if b.curOffset < b.limit {
// There is room available in the never-used range, so create
// a new descriptor for it.
d := &queue.TxBuffer{
Offset: b.curOffset,
Size: b.entrySize,
}
b.curOffset += uint64(b.entrySize)
return d
}
return nil
}
// free returns all buffers in the list to the buffer manager so that they can
// be reused.
func (b *bufferManager) free(d *queue.TxBuffer) {
// Find the last buffer in the list.
last := d
for last.Next != nil {
last = last.Next
}
// Push list onto free list.
last.Next = b.freeList
b.freeList = d
}