From 4790bc03fc65d59481e2201beb4ae1fc6d43ed62 Mon Sep 17 00:00:00 2001
From: Johannes Schlatow <johannes.schlatow@genode-labs.com>
Date: Thu, 21 Mar 2024 09:49:53 +0100
Subject: [PATCH] platform/pc: move register-based invalidation

This is in preparation of implementing the queued-invalidation
interface.

genodelabs/genode#5066
---
 .../driver/platform/pc/intel/invalidator.cc   | 120 ++++++++++++++++
 .../driver/platform/pc/intel/invalidator.h    | 130 ++++++++++++++++++
 .../pc/src/driver/platform/pc/intel/io_mmu.cc | 122 +++-------------
 .../pc/src/driver/platform/pc/intel/io_mmu.h  |  67 +--------
 .../driver/platform/pc/spec/x86_64/target.mk  |   1 +
 5 files changed, 276 insertions(+), 164 deletions(-)
 create mode 100644 repos/pc/src/driver/platform/pc/intel/invalidator.cc
 create mode 100644 repos/pc/src/driver/platform/pc/intel/invalidator.h

diff --git a/repos/pc/src/driver/platform/pc/intel/invalidator.cc b/repos/pc/src/driver/platform/pc/intel/invalidator.cc
new file mode 100644
index 0000000000..dfa9ee9641
--- /dev/null
+++ b/repos/pc/src/driver/platform/pc/intel/invalidator.cc
@@ -0,0 +1,120 @@
+/*
+ * \brief  Intel IOMMU invalidation interfaces
+ * \author Johannes Schlatow
+ * \date   2024-03-21
+ */
+
+/*
+ * Copyright (C) 2024 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+/* local includes */
+#include <intel/invalidator.h>
+#include <intel/io_mmu.h>
+
+/**
+ * Clear IOTLB.
+ *
+ * By default, we perform a global invalidation. When provided with a valid
+ * Domain_id, a domain-specific invalidation is conducted.
+ *
+ * See Table 25 for required invalidation scopes.
+ */
+void Intel::Register_invalidator::invalidate_iotlb(Domain_id domain_id)
+{
+	using Context_command = Context_mmio::Context_command;
+	using Iotlb           = Iotlb_mmio::Iotlb;
+
+	unsigned requested_scope = Context_command::Cirg::GLOBAL;
+	if (domain_id.valid())
+		requested_scope = Context_command::Cirg::DOMAIN;
+
+	/* wait for ongoing invalidation request to be completed */
+	while (_iotlb_mmio.read<Iotlb::Invalidate>());
+
+	/* invalidate IOTLB */
+	_iotlb_mmio.write<Iotlb>(Iotlb::Invalidate::bits(1) |
+	                         Iotlb::Iirg::bits(requested_scope) |
+	                         Iotlb::Dr::bits(1) | Iotlb::Dw::bits(1) |
+	                         Iotlb::Did::bits(domain_id.value));
+
+	/* wait for completion */
+	while (_iotlb_mmio.read<Iotlb::Invalidate>());
+
+	/* check for errors */
+	unsigned actual_scope = _iotlb_mmio.read<Iotlb::Iaig>();
+	if (!actual_scope)
+		error("IOTLB invalidation failed (scope=", requested_scope, ")");
+	else if (_verbose && actual_scope < requested_scope)
+		warning("Performed IOTLB invalidation with different granularity ",
+		        "(requested=", requested_scope, ", actual=", actual_scope, ")");
+
+	/*
+	 * Note: At the moment we have no practical benefit from implementing
+	 * page-selective invalidation, because
+	 * a) When adding a DMA buffer range, invalidation is only required if
+	 *    caching mode is set. This is not supposed to occur on real hardware but
+	 *    only in emulators.
+	 * b) Removal of DMA buffer ranges typically occurs only when a domain is
+	 *    destructed. In this case, invalidation is not issued for individual
+	 *    buffers but for the entire domain once all buffer ranges have been
+	 *    removed.
+	 * c) We do not use the register-based invalidation interface if queued
+	 *    invalidation is available.
+	 */
+}
+
+
+/**
+ * Clear context cache
+ *
+ * By default, we perform a global invalidation. When provided with a valid
+ * Domain_id, a domain-specific invalidation is conducted. When a rid is 
+ * provided, a device-specific invalidation is done.
+ *
+ * See Table 25 for required invalidation scopes.
+ */
+void Intel::Register_invalidator::invalidate_context(Domain_id domain_id, Pci::rid_t rid)
+{
+	using Context_command = Context_mmio::Context_command;
+
+	/* make sure that there is no context invalidation ongoing */
+	while (_context_mmio.read<Context_command::Invalidate>());
+
+	unsigned requested_scope = Context_command::Cirg::GLOBAL;
+	if (domain_id.valid())
+		requested_scope = Context_command::Cirg::DOMAIN;
+
+	if (rid != 0)
+		requested_scope = Context_command::Cirg::DEVICE;
+
+	/* clear context cache */
+	_context_mmio.write<Context_command>(Context_command::Invalidate::bits(1) |
+	                                     Context_command::Cirg::bits(requested_scope) |
+	                                     Context_command::Sid::bits(rid) |
+	                                     Context_command::Did::bits(domain_id.value));
+
+	/* wait for completion */
+	while (_context_mmio.read<Context_command::Invalidate>());
+
+	/* check for errors */
+	unsigned actual_scope = _context_mmio.read<Context_command::Caig>();
+	if (!actual_scope)
+		error("Context-cache invalidation failed (scope=", requested_scope, ")");
+	else if (_verbose && actual_scope < requested_scope)
+		warning("Performed context-cache invalidation with different granularity ",
+		        "(requested=", requested_scope, ", actual=", actual_scope, ")");
+}
+
+
+void Intel::Register_invalidator::invalidate_all(Domain_id domain_id, Pci::rid_t rid)
+{
+	invalidate_context(domain_id, rid);
+
+	/* XXX clear PASID cache if we ever switch from legacy mode translation */
+
+	invalidate_iotlb(domain_id);
+}
diff --git a/repos/pc/src/driver/platform/pc/intel/invalidator.h b/repos/pc/src/driver/platform/pc/intel/invalidator.h
new file mode 100644
index 0000000000..3cb5ea3978
--- /dev/null
+++ b/repos/pc/src/driver/platform/pc/intel/invalidator.h
@@ -0,0 +1,130 @@
+/*
+ * \brief  Intel IOMMU invalidation interfaces
+ * \author Johannes Schlatow
+ * \date   2024-03-21
+ */
+
+/*
+ * Copyright (C) 2024 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#ifndef _SRC__DRIVERS__PLATFORM__INTEL__INVALIDATOR_H_
+#define _SRC__DRIVERS__PLATFORM__INTEL__INVALIDATOR_H_
+
+/* Genode includes */
+#include <util/mmio.h>
+#include <pci/types.h>
+
+/* local includes */
+#include <intel/domain_allocator.h>
+
+namespace Intel {
+	using namespace Genode;
+
+	class Io_mmu; /* forward declaration */
+	class Invalidator;
+	class Register_invalidator;
+}
+
+
+class Intel::Invalidator
+{
+	public:
+
+		virtual ~Invalidator() { }
+
+		virtual void invalidate_iotlb(Domain_id) = 0;
+		virtual void invalidate_context(Domain_id domain, Pci::rid_t) = 0;
+		virtual void invalidate_all(Domain_id domain = Domain_id { Domain_id::INVALID },
+		                            Pci::rid_t = 0) = 0;
+};
+
+
+class Intel::Register_invalidator : public Invalidator
+{
+	private:
+
+		struct Context_mmio : Mmio<8>
+		{
+			struct Context_command : Register<0x0, 64>
+			{
+				struct Invalidate : Bitfield<63,1> { };
+
+				/* invalidation request granularity */
+				struct Cirg       : Bitfield<61,2>
+				{
+					enum {
+						GLOBAL = 0x1,
+						DOMAIN = 0x2,
+						DEVICE = 0x3
+					};
+				};
+
+				/* actual invalidation granularity */
+				struct Caig      : Bitfield<59,2> { };
+
+				/* source id */
+				struct Sid       : Bitfield<16,16> { };
+
+				/* domain id */
+				struct Did       : Bitfield<0,16> { };
+			};
+
+			Context_mmio(Byte_range_ptr const &range)
+			: Mmio<8>(range)
+			{ }
+		} _context_mmio;
+
+		struct Iotlb_mmio : Mmio<16>
+		{
+			struct Iotlb : Register<0x8, 64>
+			{
+				struct Invalidate : Bitfield<63,1> { };
+
+				/* IOTLB invalidation request granularity */
+				struct Iirg : Bitfield<60,2>
+				{
+					enum {
+						GLOBAL = 0x1,
+						DOMAIN = 0x2,
+						DEVICE = 0x3
+					};
+				};
+
+				/* IOTLB actual invalidation granularity */
+				struct Iaig : Bitfield<57,2> { };
+
+				/* drain reads/writes */
+				struct Dr   : Bitfield<49,1> { };
+				struct Dw   : Bitfield<48,1> { };
+
+				/* domain id */
+				struct Did  : Bitfield<32,16> { };
+			};
+
+			Iotlb_mmio(Byte_range_ptr const &range)
+			: Mmio<16>(range)
+			{ }
+		} _iotlb_mmio;
+
+		bool            _verbose;
+
+	public:
+
+		void invalidate_iotlb(Domain_id) override;
+		void invalidate_context(Domain_id domain, Pci::rid_t) override;
+		void invalidate_all(Domain_id domain = Domain_id { Domain_id::INVALID },
+		                    Pci::rid_t = 0) override;
+
+		Register_invalidator(addr_t context_reg_base, addr_t iotlb_reg_base, bool verbose)
+		: _context_mmio({(char*)context_reg_base, 8}),
+		  _iotlb_mmio  ({(char*)iotlb_reg_base,  16}),
+		  _verbose(verbose)
+		{ }
+};
+
+
+#endif /* _SRC__DRIVERS__PLATFORM__INTEL__INVALIDATOR_H_ */
diff --git a/repos/pc/src/driver/platform/pc/intel/io_mmu.cc b/repos/pc/src/driver/platform/pc/intel/io_mmu.cc
index 1c50d5ae4d..4033f17459 100644
--- a/repos/pc/src/driver/platform/pc/intel/io_mmu.cc
+++ b/repos/pc/src/driver/platform/pc/intel/io_mmu.cc
@@ -43,9 +43,9 @@ void Intel::Io_mmu::Domain<TABLE>::enable_pci_device(Io_mem_dataspace_capability
 	 * unless invalidation takes place.
 	 */
 	if (cur_domain.valid())
-		_intel_iommu.invalidate_all(cur_domain, Pci::Bdf::rid(bdf));
+		_intel_iommu.invalidator().invalidate_all(cur_domain, Pci::Bdf::rid(bdf));
 	else if (_intel_iommu.caching_mode())
-		_intel_iommu.invalidate_context(Domain_id(), Pci::Bdf::rid(bdf));
+		_intel_iommu.invalidator().invalidate_context(Domain_id(), Pci::Bdf::rid(bdf));
 	else
 		_intel_iommu.flush_write_buffer();
 }
