diff --git a/repos/base-nova/src/lib/base/perf.cc b/repos/base-nova/src/lib/base/perf.cc
new file mode 100644
index 0000000000..fccc3a9b5c
--- /dev/null
+++ b/repos/base-nova/src/lib/base/perf.cc
@@ -0,0 +1,87 @@
+
+/*
+ * \brief Performance Counter infrastructure, NOVA-specific implemantation
+ * \author Michael Müller
+ * \date 2022-12-15
+ */
+
+#include
+
+#include
+#include
+#include
+
+unsigned long Genode::Trace::Performance_counter::private_freemask { 0xffff };
+unsigned long Genode::Trace::Performance_counter::shared_freemask { 0xffff0000 };
+
+void Genode::Trace::Performance_counter::_init_masks()
+{
+ Nova::Hip::Cpu_desc::Vendor vendor = Nova::Hip::Cpu_desc::AMD;
+ if (vendor == Nova::Hip::Cpu_desc::AMD)
+ {
+ private_freemask = 0x3f; // 6 core performance counters
+ shared_freemask = 0x1f0000; // 5 L3 complex performance counters
+ }
+ else if (vendor == Nova::Hip::Cpu_desc::INTEL)
+ {
+ private_freemask = 0x7fff;
+ shared_freemask = 0x7fff0000; // 15 CBO performance counters
+ }
+}
+
+void Genode::Trace::Performance_counter::setup(unsigned counter, uint64_t event, uint64_t mask, uint64_t flags)
+{
+ Nova::mword_t evt = event;
+ Nova::mword_t msk = mask;
+ Nova::mword_t flg = flags;
+ Nova::uint8_t rc;
+ Nova::mword_t type = (counter >>4);
+ Nova::mword_t sel = type == Performance_counter::CORE ? counter : counter & 0xf;
+
+ if ((rc = (Nova::hpc_ctrl(Nova::HPC_SETUP, sel, type, evt, msk, flg))) != Nova::NOVA_OK)
+ throw Genode::Trace::Pfc_access_error(rc);
+}
+
+void Genode::Trace::Performance_counter::start(unsigned counter)
+{
+ Nova::uint8_t rc;
+ Nova::mword_t type = (counter >> 4);
+ Nova::mword_t sel = type == Performance_counter::CORE ? counter : counter >>4;
+
+ if ((rc = Nova::hpc_start(sel, type)) != Nova::NOVA_OK)
+ throw Genode::Trace::Pfc_access_error(rc);
+}
+
+void Genode::Trace::Performance_counter::stop(unsigned counter)
+{
+ Nova::uint8_t rc;
+ Nova::mword_t type = (counter >>4);
+ Nova::mword_t sel = type == Performance_counter::CORE ? counter : counter & 0xf;
+
+ if ((rc = Nova::hpc_stop(sel, type)) != Nova::NOVA_OK)
+ throw Genode::Trace::Pfc_access_error(rc);
+}
+
+void Genode::Trace::Performance_counter::reset(unsigned counter, unsigned val)
+{
+ Nova::uint8_t rc;
+ Nova::mword_t type = (counter >>4);
+ Nova::mword_t sel = type == Performance_counter::CORE ? counter : counter & 0xf;
+
+ if ((rc = Nova::hpc_reset(sel, type, val)) != Nova::NOVA_OK)
+ throw Genode::Trace::Pfc_access_error(rc);
+}
+
+Genode::uint64_t Genode::Trace::Performance_counter::read(unsigned counter)
+{
+ Nova::uint8_t rc;
+ Nova::mword_t value = 0;
+ Nova::mword_t type = (counter >>4);
+ Nova::mword_t sel = type == Performance_counter::CORE ? counter : counter & 0xf;
+
+ if ((rc = Nova::hpc_read(sel, type, value)) != Nova::NOVA_OK)
+ throw Genode::Trace::Pfc_access_error(rc);
+
+ Genode::log("Performance_counter::read = ", value);
+ return static_cast(value);
+}
\ No newline at end of file
diff --git a/repos/base/include/base/trace/perf.h b/repos/base/include/base/trace/perf.h
new file mode 100644
index 0000000000..e2eb6ee81b
--- /dev/null
+++ b/repos/base/include/base/trace/perf.h
@@ -0,0 +1,93 @@
+/*
+ * \brief Performance Counter infrastructure
+ * \author Michael Müller
+ * \date 2022-12-15
+ */
+
+#pragma once
+
+#include
+
+namespace Genode { namespace Trace {
+
+ class Pfc_no_avail {
+ };
+
+ class Performance_counter
+ {
+
+ private:
+ static unsigned long private_freemask;
+ static unsigned long shared_freemask;
+
+ static unsigned _alloc(unsigned long *free_mask)
+ {
+ unsigned long current_mask, new_mask;
+ unsigned bit;
+
+ do
+ {
+ current_mask = *free_mask;
+ bit = __builtin_ffsl(current_mask);
+ new_mask = current_mask & ~(1 << (bit - 1));
+ } while (!__atomic_compare_exchange(free_mask, ¤t_mask, &new_mask, true, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED));
+
+ if (!bit) // Allocation failed
+ throw Pfc_no_avail();
+
+ return bit - 1; // number of the allocated counter
+ }
+
+ static void _init_masks();
+
+ public:
+ typedef unsigned int Counter;
+
+ enum Type
+ {
+ CORE = 0,
+ CACHE = 1
+ };
+
+ static unsigned acquire(Type type) {
+ return (type == Type::CORE) ? alloc_core() : alloc_cbo();
+ }
+
+ static unsigned alloc_cbo() {
+ if (shared_freemask == 0xffff0000)
+ _init_masks();
+ return _alloc(&shared_freemask);
+ }
+
+ static unsigned alloc_core() {
+ if (private_freemask == 0xffff)
+ _init_masks();
+ return _alloc(&private_freemask);
+ }
+
+ static void release(unsigned counter) {
+ bool core = static_cast(counter >> 4);
+ if (core)
+ private_freemask |= (1 << counter);
+ else
+ shared_freemask |= (1 << counter);
+ }
+
+ static void setup(unsigned counter, Genode::uint64_t event, Genode::uint64_t mask, Genode::uint64_t flags);
+ static void start(unsigned counter);
+ static void stop(unsigned counter);
+ static void reset(unsigned counter, unsigned val=0);
+ static uint64_t read(unsigned counter);
+ };
+
+ class Pfc_access_error {
+ private:
+ Genode::uint8_t _rc;
+
+ public:
+ Pfc_access_error(uint8_t rc) : _rc(rc) {}
+ Genode::uint8_t error_code() { return _rc; }
+ };
+
+ }
+}
\ No newline at end of file