diff --git a/arm/spe.c b/arm/spe.c
index 153c182..7996f79 100644
--- a/arm/spe.c
+++ b/arm/spe.c
@@ -14,9 +14,11 @@
 #include "errata.h"
 #include "asm/barrier.h"
 #include "asm/sysreg.h"
+#include "asm/page.h"
 #include "asm/processor.h"
 #include "alloc_page.h"
 #include <bitops.h>
+#include "alloc.h"
 
 struct spe {
 	int min_interval;
@@ -27,6 +29,10 @@ struct spe {
 	bool fe_cap;
 	int align;
 	void *buffer;
+	uint64_t pmbptr_el1;
+	uint64_t pmblimitr_el1;
+	uint64_t pmsirr_el1;
+	uint64_t pmscr_el1;
 	bool unique_record_size;
 };
 
@@ -36,6 +42,7 @@ static struct spe spe;
 
 static bool spe_probe(void) { return false; }
 static void test_spe_introspection(void) { }
+static void test_spe_buffer(void) { }
 
 #else
 
@@ -59,10 +66,35 @@ static void test_spe_introspection(void) { }
 #define PMSIDR_EL1_COUNTSIZE_SHIFT	16
 #define PMSIDR_EL1_COUNTSIZE_MASK	0xFUL
 
+#define PMSIRR_EL1_INTERVAL_SHIFT	8
+#define PMSIRR_EL1_INTERVAL_MASK	0xFFFFFF
+
+#define PMSFCR_EL1_FE			0x1
+#define PMSFCR_EL1_FT			0x2
+#define PMSFCR_EL1_FL			0x4
+#define PMSFCR_EL1_B			0x10000
+#define PMSFCR_EL1_LD			0x20000
+#define PMSFCR_EL1_ST			0x40000
+
+#define PMSCR_EL1	sys_reg(3, 0, 9, 9, 0)
+#define PMSICR_EL1	sys_reg(3, 0, 9, 9, 2)
+#define PMSIRR_EL1	sys_reg(3, 0, 9, 9, 3)
+#define PMSFCR_EL1	sys_reg(3, 0, 9, 9, 4)
+#define PMSEVFR_EL1	sys_reg(3, 0, 9, 9, 5)
 #define PMSIDR_EL1	sys_reg(3, 0, 9, 9, 7)
 
+#define PMBLIMITR_EL1	sys_reg(3, 0, 9, 10, 0)
+#define PMBPTR_EL1	sys_reg(3, 0, 9, 10, 1)
+#define PMBSR_EL1	sys_reg(3, 0, 9, 10, 3)
 #define PMBIDR_EL1	sys_reg(3, 0, 9, 10, 7)
 
+#define PMBLIMITR_EL1_E			0x1
+
+#define PMSCR_EL1_E1SPE			0x2
+#define PMSCR_EL1_PA			0x10
+#define PMSCR_EL1_TS			0x20
+#define PMSCR_EL1_PCT			0x40
+
 static int min_interval(uint8_t idr_bits)
 {
 	switch (idr_bits) {
@@ -138,6 +170,131 @@ static void test_spe_introspection(void)
 		"PMSIDR_EL1: Minimal sampling interval = %d", spe.min_interval);
 }
 
+static void mem_access_loop(void *addr, int loop, uint64_t pmblimitr)
+{
+asm volatile(
+	"	msr_s " xstr(PMBLIMITR_EL1) ", %[pmblimitr]\n"
+	"       isb\n"
+	"       mov     x10, %[loop]\n"
+	"1:     sub     x10, x10, #1\n"
+	"       ldr     x9, [%[addr]]\n"
+	"       cmp     x10, #0x0\n"
+	"       b.gt    1b\n"
+	"	bfxil   %[pmblimitr], xzr, 0, 1\n"
+	"	msr_s " xstr(PMBLIMITR_EL1) ", %[pmblimitr]\n"
+	"       isb\n"
+	:
+	: [addr] "r" (addr), [pmblimitr] "r" (pmblimitr), [loop] "r" (loop)
+	: "x8", "x9", "x10", "cc");
+}
+
+char null_buff[PAGE_SIZE] = {};
+
+static void reset(void)
+{
+	/* erase the profiling buffer, reset the start and limit addresses */
+	spe.pmbptr_el1 = (uint64_t)spe.buffer;
+	spe.pmblimitr_el1 = (uint64_t)(spe.buffer + PAGE_SIZE);
+	write_sysreg_s(spe.pmbptr_el1, PMBPTR_EL1);
+	write_sysreg_s(spe.pmblimitr_el1, PMBLIMITR_EL1);
+	isb();
+
+	/* Drain any buffered data */
+	psb_csync();
+	dsb(nsh);
+
+	memset(spe.buffer, 0, PAGE_SIZE);
+
+	/* reset the syndrome register */
+	write_sysreg_s(0, PMBSR_EL1);
+
+	/* SW must write 0 to PMSICR_EL1 before enabling sampling profiling */
+	write_sysreg_s(0, PMSICR_EL1);
+
+	/* Filtering disabled */
+	write_sysreg_s(0, PMSFCR_EL1);
+
+	/* Interval Reload Register */
+	spe.pmsirr_el1 = (spe.min_interval & PMSIRR_EL1_INTERVAL_MASK) << PMSIRR_EL1_INTERVAL_SHIFT;
+	write_sysreg_s(spe.pmsirr_el1, PMSIRR_EL1);
+
+	/* Control Registrer */
+	spe.pmscr_el1 = PMSCR_EL1_E1SPE | PMSCR_EL1_TS | PMSCR_EL1_PCT | PMSCR_EL1_PA;
+	write_sysreg_s(spe.pmscr_el1, PMSCR_EL1);
+
+	/* Make sure the syndrome register is void */
+	write_sysreg_s(0, PMBSR_EL1);
+}
+
+static inline void drain(void)
+{
+	/* ensure profiling data are written */
+	psb_csync();
+	dsb(nsh);
+}
+
+static void test_spe_buffer(void)
+{
+	uint64_t pmbsr_el1, val1, val2;
+	void *addr = malloc(10 * PAGE_SIZE);
+
+	reset();
+
+	val1 = read_sysreg_s(PMBPTR_EL1);
+	val2 = read_sysreg_s(PMBLIMITR_EL1);
+	report(val1 == spe.pmbptr_el1 && val2 == spe.pmblimitr_el1,
+	       "PMBPTR_EL1, PMBLIMITR_EL1: reset");
+
+	val1 = read_sysreg_s(PMSIRR_EL1);
+	report(val1 == spe.pmsirr_el1, "PMSIRR_EL1: Sampling interval set to %d", spe.min_interval);
+
+	val1 = read_sysreg_s(PMSCR_EL1);
+	report(val1 == spe.pmscr_el1, "PMSCR_EL1: EL1 Statistical Profiling enabled");
+
+	val1 = read_sysreg_s(PMSFCR_EL1);
+	report(!val1, "PMSFCR_EL1: No Filter Control");
+
+	report(!memcmp(spe.buffer, null_buff, PAGE_SIZE),
+		       "Profiling buffer empty before profiling");
+
+	val1 = read_sysreg_s(PMBSR_EL1);
+	report(!val1, "PMBSR_EL1: Syndrome Register void before profiling");
+
+	mem_access_loop(addr, 1, spe.pmblimitr_el1 | PMBLIMITR_EL1_E);
+	drain();
+	val1 = read_sysreg_s(PMSICR_EL1);
+	/*
+	 * TODO: the value read in PMSICR_EL1.count currently seems not consistent with
+	 * programmed interval. Getting a good value would allow to estimate the number
+	 * of records to be collected in next step.
+	 */
+	report_info("count for a single iteration: PMSICR_EL1.count=%lld interval=%d",
+		    val1 & GENMASK_ULL(31, 0), spe.min_interval);
+
+	/* Stuff to profile */
+
+	mem_access_loop(addr, 1000000, spe.pmblimitr_el1 | PMBLIMITR_EL1_E);
+
+	/* end of stuff to profile */
+
+	drain();
+
+	report(memcmp(spe.buffer, null_buff, PAGE_SIZE), "Profiling buffer filled");
+
+	val1 = read_sysreg_s(PMBPTR_EL1);
+	val2 = val1 - (uint64_t)spe.buffer;
+	report(val1 > (uint64_t)spe.buffer,
+		"PMBPTR_EL1: Current write position has increased: 0x%lx -> 0x%lx (%ld bytes)",
+		(uint64_t)spe.buffer, val1, val2);
+	if (spe.unique_record_size)
+		report_info("This corresponds to %ld record(s) of %d bytes",
+			    val2 / spe.maxsize, spe.maxsize);
+	pmbsr_el1 = read_sysreg_s(PMBSR_EL1);
+	report(!pmbsr_el1, "PMBSR_EL1: no event");
+
+	free(addr);
+}
+
 #endif
 
 int main(int argc, char *argv[])
@@ -156,6 +313,10 @@ int main(int argc, char *argv[])
 		report_prefix_push(argv[1]);
 		test_spe_introspection();
 		report_prefix_pop();
+	} else if (strcmp(argv[1], "spe-buffer") == 0) {
+		report_prefix_push(argv[1]);
+		test_spe_buffer();
+		report_prefix_pop();
 	} else {
 		report_abort("Unknown sub-test '%s'", argv[1]);
 	}
diff --git a/arm/unittests.cfg b/arm/unittests.cfg
index c070939..bb0e84c 100644
--- a/arm/unittests.cfg
+++ b/arm/unittests.cfg
@@ -142,6 +142,14 @@ extra_params = -append 'spe-introspection'
 accel = kvm
 arch = arm64
 
+[spe-buffer]
+file = spe.flat
+groups = spe
+arch = arm64
+extra_params = -append 'spe-buffer'
+accel = kvm
+arch = arm64
+
 # Test GIC emulation
 [gicv2-ipi]
 file = gic.flat
diff --git a/lib/arm64/asm/barrier.h b/lib/arm64/asm/barrier.h
index 0e1904c..f9ede15 100644
--- a/lib/arm64/asm/barrier.h
+++ b/lib/arm64/asm/barrier.h
@@ -23,5 +23,6 @@
 #define smp_mb()	dmb(ish)
 #define smp_rmb()	dmb(ishld)
 #define smp_wmb()	dmb(ishst)
+#define psb_csync()	asm volatile("hint #17" : : : "memory")
 
 #endif /* _ASMARM64_BARRIER_H_ */