@@ -59,7 +59,7 @@ void Intel::Io_mmu::Domain<TABLE>::disable_pci_device(Pci::Bdf const & bdf)
 	/* lookup default mappings and insert instead */
 	_intel_iommu.apply_default_mappings(bdf);
 
-	_intel_iommu.invalidate_all(_domain_id);
+	_intel_iommu.invalidator().invalidate_all(_domain_id);
 }
 
 
@@ -97,7 +97,7 @@ void Intel::Io_mmu::Domain<TABLE>::add_range(Range const & range,
 
 	/* only invalidate iotlb if failed requests are cached */
 	if (_intel_iommu.caching_mode())
-		_intel_iommu.invalidate_iotlb(_domain_id, vaddr, size);
+		_intel_iommu.invalidator().invalidate_iotlb(_domain_id);
 	else
 		_intel_iommu.flush_write_buffer();
 }
@@ -111,7 +111,7 @@ void Intel::Io_mmu::Domain<TABLE>::remove_range(Range const & range)
 	                                      !_intel_iommu.coherent_page_walk());
 
 	if (!_skip_invalidation)
-		_intel_iommu.invalidate_iotlb(_domain_id, range.start, range.size);
+		_intel_iommu.invalidator().invalidate_iotlb(_domain_id);
 }
 
 
