summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/platform/ring0/pagetables/pcids.go
blob: 9206030bf98a5d4d95dd53a0c9b2a58c6b8707a7 (plain)
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
// 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"
)

// limitPCID is the number of valid PCIDs.
const limitPCID = 4096

// 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()
}