summaryrefslogtreecommitdiff
path: root/arch/arm64/mm/mteswap.c
blob: 63e8d72f202a3b2848e4cf1e57d424e30d65f768 (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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// SPDX-License-Identifier: GPL-2.0-only

#include <linux/pagemap.h>
#include <linux/xarray.h>
#include <linux/slab.h>
#include <linux/swap.h>
#include <linux/swapops.h>
#include <asm/mte.h>

static DEFINE_XARRAY(mte_pages);

void *mte_allocate_tag_storage(void)
{
	/* tags granule is 16 bytes, 2 tags stored per byte */
	return kmalloc(MTE_PAGE_TAG_STORAGE, GFP_KERNEL);
}

void mte_free_tag_storage(char *storage)
{
	kfree(storage);
}

int mte_save_tags(struct page *page)
{
	void *tag_storage, *ret;

	if (!page_mte_tagged(page))
		return 0;

	tag_storage = mte_allocate_tag_storage();
	if (!tag_storage)
		return -ENOMEM;

	mte_save_page_tags(page_address(page), tag_storage);

	/* lookup the swap entry.val from the page */
	ret = xa_store(&mte_pages, page_swap_entry(page).val, tag_storage,
		       GFP_KERNEL);
	if (WARN(xa_is_err(ret), "Failed to store MTE tags")) {
		mte_free_tag_storage(tag_storage);
		return xa_err(ret);
	} else if (ret) {
		/* Entry is being replaced, free the old entry */
		mte_free_tag_storage(ret);
	}

	return 0;
}

void mte_restore_tags(swp_entry_t entry, struct page *page)
{
	void *tags = xa_load(&mte_pages, entry.val);

	if (!tags)
		return;

	if (try_page_mte_tagging(page)) {
		mte_restore_page_tags(page_address(page), tags);
		set_page_mte_tagged(page);
	}
}

void mte_invalidate_tags(int type, pgoff_t offset)
{
	swp_entry_t entry = swp_entry(type, offset);
	void *tags = xa_erase(&mte_pages, entry.val);

	mte_free_tag_storage(tags);
}

static inline void __mte_invalidate_tags(struct page *page)
{
	swp_entry_t entry = page_swap_entry(page);

	mte_invalidate_tags(swp_type(entry), swp_offset(entry));
}

void mte_invalidate_tags_area(int type)
{
	swp_entry_t entry = swp_entry(type, 0);
	swp_entry_t last_entry = swp_entry(type + 1, 0);
	void *tags;

	XA_STATE(xa_state, &mte_pages, entry.val);

	xa_lock(&mte_pages);
	xas_for_each(&xa_state, tags, last_entry.val - 1) {
		__xa_erase(&mte_pages, xa_state.xa_index);
		mte_free_tag_storage(tags);
	}
	xa_unlock(&mte_pages);
}

int arch_prepare_to_swap(struct folio *folio)
{
	long i, nr;
	int err;

	if (!system_supports_mte())
		return 0;

	nr = folio_nr_pages(folio);

	for (i = 0; i < nr; i++) {
		err = mte_save_tags(folio_page(folio, i));
		if (err)
			goto out;
	}
	return 0;

out:
	while (i--)
		__mte_invalidate_tags(folio_page(folio, i));
	return err;
}

void arch_swap_restore(swp_entry_t entry, struct folio *folio)
{
	long i, nr;

	if (!system_supports_mte())
		return;

	nr = folio_nr_pages(folio);

	for (i = 0; i < nr; i++) {
		mte_restore_tags(entry, folio_page(folio, i));
		entry.val++;
	}
}