@@ -136,104 +136,9 @@ void Intel::Io_mmu::flush_write_buffer()
 }
 
 
-/**
- * Clear IOTLB.
- *
- * By default, we perform a global invalidation. When provided with a valid
- * Domain_id, a domain-specific invalidation is conducted. If provided with
- * a DMA address and size, a page-selective invalidation is performed.
- *
- * See Table 25 for required invalidation scopes.
- */
-void Intel::Io_mmu::invalidate_iotlb(Domain_id domain_id, addr_t, size_t)
+Intel::Invalidator & Intel::Io_mmu::invalidator()
 {
-	unsigned requested_scope = Context_command::Cirg::GLOBAL;
-	if (domain_id.valid())
-		requested_scope = Context_command::Cirg::DOMAIN;
-
-	/* wait for ongoing invalidation request to be completed */
-	while (Iotlb::Invalidate::get(read_iotlb_reg()));
-
-	/* invalidate IOTLB */
-	write_iotlb_reg(Iotlb::Invalidate::bits(1) |
-	                Iotlb::Iirg::bits(requested_scope) |
-	                Iotlb::Dr::bits(1) | Iotlb::Dw::bits(1) |
-	                Iotlb::Did::bits(domain_id.value));
-
-	/* wait for completion */
-	while (Iotlb::Invalidate::get(read_iotlb_reg()));
-
-	/* check for errors */
-	unsigned actual_scope = Iotlb::Iaig::get(read_iotlb_reg());
-	if (!actual_scope)
-		error("IOTLB invalidation failed (scope=", requested_scope, ")");
-	else if (_verbose && actual_scope < requested_scope)
-		warning("Performed IOTLB invalidation with different granularity ",
-		        "(requested=", requested_scope, ", actual=", actual_scope, ")");
-
-	/* XXX implement page-selective-within-domain IOTLB invalidation */
-}
-
-/**
- * Clear context cache
- *
- * By default, we perform a global invalidation. When provided with a valid
- * Domain_id, a domain-specific invalidation is conducted. When a rid is 
- * provided, a device-specific invalidation is done.
- *
- * See Table 25 for required invalidation scopes.
- */
-void Intel::Io_mmu::invalidate_context(Domain_id domain_id, Pci::rid_t rid)
-{
-	/**
-	 * We are using the register-based invalidation interface for the
-	 * moment. This is only supported in legacy mode and for major
-	 * architecture version 5 and lower (cf. 6.5).
-	 */
-
-	if (read<Version::Major>() > 5) {
-		error("Unable to invalidate caches: Register-based invalidation only ",
-		      "supported in architecture versions 5 and lower");
-		return;
-	}
-
-	/* make sure that there is no context invalidation ongoing */
-	while (read<Context_command::Invalidate>());
-
-	unsigned requested_scope = Context_command::Cirg::GLOBAL;
-	if (domain_id.valid())
-		requested_scope = Context_command::Cirg::DOMAIN;
-
-	if (rid != 0)
-		requested_scope = Context_command::Cirg::DEVICE;
-
-	/* clear context cache */
-	write<Context_command>(Context_command::Invalidate::bits(1) |
-	                       Context_command::Cirg::bits(requested_scope) |
-	                       Context_command::Sid::bits(rid) |
-	                       Context_command::Did::bits(domain_id.value));
-
-
-	/* wait for completion */
-	while (read<Context_command::Invalidate>());
-
-	/* check for errors */
-	unsigned actual_scope = read<Context_command::Caig>();
-	if (!actual_scope)
-		error("Context-cache invalidation failed (scope=", requested_scope, ")");
-	else if (_verbose && actual_scope < requested_scope)
-		warning("Performed context-cache invalidation with different granularity ",
-		        "(requested=", requested_scope, ", actual=", actual_scope, ")");
-}
-
-
-void Intel::Io_mmu::invalidate_all(Domain_id domain_id, Pci::rid_t rid)
-{
-	invalidate_context(domain_id, rid);
-
-	/* XXX clear PASID cache if we ever switch from legacy mode translation */
-
-	invalidate_iotlb(domain_id, 0, 0);
+	return *_register_invalidator;
 }
 
 
