Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,30 @@
# SPDX-License-Identifier: GPL-2.0
iommu_pt_fmt-$(CONFIG_IOMMU_PT_AMDV1) += amdv1
iommu_pt_fmt-$(CONFIG_IOMMUFD_TEST) += mock
iommu_pt_fmt-$(CONFIG_IOMMU_PT_VTDSS) += vtdss
iommu_pt_fmt-$(CONFIG_IOMMU_PT_RISCV64) += riscv64
iommu_pt_fmt-$(CONFIG_IOMMU_PT_X86_64) += x86_64
IOMMU_PT_KUNIT_TEST :=
define create_format
obj-$(2) += iommu_$(1).o
iommu_pt_kunit_test-y += kunit_iommu_$(1).o
CFLAGS_kunit_iommu_$(1).o += -DGENERIC_PT_KUNIT=1
IOMMU_PT_KUNIT_TEST := iommu_pt_kunit_test.o
endef
$(eval $(foreach fmt,$(iommu_pt_fmt-y),$(call create_format,$(fmt),y)))
$(eval $(foreach fmt,$(iommu_pt_fmt-m),$(call create_format,$(fmt),m)))
# The kunit objects are constructed by compiling the main source
# with -DGENERIC_PT_KUNIT
$(obj)/kunit_iommu_%.o: $(src)/iommu_%.c FORCE
$(call rule_mkdir)
$(call if_changed_dep,cc_o_c)
obj-$(CONFIG_IOMMU_PT_KUNIT_TEST) += $(IOMMU_PT_KUNIT_TEST)
@@ -0,0 +1,413 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES
*
* AMD IOMMU v1 page table
*
* This is described in Section "2.2.3 I/O Page Tables for Host Translations"
* of the "AMD I/O Virtualization Technology (IOMMU) Specification"
*
* Note the level numbering here matches the core code, so level 0 is the same
* as mode 1.
*
*/
#ifndef __GENERIC_PT_FMT_AMDV1_H
#define __GENERIC_PT_FMT_AMDV1_H
#include "defs_amdv1.h"
#include "../pt_defs.h"
#include <asm/page.h>
#include <linux/bitfield.h>
#include <linux/container_of.h>
#include <linux/mem_encrypt.h>
#include <linux/minmax.h>
#include <linux/sizes.h>
#include <linux/string.h>
enum {
PT_ITEM_WORD_SIZE = sizeof(u64),
/*
* The IOMMUFD selftest uses the AMDv1 format with some alterations It
* uses a 2k page size to test cases where the CPU page size is not the
* same.
*/
#ifdef AMDV1_IOMMUFD_SELFTEST
PT_MAX_VA_ADDRESS_LG2 = 56,
PT_MAX_OUTPUT_ADDRESS_LG2 = 51,
PT_MAX_TOP_LEVEL = 4,
PT_GRANULE_LG2SZ = 11,
#else
PT_MAX_VA_ADDRESS_LG2 = 64,
PT_MAX_OUTPUT_ADDRESS_LG2 = 52,
PT_MAX_TOP_LEVEL = 5,
PT_GRANULE_LG2SZ = 12,
#endif
PT_TABLEMEM_LG2SZ = 12,
/* The DTE only has these bits for the top phyiscal address */
PT_TOP_PHYS_MASK = GENMASK_ULL(51, 12),
};
/* PTE bits */
enum {
AMDV1PT_FMT_PR = BIT(0),
AMDV1PT_FMT_D = BIT(6),
AMDV1PT_FMT_NEXT_LEVEL = GENMASK_ULL(11, 9),
AMDV1PT_FMT_OA = GENMASK_ULL(51, 12),
AMDV1PT_FMT_FC = BIT_ULL(60),
AMDV1PT_FMT_IR = BIT_ULL(61),
AMDV1PT_FMT_IW = BIT_ULL(62),
};
/*
* gcc 13 has a bug where it thinks the output of FIELD_GET() is an enum, make
* these defines to avoid it.
*/
#define AMDV1PT_FMT_NL_DEFAULT 0
#define AMDV1PT_FMT_NL_SIZE 7
static inline pt_oaddr_t amdv1pt_table_pa(const struct pt_state *pts)
{
u64 entry = pts->entry;
if (pts_feature(pts, PT_FEAT_AMDV1_ENCRYPT_TABLES))
entry = __sme_clr(entry);
return oalog2_mul(FIELD_GET(AMDV1PT_FMT_OA, entry), PT_GRANULE_LG2SZ);
}
#define pt_table_pa amdv1pt_table_pa
/* Returns the oa for the start of the contiguous entry */
static inline pt_oaddr_t amdv1pt_entry_oa(const struct pt_state *pts)
{
u64 entry = pts->entry;
pt_oaddr_t oa;
if (pts_feature(pts, PT_FEAT_AMDV1_ENCRYPT_TABLES))
entry = __sme_clr(entry);
oa = FIELD_GET(AMDV1PT_FMT_OA, entry);
if (FIELD_GET(AMDV1PT_FMT_NEXT_LEVEL, entry) == AMDV1PT_FMT_NL_SIZE) {
unsigned int sz_bits = oaffz(oa);
oa = oalog2_set_mod(oa, 0, sz_bits);
} else if (PT_WARN_ON(FIELD_GET(AMDV1PT_FMT_NEXT_LEVEL, entry) !=
AMDV1PT_FMT_NL_DEFAULT))
return 0;
return oalog2_mul(oa, PT_GRANULE_LG2SZ);
}
#define pt_entry_oa amdv1pt_entry_oa
static inline bool amdv1pt_can_have_leaf(const struct pt_state *pts)
{
/*
* Table 15: Page Table Level Parameters
* The top most level cannot have translation entries
*/
return pts->level < PT_MAX_TOP_LEVEL;
}
#define pt_can_have_leaf amdv1pt_can_have_leaf
/* Body in pt_fmt_defaults.h */
static inline unsigned int pt_table_item_lg2sz(const struct pt_state *pts);
static inline unsigned int
amdv1pt_entry_num_contig_lg2(const struct pt_state *pts)
{
u32 code;
if (FIELD_GET(AMDV1PT_FMT_NEXT_LEVEL, pts->entry) ==
AMDV1PT_FMT_NL_DEFAULT)
return ilog2(1);
PT_WARN_ON(FIELD_GET(AMDV1PT_FMT_NEXT_LEVEL, pts->entry) !=
AMDV1PT_FMT_NL_SIZE);
/*
* The contiguous size is encoded in the length of a string of 1's in
* the low bits of the OA. Reverse the equation:
* code = log2_to_int(num_contig_lg2 + item_lg2sz -
* PT_GRANULE_LG2SZ - 1) - 1
* Which can be expressed as:
* num_contig_lg2 = oalog2_ffz(code) + 1 -
* item_lg2sz - PT_GRANULE_LG2SZ
*
* Assume the bit layout is correct and remove the masking. Reorganize
* the equation to move all the arithmetic before the ffz.
*/
code = pts->entry >> (__bf_shf(AMDV1PT_FMT_OA) - 1 +
pt_table_item_lg2sz(pts) - PT_GRANULE_LG2SZ);
return ffz_t(u32, code);
}
#define pt_entry_num_contig_lg2 amdv1pt_entry_num_contig_lg2
static inline unsigned int amdv1pt_num_items_lg2(const struct pt_state *pts)
{
/*
* Top entry covers bits [63:57] only, this is handled through
* max_vasz_lg2.
*/
if (PT_WARN_ON(pts->level == 5))
return 7;
return PT_TABLEMEM_LG2SZ - ilog2(sizeof(u64));
}
#define pt_num_items_lg2 amdv1pt_num_items_lg2
static inline pt_vaddr_t amdv1pt_possible_sizes(const struct pt_state *pts)
{
unsigned int isz_lg2 = pt_table_item_lg2sz(pts);
if (!amdv1pt_can_have_leaf(pts))
return 0;
/*
* Table 14: Example Page Size Encodings
* Address bits 51:32 can be used to encode page sizes greater than 4
* Gbytes. Address bits 63:52 are zero-extended.
*
* 512GB Pages are not supported due to a hardware bug.
* Otherwise every power of two size is supported.
*/
return GENMASK_ULL(min(51, isz_lg2 + amdv1pt_num_items_lg2(pts) - 1),
isz_lg2) & ~SZ_512G;
}
#define pt_possible_sizes amdv1pt_possible_sizes
static inline enum pt_entry_type amdv1pt_load_entry_raw(struct pt_state *pts)
{
const u64 *tablep = pt_cur_table(pts, u64) + pts->index;
unsigned int next_level;
u64 entry;
pts->entry = entry = READ_ONCE(*tablep);
if (!(entry & AMDV1PT_FMT_PR))
return PT_ENTRY_EMPTY;
next_level = FIELD_GET(AMDV1PT_FMT_NEXT_LEVEL, pts->entry);
if (pts->level == 0 || next_level == AMDV1PT_FMT_NL_DEFAULT ||
next_level == AMDV1PT_FMT_NL_SIZE)
return PT_ENTRY_OA;
return PT_ENTRY_TABLE;
}
#define pt_load_entry_raw amdv1pt_load_entry_raw
static __always_inline void
amdv1pt_install_leaf_entry(struct pt_state *pts, pt_oaddr_t oa,
unsigned int oasz_lg2,
const struct pt_write_attrs *attrs)
{
unsigned int isz_lg2 = pt_table_item_lg2sz(pts);
u64 *tablep = pt_cur_table(pts, u64) + pts->index;
u64 entry;
if (!pt_check_install_leaf_args(pts, oa, oasz_lg2))
return;
entry = AMDV1PT_FMT_PR |
FIELD_PREP(AMDV1PT_FMT_OA, log2_div(oa, PT_GRANULE_LG2SZ)) |
attrs->descriptor_bits;
if (oasz_lg2 == isz_lg2) {
entry |= FIELD_PREP(AMDV1PT_FMT_NEXT_LEVEL,
AMDV1PT_FMT_NL_DEFAULT);
WRITE_ONCE(*tablep, entry);
} else {
unsigned int num_contig_lg2 = oasz_lg2 - isz_lg2;
u64 *end = tablep + log2_to_int(num_contig_lg2);
entry |= FIELD_PREP(AMDV1PT_FMT_NEXT_LEVEL,
AMDV1PT_FMT_NL_SIZE) |
FIELD_PREP(AMDV1PT_FMT_OA,
oalog2_to_int(oasz_lg2 - PT_GRANULE_LG2SZ -
1) -
1);
/* See amdv1pt_clear_entries() */
if (num_contig_lg2 <= ilog2(32)) {
for (; tablep != end; tablep++)
WRITE_ONCE(*tablep, entry);
} else {
memset64(tablep, entry, log2_to_int(num_contig_lg2));
}
}
pts->entry = entry;
}
#define pt_install_leaf_entry amdv1pt_install_leaf_entry
static inline bool amdv1pt_install_table(struct pt_state *pts,
pt_oaddr_t table_pa,
const struct pt_write_attrs *attrs)
{
u64 entry;
/*
* IR and IW are ANDed from the table levels along with the PTE. We
* always control permissions from the PTE, so always set IR and IW for
* tables.
*/
entry = AMDV1PT_FMT_PR |
FIELD_PREP(AMDV1PT_FMT_NEXT_LEVEL, pts->level) |
FIELD_PREP(AMDV1PT_FMT_OA,
log2_div(table_pa, PT_GRANULE_LG2SZ)) |
AMDV1PT_FMT_IR | AMDV1PT_FMT_IW;
if (pts_feature(pts, PT_FEAT_AMDV1_ENCRYPT_TABLES))
entry = __sme_set(entry);
return pt_table_install64(pts, entry);
}
#define pt_install_table amdv1pt_install_table
static inline void amdv1pt_attr_from_entry(const struct pt_state *pts,
struct pt_write_attrs *attrs)
{
attrs->descriptor_bits =
pts->entry & (AMDV1PT_FMT_FC | AMDV1PT_FMT_IR | AMDV1PT_FMT_IW);
}
#define pt_attr_from_entry amdv1pt_attr_from_entry
static inline void amdv1pt_clear_entries(struct pt_state *pts,
unsigned int num_contig_lg2)
{
u64 *tablep = pt_cur_table(pts, u64) + pts->index;
u64 *end = tablep + log2_to_int(num_contig_lg2);
/*
* gcc generates rep stos for the io-pgtable code, and this difference
* can show in microbenchmarks with larger contiguous page sizes.
* rep is slower for small cases.
*/
if (num_contig_lg2 <= ilog2(32)) {
for (; tablep != end; tablep++)
WRITE_ONCE(*tablep, 0);
} else {
memset64(tablep, 0, log2_to_int(num_contig_lg2));
}
}
#define pt_clear_entries amdv1pt_clear_entries
static inline bool amdv1pt_entry_is_write_dirty(const struct pt_state *pts)
{
unsigned int num_contig_lg2 = amdv1pt_entry_num_contig_lg2(pts);
u64 *tablep = pt_cur_table(pts, u64) +
log2_set_mod(pts->index, 0, num_contig_lg2);
u64 *end = tablep + log2_to_int(num_contig_lg2);
for (; tablep != end; tablep++)
if (READ_ONCE(*tablep) & AMDV1PT_FMT_D)
return true;
return false;
}
#define pt_entry_is_write_dirty amdv1pt_entry_is_write_dirty
static inline void amdv1pt_entry_make_write_clean(struct pt_state *pts)
{
unsigned int num_contig_lg2 = amdv1pt_entry_num_contig_lg2(pts);
u64 *tablep = pt_cur_table(pts, u64) +
log2_set_mod(pts->index, 0, num_contig_lg2);
u64 *end = tablep + log2_to_int(num_contig_lg2);
for (; tablep != end; tablep++)
WRITE_ONCE(*tablep, READ_ONCE(*tablep) & ~(u64)AMDV1PT_FMT_D);
}
#define pt_entry_make_write_clean amdv1pt_entry_make_write_clean
static inline bool amdv1pt_entry_make_write_dirty(struct pt_state *pts)
{
u64 *tablep = pt_cur_table(pts, u64) + pts->index;
u64 new = pts->entry | AMDV1PT_FMT_D;
return try_cmpxchg64(tablep, &pts->entry, new);
}
#define pt_entry_make_write_dirty amdv1pt_entry_make_write_dirty
/* --- iommu */
#include <linux/generic_pt/iommu.h>
#include <linux/iommu.h>
#define pt_iommu_table pt_iommu_amdv1
/* The common struct is in the per-format common struct */
static inline struct pt_common *common_from_iommu(struct pt_iommu *iommu_table)
{
return &container_of(iommu_table, struct pt_iommu_amdv1, iommu)
->amdpt.common;
}
static inline struct pt_iommu *iommu_from_common(struct pt_common *common)
{
return &container_of(common, struct pt_iommu_amdv1, amdpt.common)->iommu;
}
static inline int amdv1pt_iommu_set_prot(struct pt_common *common,
struct pt_write_attrs *attrs,
unsigned int iommu_prot)
{
u64 pte = 0;
if (pt_feature(common, PT_FEAT_AMDV1_FORCE_COHERENCE))
pte |= AMDV1PT_FMT_FC;
if (iommu_prot & IOMMU_READ)
pte |= AMDV1PT_FMT_IR;
if (iommu_prot & IOMMU_WRITE)
pte |= AMDV1PT_FMT_IW;
/*
* Ideally we'd have an IOMMU_ENCRYPTED flag set by higher levels to
* control this. For now if the tables use sme_set then so do the ptes.
*/
if (pt_feature(common, PT_FEAT_AMDV1_ENCRYPT_TABLES) &&
!(iommu_prot & IOMMU_MMIO))
pte = __sme_set(pte);
attrs->descriptor_bits = pte;
return 0;
}
#define pt_iommu_set_prot amdv1pt_iommu_set_prot
static inline int amdv1pt_iommu_fmt_init(struct pt_iommu_amdv1 *iommu_table,
const struct pt_iommu_amdv1_cfg *cfg)
{
struct pt_amdv1 *table = &iommu_table->amdpt;
unsigned int max_vasz_lg2 = PT_MAX_VA_ADDRESS_LG2;
if (cfg->starting_level == 0 || cfg->starting_level > PT_MAX_TOP_LEVEL)
return -EINVAL;
if (!pt_feature(&table->common, PT_FEAT_DYNAMIC_TOP) &&
cfg->starting_level != PT_MAX_TOP_LEVEL)
max_vasz_lg2 = PT_GRANULE_LG2SZ +
(PT_TABLEMEM_LG2SZ - ilog2(sizeof(u64))) *
(cfg->starting_level + 1);
table->common.max_vasz_lg2 =
min(max_vasz_lg2, cfg->common.hw_max_vasz_lg2);
table->common.max_oasz_lg2 =
min(PT_MAX_OUTPUT_ADDRESS_LG2, cfg->common.hw_max_oasz_lg2);
pt_top_set_level(&table->common, cfg->starting_level);
return 0;
}
#define pt_iommu_fmt_init amdv1pt_iommu_fmt_init
#ifndef PT_FMT_VARIANT
static inline void
amdv1pt_iommu_fmt_hw_info(struct pt_iommu_amdv1 *table,
const struct pt_range *top_range,
struct pt_iommu_amdv1_hw_info *info)
{
info->host_pt_root = virt_to_phys(top_range->top_table);
PT_WARN_ON(info->host_pt_root & ~PT_TOP_PHYS_MASK);
info->mode = top_range->top_level + 1;
}
#define pt_iommu_fmt_hw_info amdv1pt_iommu_fmt_hw_info
#endif
#if defined(GENERIC_PT_KUNIT)
static const struct pt_iommu_amdv1_cfg amdv1_kunit_fmt_cfgs[] = {
/* Matches what io_pgtable does */
[0] = { .starting_level = 2 },
[1] = { .starting_level = 2, .common.hw_max_vasz_lg2 = 32 },
};
#define kunit_fmt_cfgs amdv1_kunit_fmt_cfgs
enum { KUNIT_FMT_FEATURES = 0 };
#endif
#endif
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES
*
*/
#ifndef __GENERIC_PT_FMT_DEFS_AMDV1_H
#define __GENERIC_PT_FMT_DEFS_AMDV1_H
#include <linux/generic_pt/common.h>
#include <linux/types.h>
typedef u64 pt_vaddr_t;
typedef u64 pt_oaddr_t;
struct amdv1pt_write_attrs {
u64 descriptor_bits;
gfp_t gfp;
};
#define pt_write_attrs amdv1pt_write_attrs
#endif
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES
*
*/
#ifndef __GENERIC_PT_FMT_DEFS_RISCV_H
#define __GENERIC_PT_FMT_DEFS_RISCV_H
#include <linux/generic_pt/common.h>
#include <linux/types.h>
#ifdef PT_RISCV_32BIT
typedef u32 pt_riscv_entry_t;
#define riscvpt_write_attrs riscv32pt_write_attrs
#else
typedef u64 pt_riscv_entry_t;
#define riscvpt_write_attrs riscv64pt_write_attrs
#endif
typedef pt_riscv_entry_t pt_vaddr_t;
typedef u64 pt_oaddr_t;
struct riscvpt_write_attrs {
pt_riscv_entry_t descriptor_bits;
gfp_t gfp;
};
#define pt_write_attrs riscvpt_write_attrs
#endif
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES
*
*/
#ifndef __GENERIC_PT_FMT_DEFS_VTDSS_H
#define __GENERIC_PT_FMT_DEFS_VTDSS_H
#include <linux/generic_pt/common.h>
#include <linux/types.h>
typedef u64 pt_vaddr_t;
typedef u64 pt_oaddr_t;
struct vtdss_pt_write_attrs {
u64 descriptor_bits;
gfp_t gfp;
};
#define pt_write_attrs vtdss_pt_write_attrs
#endif
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES
*
*/
#ifndef __GENERIC_PT_FMT_DEFS_X86_64_H
#define __GENERIC_PT_FMT_DEFS_X86_64_H
#include <linux/generic_pt/common.h>
#include <linux/types.h>
typedef u64 pt_vaddr_t;
typedef u64 pt_oaddr_t;
struct x86_64_pt_write_attrs {
u64 descriptor_bits;
gfp_t gfp;
};
#define pt_write_attrs x86_64_pt_write_attrs
#endif
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES
*/
#define PT_FMT amdv1
#define PT_SUPPORTED_FEATURES \
(BIT(PT_FEAT_FULL_VA) | BIT(PT_FEAT_DYNAMIC_TOP) | \
BIT(PT_FEAT_FLUSH_RANGE) | BIT(PT_FEAT_FLUSH_RANGE_NO_GAPS) | \
BIT(PT_FEAT_AMDV1_ENCRYPT_TABLES) | \
BIT(PT_FEAT_AMDV1_FORCE_COHERENCE))
#define PT_FORCE_ENABLED_FEATURES \
(BIT(PT_FEAT_DYNAMIC_TOP) | BIT(PT_FEAT_AMDV1_ENCRYPT_TABLES) | \
BIT(PT_FEAT_AMDV1_FORCE_COHERENCE))
#include "iommu_template.h"
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES
*/
#define AMDV1_IOMMUFD_SELFTEST 1
#define PT_FMT amdv1
#define PT_FMT_VARIANT mock
#define PT_SUPPORTED_FEATURES 0
#include "iommu_template.h"
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES
*/
#define PT_FMT riscv
#define PT_FMT_VARIANT 64
#define PT_SUPPORTED_FEATURES \
(BIT(PT_FEAT_SIGN_EXTEND) | BIT(PT_FEAT_FLUSH_RANGE) | \
BIT(PT_FEAT_RISCV_SVNAPOT_64K) | \
BIT(PT_FEAT_DETAILED_GATHER))
#define PT_FORCE_ENABLED_FEATURES BIT(PT_FEAT_DETAILED_GATHER)
#include "iommu_template.h"
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES
*
* Template to build the iommu module and kunit from the format and
* implementation headers.
*
* The format should have:
* #define PT_FMT <name>
* #define PT_SUPPORTED_FEATURES (BIT(PT_FEAT_xx) | BIT(PT_FEAT_yy))
* And optionally:
* #define PT_FORCE_ENABLED_FEATURES ..
* #define PT_FMT_VARIANT <suffix>
*/
#include <linux/args.h>
#include <linux/stringify.h>
#ifdef PT_FMT_VARIANT
#define PTPFX_RAW \
CONCATENATE(CONCATENATE(PT_FMT, _), PT_FMT_VARIANT)
#else
#define PTPFX_RAW PT_FMT
#endif
#define PTPFX CONCATENATE(PTPFX_RAW, _)
#define _PT_FMT_H PT_FMT.h
#define PT_FMT_H __stringify(_PT_FMT_H)
#define _PT_DEFS_H CONCATENATE(defs_, _PT_FMT_H)
#define PT_DEFS_H __stringify(_PT_DEFS_H)
#include <linux/generic_pt/common.h>
#include PT_DEFS_H
#include "../pt_defs.h"
#include PT_FMT_H
#include "../pt_common.h"
#ifndef GENERIC_PT_KUNIT
#include "../iommu_pt.h"
#else
/*
* The makefile will compile the .c file twice, once with GENERIC_PT_KUNIT set
* which means we are building the kunit modle.
*/
#include "../kunit_generic_pt.h"
#include "../kunit_iommu_pt.h"
#endif
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES
*/
#define PT_FMT vtdss
#define PT_SUPPORTED_FEATURES \
(BIT(PT_FEAT_FLUSH_RANGE) | BIT(PT_FEAT_VTDSS_FORCE_COHERENCE) | \
BIT(PT_FEAT_VTDSS_FORCE_WRITEABLE) | BIT(PT_FEAT_DMA_INCOHERENT))
#include "iommu_template.h"
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES
*/
#define PT_FMT x86_64
#define PT_SUPPORTED_FEATURES \
(BIT(PT_FEAT_SIGN_EXTEND) | BIT(PT_FEAT_FLUSH_RANGE) | \
BIT(PT_FEAT_FLUSH_RANGE_NO_GAPS) | \
BIT(PT_FEAT_X86_64_AMD_ENCRYPT_TABLES) | BIT(PT_FEAT_DMA_INCOHERENT))
#include "iommu_template.h"
@@ -0,0 +1,322 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES
*
* RISC-V page table
*
* This is described in Sections:
* 12.3. Sv32: Page-Based 32-bit Virtual-Memory Systems
* 12.4. Sv39: Page-Based 39-bit Virtual-Memory System
* 12.5. Sv48: Page-Based 48-bit Virtual-Memory System
* 12.6. Sv57: Page-Based 57-bit Virtual-Memory System
* of the "The RISC-V Instruction Set Manual: Volume II"
*
* This includes the contiguous page extension from:
* Chapter 13. "Svnapot" Extension for NAPOT Translation Contiguity,
* Version 1.0
*
* The table format is sign extended and supports leafs in every level. The spec
* doesn't talk a lot about levels, but level here is the same as i=LEVELS-1 in
* the spec.
*/
#ifndef __GENERIC_PT_FMT_RISCV_H
#define __GENERIC_PT_FMT_RISCV_H
#include "defs_riscv.h"
#include "../pt_defs.h"
#include <linux/bitfield.h>
#include <linux/container_of.h>
#include <linux/log2.h>
#include <linux/sizes.h>
enum {
PT_ITEM_WORD_SIZE = sizeof(pt_riscv_entry_t),
#ifdef PT_RISCV_32BIT
PT_MAX_VA_ADDRESS_LG2 = 32,
PT_MAX_OUTPUT_ADDRESS_LG2 = 34,
PT_MAX_TOP_LEVEL = 1,
#else
PT_MAX_VA_ADDRESS_LG2 = 57,
PT_MAX_OUTPUT_ADDRESS_LG2 = 56,
PT_MAX_TOP_LEVEL = 4,
#endif
PT_GRANULE_LG2SZ = 12,
PT_TABLEMEM_LG2SZ = 12,
/* fsc.PPN is 44 bits wide, all PPNs are 4k aligned */
PT_TOP_PHYS_MASK = GENMASK_ULL(55, 12),
};
/* PTE bits */
enum {
RISCVPT_V = BIT(0),
RISCVPT_R = BIT(1),
RISCVPT_W = BIT(2),
RISCVPT_X = BIT(3),
RISCVPT_U = BIT(4),
RISCVPT_G = BIT(5),
RISCVPT_A = BIT(6),
RISCVPT_D = BIT(7),
RISCVPT_RSW = GENMASK(9, 8),
RISCVPT_PPN32 = GENMASK(31, 10),
RISCVPT_PPN64 = GENMASK_ULL(53, 10),
RISCVPT_PPN64_64K = GENMASK_ULL(53, 14),
RISCVPT_PBMT = GENMASK_ULL(62, 61),
RISCVPT_NC = BIT_ULL(61),
RISCVPT_IO = BIT_ULL(62),
RISCVPT_N = BIT_ULL(63),
/* Svnapot encodings for ppn[0] */
RISCVPT_PPN64_64K_SZ = BIT(13),
};
#ifdef PT_RISCV_32BIT
#define RISCVPT_PPN RISCVPT_PPN32
#define pt_riscv pt_riscv_32
#else
#define RISCVPT_PPN RISCVPT_PPN64
#define pt_riscv pt_riscv_64
#endif
#define common_to_riscvpt(common_ptr) \
container_of_const(common_ptr, struct pt_riscv, common)
#define to_riscvpt(pts) common_to_riscvpt((pts)->range->common)
static inline pt_oaddr_t riscvpt_table_pa(const struct pt_state *pts)
{
return oalog2_mul(FIELD_GET(RISCVPT_PPN, pts->entry), PT_GRANULE_LG2SZ);
}
#define pt_table_pa riscvpt_table_pa
static inline pt_oaddr_t riscvpt_entry_oa(const struct pt_state *pts)
{
if (pts_feature(pts, PT_FEAT_RISCV_SVNAPOT_64K) &&
pts->entry & RISCVPT_N) {
PT_WARN_ON(pts->level != 0);
return oalog2_mul(FIELD_GET(RISCVPT_PPN64_64K, pts->entry),
ilog2(SZ_64K));
}
return oalog2_mul(FIELD_GET(RISCVPT_PPN, pts->entry), PT_GRANULE_LG2SZ);
}
#define pt_entry_oa riscvpt_entry_oa
static inline bool riscvpt_can_have_leaf(const struct pt_state *pts)
{
return true;
}
#define pt_can_have_leaf riscvpt_can_have_leaf
/* Body in pt_fmt_defaults.h */
static inline unsigned int pt_table_item_lg2sz(const struct pt_state *pts);
static inline unsigned int
riscvpt_entry_num_contig_lg2(const struct pt_state *pts)
{
if (PT_SUPPORTED_FEATURE(PT_FEAT_RISCV_SVNAPOT_64K) &&
pts->entry & RISCVPT_N) {
PT_WARN_ON(!pts_feature(pts, PT_FEAT_RISCV_SVNAPOT_64K));
PT_WARN_ON(pts->level);
return ilog2(16);
}
return ilog2(1);
}
#define pt_entry_num_contig_lg2 riscvpt_entry_num_contig_lg2
static inline unsigned int riscvpt_num_items_lg2(const struct pt_state *pts)
{
return PT_TABLEMEM_LG2SZ - ilog2(sizeof(u64));
}
#define pt_num_items_lg2 riscvpt_num_items_lg2
static inline unsigned short
riscvpt_contig_count_lg2(const struct pt_state *pts)
{
if (pts->level == 0 && pts_feature(pts, PT_FEAT_RISCV_SVNAPOT_64K))
return ilog2(16);
return ilog2(1);
}
#define pt_contig_count_lg2 riscvpt_contig_count_lg2
static inline enum pt_entry_type riscvpt_load_entry_raw(struct pt_state *pts)
{
const pt_riscv_entry_t *tablep = pt_cur_table(pts, pt_riscv_entry_t);
pt_riscv_entry_t entry;
pts->entry = entry = READ_ONCE(tablep[pts->index]);
if (!(entry & RISCVPT_V))
return PT_ENTRY_EMPTY;
if (pts->level == 0 ||
((entry & (RISCVPT_X | RISCVPT_W | RISCVPT_R)) != 0))
return PT_ENTRY_OA;
return PT_ENTRY_TABLE;
}
#define pt_load_entry_raw riscvpt_load_entry_raw
static inline void
riscvpt_install_leaf_entry(struct pt_state *pts, pt_oaddr_t oa,
unsigned int oasz_lg2,
const struct pt_write_attrs *attrs)
{
pt_riscv_entry_t *tablep = pt_cur_table(pts, pt_riscv_entry_t);
pt_riscv_entry_t entry;
if (!pt_check_install_leaf_args(pts, oa, oasz_lg2))
return;
entry = RISCVPT_V |
FIELD_PREP(RISCVPT_PPN, log2_div(oa, PT_GRANULE_LG2SZ)) |
attrs->descriptor_bits;
if (pts_feature(pts, PT_FEAT_RISCV_SVNAPOT_64K) && pts->level == 0 &&
oasz_lg2 != PT_GRANULE_LG2SZ) {
u64 *end;
entry |= RISCVPT_N | RISCVPT_PPN64_64K_SZ;
tablep += pts->index;
end = tablep + log2_div(SZ_64K, PT_GRANULE_LG2SZ);
for (; tablep != end; tablep++)
WRITE_ONCE(*tablep, entry);
} else {
/* FIXME does riscv need this to be cmpxchg? */
WRITE_ONCE(tablep[pts->index], entry);
}
pts->entry = entry;
}
#define pt_install_leaf_entry riscvpt_install_leaf_entry
static inline bool riscvpt_install_table(struct pt_state *pts,
pt_oaddr_t table_pa,
const struct pt_write_attrs *attrs)
{
pt_riscv_entry_t entry;
entry = RISCVPT_V |
FIELD_PREP(RISCVPT_PPN, log2_div(table_pa, PT_GRANULE_LG2SZ));
return pt_table_install64(pts, entry);
}
#define pt_install_table riscvpt_install_table
static inline void riscvpt_attr_from_entry(const struct pt_state *pts,
struct pt_write_attrs *attrs)
{
attrs->descriptor_bits =
pts->entry & (RISCVPT_R | RISCVPT_W | RISCVPT_X | RISCVPT_U |
RISCVPT_G | RISCVPT_A | RISCVPT_D | RISCVPT_NC |
RISCVPT_IO);
}
#define pt_attr_from_entry riscvpt_attr_from_entry
/* --- iommu */
#include <linux/generic_pt/iommu.h>
#include <linux/iommu.h>
#define pt_iommu_table pt_iommu_riscv_64
/* The common struct is in the per-format common struct */
static inline struct pt_common *common_from_iommu(struct pt_iommu *iommu_table)
{
return &container_of(iommu_table, struct pt_iommu_table, iommu)
->riscv_64pt.common;
}
static inline struct pt_iommu *iommu_from_common(struct pt_common *common)
{
return &container_of(common, struct pt_iommu_table, riscv_64pt.common)
->iommu;
}
static inline int riscvpt_iommu_set_prot(struct pt_common *common,
struct pt_write_attrs *attrs,
unsigned int iommu_prot)
{
u64 pte;
pte = RISCVPT_A | RISCVPT_U;
if (iommu_prot & IOMMU_WRITE)
pte |= RISCVPT_W | RISCVPT_R | RISCVPT_D;
if (iommu_prot & IOMMU_READ)
pte |= RISCVPT_R;
if (!(iommu_prot & IOMMU_NOEXEC))
pte |= RISCVPT_X;
if (common->features & BIT(PT_FEAT_RISCV_SVPBMT)) {
if (iommu_prot & IOMMU_MMIO)
pte |= RISCVPT_IO;
else if (!(iommu_prot & IOMMU_CACHE))
pte |= RISCVPT_NC;
}
/* Caller must specify a supported combination of flags */
if (unlikely((pte & (RISCVPT_X | RISCVPT_W | RISCVPT_R)) == 0))
return -EOPNOTSUPP;
attrs->descriptor_bits = pte;
return 0;
}
#define pt_iommu_set_prot riscvpt_iommu_set_prot
static inline int
riscvpt_iommu_fmt_init(struct pt_iommu_riscv_64 *iommu_table,
const struct pt_iommu_riscv_64_cfg *cfg)
{
struct pt_riscv *table = &iommu_table->riscv_64pt;
switch (cfg->common.hw_max_vasz_lg2) {
case 39:
pt_top_set_level(&table->common, 2);
break;
case 48:
pt_top_set_level(&table->common, 3);
break;
case 57:
pt_top_set_level(&table->common, 4);
break;
default:
return -EINVAL;
}
table->common.max_oasz_lg2 =
min(PT_MAX_OUTPUT_ADDRESS_LG2, cfg->common.hw_max_oasz_lg2);
return 0;
}
#define pt_iommu_fmt_init riscvpt_iommu_fmt_init
static inline void
riscvpt_iommu_fmt_hw_info(struct pt_iommu_riscv_64 *table,
const struct pt_range *top_range,
struct pt_iommu_riscv_64_hw_info *info)
{
phys_addr_t top_phys = virt_to_phys(top_range->top_table);
info->ppn = oalog2_div(top_phys, PT_GRANULE_LG2SZ);
PT_WARN_ON(top_phys & ~PT_TOP_PHYS_MASK);
/*
* See Table 3. Encodings of iosatp.MODE field" for DC.tx.SXL = 0:
* 8 = Sv39 = top level 2
* 9 = Sv38 = top level 3
* 10 = Sv57 = top level 4
*/
info->fsc_iosatp_mode = top_range->top_level + 6;
}
#define pt_iommu_fmt_hw_info riscvpt_iommu_fmt_hw_info
#if defined(GENERIC_PT_KUNIT)
static const struct pt_iommu_riscv_64_cfg riscv_64_kunit_fmt_cfgs[] = {
[0] = { .common.features = BIT(PT_FEAT_RISCV_SVNAPOT_64K),
.common.hw_max_oasz_lg2 = 56,
.common.hw_max_vasz_lg2 = 39 },
[1] = { .common.features = 0,
.common.hw_max_oasz_lg2 = 56,
.common.hw_max_vasz_lg2 = 48 },
[2] = { .common.features = BIT(PT_FEAT_RISCV_SVNAPOT_64K),
.common.hw_max_oasz_lg2 = 56,
.common.hw_max_vasz_lg2 = 57 },
};
#define kunit_fmt_cfgs riscv_64_kunit_fmt_cfgs
enum {
KUNIT_FMT_FEATURES = BIT(PT_FEAT_RISCV_SVNAPOT_64K),
};
#endif
#endif
@@ -0,0 +1,285 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES
*
* Intel VT-d Second Stange 5/4 level page table
*
* This is described in
* Section "3.7 Second-Stage Translation"
* Section "9.8 Second-Stage Paging Entries"
*
* Of the "Intel Virtualization Technology for Directed I/O Architecture
* Specification".
*
* The named levels in the spec map to the pts->level as:
* Table/SS-PTE - 0
* Directory/SS-PDE - 1
* Directory Ptr/SS-PDPTE - 2
* PML4/SS-PML4E - 3
* PML5/SS-PML5E - 4
*/
#ifndef __GENERIC_PT_FMT_VTDSS_H
#define __GENERIC_PT_FMT_VTDSS_H
#include "defs_vtdss.h"
#include "../pt_defs.h"
#include <linux/bitfield.h>
#include <linux/container_of.h>
#include <linux/log2.h>
enum {
PT_MAX_OUTPUT_ADDRESS_LG2 = 52,
PT_MAX_VA_ADDRESS_LG2 = 57,
PT_ITEM_WORD_SIZE = sizeof(u64),
PT_MAX_TOP_LEVEL = 4,
PT_GRANULE_LG2SZ = 12,
PT_TABLEMEM_LG2SZ = 12,
/* SSPTPTR is 4k aligned and limited by HAW */
PT_TOP_PHYS_MASK = GENMASK_ULL(63, 12),
};
/* Shared descriptor bits */
enum {
VTDSS_FMT_R = BIT(0),
VTDSS_FMT_W = BIT(1),
VTDSS_FMT_A = BIT(8),
VTDSS_FMT_D = BIT(9),
VTDSS_FMT_SNP = BIT(11),
VTDSS_FMT_OA = GENMASK_ULL(51, 12),
};
/* PDPTE/PDE */
enum {
VTDSS_FMT_PS = BIT(7),
};
#define common_to_vtdss_pt(common_ptr) \
container_of_const(common_ptr, struct pt_vtdss, common)
#define to_vtdss_pt(pts) common_to_vtdss_pt((pts)->range->common)
static inline pt_oaddr_t vtdss_pt_table_pa(const struct pt_state *pts)
{
return oalog2_mul(FIELD_GET(VTDSS_FMT_OA, pts->entry),
PT_TABLEMEM_LG2SZ);
}
#define pt_table_pa vtdss_pt_table_pa
static inline pt_oaddr_t vtdss_pt_entry_oa(const struct pt_state *pts)
{
return oalog2_mul(FIELD_GET(VTDSS_FMT_OA, pts->entry),
PT_GRANULE_LG2SZ);
}
#define pt_entry_oa vtdss_pt_entry_oa
static inline bool vtdss_pt_can_have_leaf(const struct pt_state *pts)
{
return pts->level <= 2;
}
#define pt_can_have_leaf vtdss_pt_can_have_leaf
static inline unsigned int vtdss_pt_num_items_lg2(const struct pt_state *pts)
{
return PT_TABLEMEM_LG2SZ - ilog2(sizeof(u64));
}
#define pt_num_items_lg2 vtdss_pt_num_items_lg2
static inline enum pt_entry_type vtdss_pt_load_entry_raw(struct pt_state *pts)
{
const u64 *tablep = pt_cur_table(pts, u64);
u64 entry;
pts->entry = entry = READ_ONCE(tablep[pts->index]);
if (!entry)
return PT_ENTRY_EMPTY;
if (pts->level == 0 ||
(vtdss_pt_can_have_leaf(pts) && (pts->entry & VTDSS_FMT_PS)))
return PT_ENTRY_OA;
return PT_ENTRY_TABLE;
}
#define pt_load_entry_raw vtdss_pt_load_entry_raw
static inline void
vtdss_pt_install_leaf_entry(struct pt_state *pts, pt_oaddr_t oa,
unsigned int oasz_lg2,
const struct pt_write_attrs *attrs)
{
u64 *tablep = pt_cur_table(pts, u64);
u64 entry;
if (!pt_check_install_leaf_args(pts, oa, oasz_lg2))
return;
entry = FIELD_PREP(VTDSS_FMT_OA, log2_div(oa, PT_GRANULE_LG2SZ)) |
attrs->descriptor_bits;
if (pts->level != 0)
entry |= VTDSS_FMT_PS;
WRITE_ONCE(tablep[pts->index], entry);
pts->entry = entry;
}
#define pt_install_leaf_entry vtdss_pt_install_leaf_entry
static inline bool vtdss_pt_install_table(struct pt_state *pts,
pt_oaddr_t table_pa,
const struct pt_write_attrs *attrs)
{
u64 entry;
entry = VTDSS_FMT_R | VTDSS_FMT_W |
FIELD_PREP(VTDSS_FMT_OA, log2_div(table_pa, PT_GRANULE_LG2SZ));
return pt_table_install64(pts, entry);
}
#define pt_install_table vtdss_pt_install_table
static inline void vtdss_pt_attr_from_entry(const struct pt_state *pts,
struct pt_write_attrs *attrs)
{
attrs->descriptor_bits = pts->entry &
(VTDSS_FMT_R | VTDSS_FMT_W | VTDSS_FMT_SNP);
}
#define pt_attr_from_entry vtdss_pt_attr_from_entry
static inline bool vtdss_pt_entry_is_write_dirty(const struct pt_state *pts)
{
u64 *tablep = pt_cur_table(pts, u64) + pts->index;
return READ_ONCE(*tablep) & VTDSS_FMT_D;
}
#define pt_entry_is_write_dirty vtdss_pt_entry_is_write_dirty
static inline void vtdss_pt_entry_make_write_clean(struct pt_state *pts)
{
u64 *tablep = pt_cur_table(pts, u64) + pts->index;
WRITE_ONCE(*tablep, READ_ONCE(*tablep) & ~(u64)VTDSS_FMT_D);
}
#define pt_entry_make_write_clean vtdss_pt_entry_make_write_clean
static inline bool vtdss_pt_entry_make_write_dirty(struct pt_state *pts)
{
u64 *tablep = pt_cur_table(pts, u64) + pts->index;
u64 new = pts->entry | VTDSS_FMT_D;
return try_cmpxchg64(tablep, &pts->entry, new);
}
#define pt_entry_make_write_dirty vtdss_pt_entry_make_write_dirty
static inline unsigned int vtdss_pt_max_sw_bit(struct pt_common *common)
{
return 10;
}
#define pt_max_sw_bit vtdss_pt_max_sw_bit
static inline u64 vtdss_pt_sw_bit(unsigned int bitnr)
{
if (__builtin_constant_p(bitnr) && bitnr > 10)
BUILD_BUG();
/* Bits marked Ignored in the specification */
switch (bitnr) {
case 0:
return BIT(10);
case 1 ... 9:
return BIT_ULL((bitnr - 1) + 52);
case 10:
return BIT_ULL(63);
/* Some bits in 9-3 are available in some entries */
default:
PT_WARN_ON(true);
return 0;
}
}
#define pt_sw_bit vtdss_pt_sw_bit
/* --- iommu */
#include <linux/generic_pt/iommu.h>
#include <linux/iommu.h>
#define pt_iommu_table pt_iommu_vtdss
/* The common struct is in the per-format common struct */
static inline struct pt_common *common_from_iommu(struct pt_iommu *iommu_table)
{
return &container_of(iommu_table, struct pt_iommu_table, iommu)
->vtdss_pt.common;
}
static inline struct pt_iommu *iommu_from_common(struct pt_common *common)
{
return &container_of(common, struct pt_iommu_table, vtdss_pt.common)
->iommu;
}
static inline int vtdss_pt_iommu_set_prot(struct pt_common *common,
struct pt_write_attrs *attrs,
unsigned int iommu_prot)
{
u64 pte = 0;
/*
* VTDSS does not have a present bit, so we tell if any entry is present
* by checking for R or W.
*/
if (!(iommu_prot & (IOMMU_READ | IOMMU_WRITE)))
return -EINVAL;
if (iommu_prot & IOMMU_READ)
pte |= VTDSS_FMT_R;
if (iommu_prot & IOMMU_WRITE)
pte |= VTDSS_FMT_W;
if (pt_feature(common, PT_FEAT_VTDSS_FORCE_COHERENCE))
pte |= VTDSS_FMT_SNP;
if (pt_feature(common, PT_FEAT_VTDSS_FORCE_WRITEABLE) &&
!(iommu_prot & IOMMU_WRITE)) {
pr_err_ratelimited(
"Read-only mapping is disallowed on the domain which serves as the parent in a nested configuration, due to HW errata (ERRATA_772415_SPR17)\n");
return -EINVAL;
}
attrs->descriptor_bits = pte;
return 0;
}
#define pt_iommu_set_prot vtdss_pt_iommu_set_prot
static inline int vtdss_pt_iommu_fmt_init(struct pt_iommu_vtdss *iommu_table,
const struct pt_iommu_vtdss_cfg *cfg)
{
struct pt_vtdss *table = &iommu_table->vtdss_pt;
if (cfg->top_level > 4 || cfg->top_level < 2)
return -EOPNOTSUPP;
pt_top_set_level(&table->common, cfg->top_level);
return 0;
}
#define pt_iommu_fmt_init vtdss_pt_iommu_fmt_init
static inline void
vtdss_pt_iommu_fmt_hw_info(struct pt_iommu_vtdss *table,
const struct pt_range *top_range,
struct pt_iommu_vtdss_hw_info *info)
{
info->ssptptr = virt_to_phys(top_range->top_table);
PT_WARN_ON(info->ssptptr & ~PT_TOP_PHYS_MASK);
/*
* top_level = 2 = 3 level table aw=1
* top_level = 3 = 4 level table aw=2
* top_level = 4 = 5 level table aw=3
*/
info->aw = top_range->top_level - 1;
}
#define pt_iommu_fmt_hw_info vtdss_pt_iommu_fmt_hw_info
#if defined(GENERIC_PT_KUNIT)
static const struct pt_iommu_vtdss_cfg vtdss_kunit_fmt_cfgs[] = {
[0] = { .common.hw_max_vasz_lg2 = 39, .top_level = 2},
[1] = { .common.hw_max_vasz_lg2 = 48, .top_level = 3},
[2] = { .common.hw_max_vasz_lg2 = 57, .top_level = 4},
};
#define kunit_fmt_cfgs vtdss_kunit_fmt_cfgs
enum { KUNIT_FMT_FEATURES = BIT(PT_FEAT_VTDSS_FORCE_WRITEABLE) };
#endif
#endif
@@ -0,0 +1,280 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES
*
* x86 page table. Supports the 4 and 5 level variations.
*
* The 4 and 5 level version is described in:
* Section "4.4 4-Level Paging and 5-Level Paging" of the Intel Software
* Developer's Manual Volume 3
*
* Section "9.7 First-Stage Paging Entries" of the "Intel Virtualization
* Technology for Directed I/O Architecture Specification"
*
* Section "2.2.6 I/O Page Tables for Guest Translations" of the "AMD I/O
* Virtualization Technology (IOMMU) Specification"
*
* It is used by x86 CPUs, AMD and VT-d IOMMU HW.
*
* Note the 3 level format is very similar and almost implemented here. The
* reserved/ignored layout is different and there are functional bit
* differences.
*
* This format uses PT_FEAT_SIGN_EXTEND to have a upper/non-canonical/lower
* split. PT_FEAT_SIGN_EXTEND is optional as AMD IOMMU sometimes uses non-sign
* extended addressing with this page table format.
*
* The named levels in the spec map to the pts->level as:
* Table/PTE - 0
* Directory/PDE - 1
* Directory Ptr/PDPTE - 2
* PML4/PML4E - 3
* PML5/PML5E - 4
*/
#ifndef __GENERIC_PT_FMT_X86_64_H
#define __GENERIC_PT_FMT_X86_64_H
#include "defs_x86_64.h"
#include "../pt_defs.h"
#include <linux/bitfield.h>
#include <linux/container_of.h>
#include <linux/log2.h>
#include <linux/mem_encrypt.h>
enum {
PT_MAX_OUTPUT_ADDRESS_LG2 = 52,
PT_MAX_VA_ADDRESS_LG2 = 57,
PT_ITEM_WORD_SIZE = sizeof(u64),
PT_MAX_TOP_LEVEL = 4,
PT_GRANULE_LG2SZ = 12,
PT_TABLEMEM_LG2SZ = 12,
/*
* For AMD the GCR3 Base only has these bits. For VT-d FSPTPTR is 4k
* aligned and is limited by the architected HAW
*/
PT_TOP_PHYS_MASK = GENMASK_ULL(51, 12),
};
/* Shared descriptor bits */
enum {
X86_64_FMT_P = BIT(0),
X86_64_FMT_RW = BIT(1),
X86_64_FMT_U = BIT(2),
X86_64_FMT_A = BIT(5),
X86_64_FMT_D = BIT(6),
X86_64_FMT_OA = GENMASK_ULL(51, 12),
X86_64_FMT_XD = BIT_ULL(63),
};
/* PDPTE/PDE */
enum {
X86_64_FMT_PS = BIT(7),
};
static inline pt_oaddr_t x86_64_pt_table_pa(const struct pt_state *pts)
{
u64 entry = pts->entry;
if (pts_feature(pts, PT_FEAT_X86_64_AMD_ENCRYPT_TABLES))
entry = __sme_clr(entry);
return oalog2_mul(FIELD_GET(X86_64_FMT_OA, entry),
PT_TABLEMEM_LG2SZ);
}
#define pt_table_pa x86_64_pt_table_pa
static inline pt_oaddr_t x86_64_pt_entry_oa(const struct pt_state *pts)
{
u64 entry = pts->entry;
if (pts_feature(pts, PT_FEAT_X86_64_AMD_ENCRYPT_TABLES))
entry = __sme_clr(entry);
return oalog2_mul(FIELD_GET(X86_64_FMT_OA, entry),
PT_GRANULE_LG2SZ);
}
#define pt_entry_oa x86_64_pt_entry_oa
static inline bool x86_64_pt_can_have_leaf(const struct pt_state *pts)
{
return pts->level <= 2;
}
#define pt_can_have_leaf x86_64_pt_can_have_leaf
static inline unsigned int x86_64_pt_num_items_lg2(const struct pt_state *pts)
{
return PT_TABLEMEM_LG2SZ - ilog2(sizeof(u64));
}
#define pt_num_items_lg2 x86_64_pt_num_items_lg2
static inline enum pt_entry_type x86_64_pt_load_entry_raw(struct pt_state *pts)
{
const u64 *tablep = pt_cur_table(pts, u64);
u64 entry;
pts->entry = entry = READ_ONCE(tablep[pts->index]);
if (!(entry & X86_64_FMT_P))
return PT_ENTRY_EMPTY;
if (pts->level == 0 ||
(x86_64_pt_can_have_leaf(pts) && (entry & X86_64_FMT_PS)))
return PT_ENTRY_OA;
return PT_ENTRY_TABLE;
}
#define pt_load_entry_raw x86_64_pt_load_entry_raw
static inline void
x86_64_pt_install_leaf_entry(struct pt_state *pts, pt_oaddr_t oa,
unsigned int oasz_lg2,
const struct pt_write_attrs *attrs)
{
u64 *tablep = pt_cur_table(pts, u64);
u64 entry;
if (!pt_check_install_leaf_args(pts, oa, oasz_lg2))
return;
entry = X86_64_FMT_P |
FIELD_PREP(X86_64_FMT_OA, log2_div(oa, PT_GRANULE_LG2SZ)) |
attrs->descriptor_bits;
if (pts->level != 0)
entry |= X86_64_FMT_PS;
WRITE_ONCE(tablep[pts->index], entry);
pts->entry = entry;
}
#define pt_install_leaf_entry x86_64_pt_install_leaf_entry
static inline bool x86_64_pt_install_table(struct pt_state *pts,
pt_oaddr_t table_pa,
const struct pt_write_attrs *attrs)
{
u64 entry;
entry = X86_64_FMT_P | X86_64_FMT_RW | X86_64_FMT_U | X86_64_FMT_A |
FIELD_PREP(X86_64_FMT_OA, log2_div(table_pa, PT_GRANULE_LG2SZ));
if (pts_feature(pts, PT_FEAT_X86_64_AMD_ENCRYPT_TABLES))
entry = __sme_set(entry);
return pt_table_install64(pts, entry);
}
#define pt_install_table x86_64_pt_install_table
static inline void x86_64_pt_attr_from_entry(const struct pt_state *pts,
struct pt_write_attrs *attrs)
{
attrs->descriptor_bits = pts->entry &
(X86_64_FMT_RW | X86_64_FMT_U | X86_64_FMT_A |
X86_64_FMT_D | X86_64_FMT_XD);
}
#define pt_attr_from_entry x86_64_pt_attr_from_entry
static inline unsigned int x86_64_pt_max_sw_bit(struct pt_common *common)
{
return 12;
}
#define pt_max_sw_bit x86_64_pt_max_sw_bit
static inline u64 x86_64_pt_sw_bit(unsigned int bitnr)
{
if (__builtin_constant_p(bitnr) && bitnr > 12)
BUILD_BUG();
/* Bits marked Ignored/AVL in the specification */
switch (bitnr) {
case 0:
return BIT(9);
case 1:
return BIT(11);
case 2 ... 12:
return BIT_ULL((bitnr - 2) + 52);
/* Some bits in 8,6,4,3 are available in some entries */
default:
PT_WARN_ON(true);
return 0;
}
}
#define pt_sw_bit x86_64_pt_sw_bit
/* --- iommu */
#include <linux/generic_pt/iommu.h>
#include <linux/iommu.h>
#define pt_iommu_table pt_iommu_x86_64
/* The common struct is in the per-format common struct */
static inline struct pt_common *common_from_iommu(struct pt_iommu *iommu_table)
{
return &container_of(iommu_table, struct pt_iommu_table, iommu)
->x86_64_pt.common;
}
static inline struct pt_iommu *iommu_from_common(struct pt_common *common)
{
return &container_of(common, struct pt_iommu_table, x86_64_pt.common)
->iommu;
}
static inline int x86_64_pt_iommu_set_prot(struct pt_common *common,
struct pt_write_attrs *attrs,
unsigned int iommu_prot)
{
u64 pte;
pte = X86_64_FMT_U | X86_64_FMT_A;
if (iommu_prot & IOMMU_WRITE)
pte |= X86_64_FMT_RW | X86_64_FMT_D;
/*
* Ideally we'd have an IOMMU_ENCRYPTED flag set by higher levels to
* control this. For now if the tables use sme_set then so do the ptes.
*/
if (pt_feature(common, PT_FEAT_X86_64_AMD_ENCRYPT_TABLES) &&
!(iommu_prot & IOMMU_MMIO))
pte = __sme_set(pte);
attrs->descriptor_bits = pte;
return 0;
}
#define pt_iommu_set_prot x86_64_pt_iommu_set_prot
static inline int
x86_64_pt_iommu_fmt_init(struct pt_iommu_x86_64 *iommu_table,
const struct pt_iommu_x86_64_cfg *cfg)
{
struct pt_x86_64 *table = &iommu_table->x86_64_pt;
if (cfg->top_level < 3 || cfg->top_level > 4)
return -EOPNOTSUPP;
pt_top_set_level(&table->common, cfg->top_level);
table->common.max_oasz_lg2 =
min(PT_MAX_OUTPUT_ADDRESS_LG2, cfg->common.hw_max_oasz_lg2);
return 0;
}
#define pt_iommu_fmt_init x86_64_pt_iommu_fmt_init
static inline void
x86_64_pt_iommu_fmt_hw_info(struct pt_iommu_x86_64 *table,
const struct pt_range *top_range,
struct pt_iommu_x86_64_hw_info *info)
{
info->gcr3_pt = virt_to_phys(top_range->top_table);
PT_WARN_ON(info->gcr3_pt & ~PT_TOP_PHYS_MASK);
info->levels = top_range->top_level + 1;
}
#define pt_iommu_fmt_hw_info x86_64_pt_iommu_fmt_hw_info
#if defined(GENERIC_PT_KUNIT)
static const struct pt_iommu_x86_64_cfg x86_64_kunit_fmt_cfgs[] = {
[0] = { .common.features = BIT(PT_FEAT_SIGN_EXTEND),
.common.hw_max_vasz_lg2 = 48, .top_level = 3 },
[1] = { .common.features = BIT(PT_FEAT_SIGN_EXTEND),
.common.hw_max_vasz_lg2 = 57, .top_level = 4 },
/* AMD IOMMU PASID 0 formats with no SIGN_EXTEND */
[2] = { .common.hw_max_vasz_lg2 = 47, .top_level = 3 },
[3] = { .common.hw_max_vasz_lg2 = 56, .top_level = 4},
};
#define kunit_fmt_cfgs x86_64_kunit_fmt_cfgs
enum { KUNIT_FMT_FEATURES = BIT(PT_FEAT_SIGN_EXTEND)};
#endif
#endif