// Copyright 2018 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.

package pagetables

import (
	"gvisor.dev/gvisor/pkg/sync"
)

// PCIDs is a simple PCID database.
//
// This is not protected by locks and is thus suitable for use only with a
// single CPU at a time.
type PCIDs struct {
	// mu protects below.
	mu sync.Mutex

	// cache are the assigned page tables.
	cache map[*PageTables]uint16

	// avail are available PCIDs.
	avail []uint16
}

// NewPCIDs returns a new PCID database.
//
// start is the first index to assign. Typically this will be one, as the zero
// pcid will always be flushed on transition (see pagetables_x86.go). This may
// be more than one if specific PCIDs are reserved.
//
// Nil is returned iff the start and size are out of range.
func NewPCIDs(start, size uint16) *PCIDs {
	if start+uint16(size) > limitPCID {
		return nil // See comment.
	}
	p := &PCIDs{
		cache: make(map[*PageTables]uint16),
	}
	for pcid := start; pcid < start+size; pcid++ {
		p.avail = append(p.avail, pcid)
	}
	return p
}

// Assign assigns a PCID to the given PageTables.
//
// This may overwrite any previous assignment provided. If this in the case,
// true is returned to indicate that the PCID should be flushed.
func (p *PCIDs) Assign(pt *PageTables) (uint16, bool) {
	p.mu.Lock()
	if pcid, ok := p.cache[pt]; ok {
		p.mu.Unlock()
		return pcid, false // No flush.
	}

	// Is there something available?
	if len(p.avail) > 0 {
		pcid := p.avail[len(p.avail)-1]
		p.avail = p.avail[:len(p.avail)-1]
		p.cache[pt] = pcid

		// We need to flush because while this is in the available
		// pool, it may have been used previously.
		p.mu.Unlock()
		return pcid, true
	}

	// Evict an existing table.
	for old, pcid := range p.cache {
		delete(p.cache, old)
		p.cache[pt] = pcid

		// A flush is definitely required in this case, these page
		// tables may still be active. (They will just be assigned some
		// other PCID if and when they hit the given CPU again.)
		p.mu.Unlock()
		return pcid, true
	}

	// No PCID.
	p.mu.Unlock()
	return 0, false
}

// Drop drops references to a set of page tables.
func (p *PCIDs) Drop(pt *PageTables) {
	p.mu.Lock()
	if pcid, ok := p.cache[pt]; ok {
		delete(p.cache, pt)
		p.avail = append(p.avail, pcid)
	}
	p.mu.Unlock()
}