@@ -406,7 +311,7 @@ void Intel::Io_mmu::default_mappings_complete()
 
 	/* caches must be cleared if Esrtps is not set (see 6.6) */
 	if (!read<Capability::Esrtps>())
-		invalidate_all();
+		invalidator().invalidate_all();
 
 	/* enable IOMMU */
 	if (!read<Global_status::Enabled>())
@@ -443,7 +348,7 @@ void Intel::Io_mmu::resume()
 	_global_command<Global_command::Srtp>(1);
 
 	if (!read<Capability::Esrtps>())
-		invalidate_all();
+		invalidator().invalidate_all();
 
 	/* enable IOMMU */
 	if (!read<Global_status::Enabled>())
@@ -481,6 +386,15 @@ Intel::Io_mmu::Io_mmu(Env                      & env,
 		/* disable queued invalidation interface */
 		if (read<Global_status::Qies>())
 			_global_command<Global_command::Qie>(false);
+
+		if (read<Version::Major>() > 5) {
+			error("Register-based invalidation only ",
+			      "supported in architecture versions 5 and lower");
+		}
+
+		addr_t context_reg_base = base() + 0x28;
+		addr_t iotlb_reg_base   = base() + 8*_offset<Extended_capability::Iro>();
+		_register_invalidator.construct(context_reg_base, iotlb_reg_base, _verbose);
 	}
 
 	/* enable fault event interrupts (if not already enabled by kernel) */
diff --git a/repos/pc/src/driver/platform/pc/intel/io_mmu.h b/repos/pc/src/driver/platform/pc/intel/io_mmu.h
index 2760073d81..a2da53ee19 100644
--- a/repos/pc/src/driver/platform/pc/intel/io_mmu.h
+++ b/repos/pc/src/driver/platform/pc/intel/io_mmu.h
@@ -31,6 +31,7 @@
 #include <intel/page_table.h>
 #include <intel/domain_allocator.h>
 #include <intel/default_mappings.h>
+#include <intel/invalidator.h>
 #include <expanding_page_table_allocator.h>
 
 namespace Intel {
@@ -50,6 +51,8 @@ class Intel::Io_mmu : private Attached_mmio<0x800>,
 {
 	public:
 
+		friend class Register_invalidator;
+
 		/* Use derived domain class to store reference to buffer registry */
 		template <typename TABLE>
 		class Domain : public Driver::Io_mmu::Domain,
@@ -92,7 +95,7 @@ class Intel::Io_mmu : private Attached_mmio<0x800>,
 						_domain._skip_invalidation = false;
 
 						if (_requires_invalidation)
-							_domain._intel_iommu.invalidate_all(_domain._domain_id);
+							_domain._intel_iommu.invalidator().invalidate_all(_domain._domain_id);
 						else
 							_domain._intel_iommu.flush_write_buffer();
 					}
@@ -191,6 +194,8 @@ class Intel::Io_mmu : private Attached_mmio<0x800>,
 		Signal_handler<Io_mmu>        _fault_handler      {
 			_env.ep(), *this, &Io_mmu::_handle_faults };
 
+		Constructible<Register_invalidator> _register_invalidator { };
+
 		/**
 		 * Registers
 		 */
@@ -287,30 +292,6 @@ class Intel::Io_mmu : private Attached_mmio<0x800>,
 			struct Address : Bitfield<12,52> { };
 		};
 
-		struct Context_command : Register<0x28, 64>
-		{
-			struct Invalidate : Bitfield<63,1> { };
-
-			/* invalidation request granularity */
-			struct Cirg       : Bitfield<61,2>
-			{
-				enum {
-					GLOBAL = 0x1,
-					DOMAIN = 0x2,
-					DEVICE = 0x3
-				};
-			};
-
-			/* actual invalidation granularity */
-			struct Caig      : Bitfield<59,2> { };
-
-			/* source id */
-			struct Sid       : Bitfield<16,16> { };
-
-			/* domain id */
-			struct Did       : Bitfield<0,16> { };
-		};
-
 		struct Fault_status : Register<0x34, 32>
 		{
 			/* fault record index */
@@ -383,32 +364,6 @@ class Intel::Io_mmu : private Attached_mmio<0x800>,
 			struct Info : Bitfield<12,52> { };
 		};
 
-		struct Iotlb : Genode::Register<64>
-		{
-			struct Invalidate : Bitfield<63,1> { };
-
-			/* IOTLB invalidation request granularity */
-			struct Iirg : Bitfield<60,2>
-			{
-				enum {
-					GLOBAL = 0x1,
-					DOMAIN = 0x2,
-					DEVICE = 0x3
-				};
-			};
-
-			/* IOTLB actual invalidation granularity */
-			struct Iaig : Bitfield<57,2> { };
-
-			/* drain reads/writes */
-			struct Dr   : Bitfield<49,1> { };
-			struct Dw   : Bitfield<48,1> { };
-
-			/* domain id */
-			struct Did  : Bitfield<32,16> { };
-
-		};
-
 		/* saved registers during suspend */
 		Fault_event_control::access_t  _s3_fec { };
 		Fault_event_data ::access_t    _s3_fedata { };
@@ -461,12 +416,6 @@ class Intel::Io_mmu : private Attached_mmio<0x800>,
 		All_registers::access_t read_offset_register(unsigned index) {
 			return read<All_registers>(_offset<OFFSET_BITFIELD>() + index); }
 
-		void write_iotlb_reg(Iotlb::access_t v) {
-			write_offset_register<Extended_capability::Iro>(1, v); }
-
-		Iotlb::access_t read_iotlb_reg() {
-			return read_offset_register<Extended_capability::Iro>(1); }
-
 		template <typename REG>
 		REG::access_t read_fault_record(unsigned index) {
 			return read_offset_register<Capability::Fro>(index*2 + REG::offset()); }
@@ -539,9 +488,7 @@ class Intel::Io_mmu : private Attached_mmio<0x800>,
 
 		void generate(Xml_generator &) override;
 
-		void invalidate_iotlb(Domain_id, addr_t, size_t);
-		void invalidate_context(Domain_id domain, Pci::rid_t);
-		void invalidate_all(Domain_id domain = Domain_id { Domain_id::INVALID }, Pci::rid_t = 0);
+		Invalidator & invalidator();
 
 		bool     coherent_page_walk()   const { return read<Extended_capability::Page_walk_coherency>(); }
 		bool     caching_mode()         const { return read<Capability::Caching_mode>(); }
diff --git a/repos/pc/src/driver/platform/pc/spec/x86_64/target.mk b/repos/pc/src/driver/platform/pc/spec/x86_64/target.mk
index 8540b1e838..b4ae7ccc2e 100644
--- a/repos/pc/src/driver/platform/pc/spec/x86_64/target.mk
+++ b/repos/pc/src/driver/platform/pc/spec/x86_64/target.mk
@@ -9,6 +9,7 @@ SRC_CC += intel/managed_root_table.cc
 SRC_CC += intel/io_mmu.cc
 SRC_CC += intel/page_table.cc
 SRC_CC += intel/default_mappings.cc
+SRC_CC += intel/invalidator.cc
 SRC_CC += ioapic.cc
 
 INC_DIR += $(PRG_DIR)/../../