#!/usr/bin/perl

###############################################################################
# BSD LICENSE
#
# Copyright(c) 2016-2023 Intel Corporation. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#   * Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in
#     the documentation and/or other materials provided with the
#     distribution.
#   * Neither the name of Intel Corporation nor the names of its
#     contributors may be used to endorse or promote products derived
#     from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
###############################################################################

# PerlTidy options:
# -bar -ce -pt=2 -sbt=2 -bt=2 -bbt=2 -et=4 -baao -nsfs -vtc=1 -ci=4

use bigint;
use strict;
use warnings;

use pqos;

my $l2ca_cap_p;
my $l3ca_cap_p;
my $cap_p;
my $cpuinfo_p;
my $num_l2_cos;
my $num_l2_ways;
my $num_l3_cos;
my $num_l3_ways;
my $num_cores;
my $num_sockets;
my $sockets_a;
my $l2_ids_a;
my $num_l2_ids;

# Function to read the msr for a selected cpu
sub read_msr {
	my ($cpu_id, $msr) = @_;

	my $cmd;
	if ($^O eq "freebsd") {
		$cmd = sprintf("cpucontrol -m 0x%x /dev/cpuctl%u", $msr, $cpu_id);
	} else {
		$cmd = sprintf("rdmsr -p%u -0 -c 0x%x", $cpu_id, $msr);
	}

	my @result = `$cmd`;

	if (0 != $?) {
		print __LINE__, " $cmd FAILED!\n";
		return;
	}

	if ($^O eq "freebsd") {
		my @result_array = split / /, $result[0];
		return int(($result_array[2] << 32) + $result_array[3]);
	} else {
		return int(hex $result[0]);
	}
}

sub __func__ {
	return (caller(1))[3];
}

# Function to determine if msr tools are available
sub check_msr_tools {

	if ($^O eq "freebsd") {
		`cpucontrol -m 0xC8F /dev/cpuctl0`;
	} else {
		`rdmsr -p 0 -u 0xC8F`;
	}

	if (-1 == $?) {
		if ($^O eq "freebsd") {
			print __LINE__, " cpucontrol tool not found... ";
		} else {
			print __LINE__, " rdmsr tool not found... ";
		}
		print "please install.\n";
		return -1;
	}

	if (!defined read_msr(0, 0xC8F)) {
		print __LINE__, " unable to read MSR!...\n";
		return -1;
	}

	return 0;
}

# Function to shutdown libpqos
sub shutdown_pqos {
	print "Shutting down pqos lib...\n";
	pqos::pqos_fini();
	$l2ca_cap_p = undef;
	$l3ca_cap_p = undef;
	$cap_p      = undef;
	$cpuinfo_p  = undef;

	if (defined $sockets_a) {
		pqos::delete_uint_a($sockets_a);
		$sockets_a = undef;
	}

	if (defined $l2_ids_a) {
		pqos::delete_uint_a($l2_ids_a);
		$l2_ids_a = undef;
	}

	return;
}

# Function to initialize libpqos
sub init_pqos {
	my $cfg = pqos::pqos_config->new();
	my $ret = -1;

	$cfg->{verbose} = 2;                # SUPER_VERBOSE
	$cfg->{fd_log}  = fileno(STDOUT);

	if (0 != pqos::pqos_init($cfg)) {
		print __LINE__, " pqos::pqos_init FAILED!\n";
		goto EXIT;
	}

	$l2ca_cap_p = pqos::get_cap_l2ca();
	if (defined $l2ca_cap_p) {
		print __LINE__, " L2 CAT detected...\n";
		my $l2ca_cap = pqos::l2ca_cap_p_value($l2ca_cap_p);
		$num_l2_cos  = $l2ca_cap->{num_classes};
		$num_l2_ways = $l2ca_cap->{num_ways};
	}

	$l3ca_cap_p = pqos::get_cap_l3ca();
	if (defined $l3ca_cap_p) {
		print __LINE__, " L3 CAT detected...\n";
		my $l3ca_cap = pqos::l3ca_cap_p_value($l3ca_cap_p);
		$num_l3_cos  = $l3ca_cap->{num_classes};
		$num_l3_ways = $l3ca_cap->{num_ways};
	}

	if (!defined $l2ca_cap_p && !defined $l3ca_cap_p) {
		print __LINE__, " CAT not detected, ",
			"pqos::get_cap_l2ca && pqos::get_cap_l2ca FAILED!\n";
		goto EXIT;
	}

	my $cap_p_p     = pqos::new_pqos_cap_p_p();
	my $cpuinfo_p_p = pqos::new_pqos_cpuinfo_p_p();
	if (0 != pqos::pqos_cap_get($cap_p_p, $cpuinfo_p_p)) {
		print __LINE__, " pqos::pqos_cap_get FAILED!\n";
		goto EXIT;
	}
	$cap_p     = pqos::pqos_cap_p_p_value($cap_p_p);
	$cpuinfo_p = pqos::pqos_cpuinfo_p_p_value($cpuinfo_p_p);
	pqos::delete_pqos_cap_p_p($cap_p_p);
	pqos::delete_pqos_cpuinfo_p_p($cpuinfo_p_p);

	$num_cores = pqos::cpuinfo_p_value($cpuinfo_p)->{num_cores};

	($sockets_a, $num_sockets) = pqos::pqos_cpu_get_sockets($cpuinfo_p);
	if (0 == $sockets_a || 0 == $num_sockets) {
		print __LINE__, " pqos::pqos_cpu_get_sockets FAILED!\n";
		goto EXIT;
	}

	($l2_ids_a, $num_l2_ids) = pqos::pqos_cpu_get_l2ids($cpuinfo_p);
	if (0 == $l2_ids_a || 0 == $num_l2_ids) {
		print __LINE__, " pqos::pqos_cpu_get_l2ids FAILED!\n";
		goto EXIT;
	}
	$ret = 0;

EXIT:
	return sprintf("%s: %s", __func__, $ret == 0 ? "PASS" : "FAILED!");
}

# Function to generate CoS IDs - for testing purposes only
sub generate_cos {
	my ($num_cos, $cpu_id, $socket_id) = @_;

	return ($cpu_id + $socket_id) % $num_cos;
}

# Function to generate CoS's way mask - for testing purposes only
sub generate_ways_mask {
	my ($num_cos, $num_ways, $cos_id, $socket_id) = @_;

	my $bits_per_cos = int($num_ways / $num_cos);
	if ($bits_per_cos < 2) {
		$bits_per_cos = 2;
	}

	my $base_mask = (1 << ($bits_per_cos)) - 1;
	my $ways_mask =
		$base_mask
		<< (($cos_id * $bits_per_cos + $socket_id % 3) % ($num_ways - 1));
	my $result = $ways_mask & (2**$num_ways - 1);

	if (0 == $result) {
		$result = $base_mask;
	}

	return $result;
}

# Function to get CoS assigned to CPU via MSRs (using rdmsr from msr-tools)
sub get_msr_assoc {
	my ($cpu_id) = @_;
	return read_msr($cpu_id, 0xC8F) >> 32;
}

# Function to get L2 CoS ways mask via MSRs (using rdmsr from msr-tools)
sub get_msr_l2_ways_mask {
	return get_msr_ways_mask(@_, 0xD10);
}

# Function to get L3 CoS ways mask via MSRs (using rdmsr from msr-tools)
sub get_msr_l3_ways_mask {
	return get_msr_ways_mask(@_, 0xC90);
}

# Function to get CoS ways mask via MSRs (using rdmsr from msr-tools)
sub get_msr_ways_mask {
	my ($cos_id, $socket_id, $msr_mask_start) = @_;
	my $cpu_id;

	if (!defined $cpuinfo_p || !defined $num_cores) {
		print __LINE__, " !defined ... FAILED!\n";
		return;
	}

	for ($cpu_id = 0; $cpu_id < $num_cores; $cpu_id++) {
		(my $result, my $cpu_socket_id) =
			pqos::pqos_cpu_get_socketid($cpuinfo_p, $cpu_id);
		if (0 != $result) {
			print __LINE__, " pqos::pqos_cpu_get_socketid FAILED!\n";
			return;
		}

		if ($socket_id == $cpu_socket_id) {
			last;
		}
	}

	return read_msr($cpu_id, int($msr_mask_start + $cos_id)) & (2**32 - 1);
}

# Function to print current CAT configuration
sub print_cfg {
	my $cos_id;
	my $l2ca = pqos::pqos_l2ca->new();
	my $l3ca = pqos::pqos_l3ca->new();

	if (!defined $cpuinfo_p ||
		!defined $num_cores   ||
		!defined $num_sockets ||
		!defined $sockets_a   ||
		(!defined $num_l2_cos && !defined $num_l3_cos)) {
		return -1;
	}

	print "CoS configuration:\n";

	for (my $l2_idx = 0; $l2_idx < $num_l2_ids; $l2_idx++) {
		my $l2_id = pqos::uint_a_getitem($l2_ids_a, $l2_idx);

		for (
			$cos_id = 0;
			defined $num_l2_cos && $cos_id < $num_l2_cos;
			$cos_id++
			) {
			if (0 != pqos::get_l2ca($l2ca, $l2_id, $cos_id)) {
				print __LINE__, " pqos::get_l2ca FAILED!\n";
				return -1;
			}

			printf("L2, L2_ID: %d, CoS: %d, ways_mask: 0x%x\n",
				$l2_id, $l2ca->{class_id}, $l2ca->{ways_mask});
		}
	}

	for (my $socket_idx = 0; $socket_idx < $num_sockets; $socket_idx++) {
		my $socket_id = pqos::uint_a_getitem($sockets_a, $socket_idx);

		for (
			$cos_id = 0;
			defined $num_l3_cos && $cos_id < $num_l3_cos;
			$cos_id++
			) {
			if (0 != pqos::get_l3ca($l3ca, $socket_id, $cos_id)) {
				print __LINE__, " pqos::get_l3ca FAILED!\n";
				return -1;
			}

			printf("L3, Socket: %d, CoS: %d, CDP: %d",
				$socket_id, $l3ca->{class_id}, $l3ca->{cdp});
			if (int($l3ca->{cdp}) == 1) {
				printf(
					", data_mask: 0x%x, code_mask: 0x%x",
					$l3ca->{u}->{s}->{data_mask},
					$l3ca->{u}->{s}->{code_mask});
			} else {
				printf(", ways_mask: 0x%x", $l3ca->{u}->{ways_mask});
			}
			print "\n";
		}
	}

	print "CoS association:\n";

	for (my $cpu_id = 0; $cpu_id < $num_cores; $cpu_id++) {
		(my $result, $cos_id) = pqos::pqos_alloc_assoc_get($cpu_id);
		if (0 != $result) {
			print __LINE__, " pqos::pqos_alloc_assoc_get FAILED!\n";
			return -1;
		}

		($result, my $socket_id) =
			pqos::pqos_cpu_get_socketid($cpuinfo_p, $cpu_id);
		if (0 != $result) {
			print __LINE__, " pqos::pqos_cpu_get_socketid FAILED!\n";
			return -1;
		}

		print "Socket: ", $socket_id, ", Core: ", $cpu_id, ", CoS: ",
			$cos_id, "\n";
	}

	return 0;
}

# Function to test CoS association to CPUs
# Association is configured using libpqos API
# Association is verified using libpqos API and also by reading MSRs
sub test_assoc {
	my $cos_id;
	my $num_cos;
	my $gen_cos_id;
	my $socket_id;
	my $result;
	my $ret = -1;

	if (!defined $cpuinfo_p ||
		!defined $num_cores ||
		(!defined $num_l2_cos && !defined $num_l3_cos)) {
		print __LINE__, " No variables defined in test_assoc FAILED!\n";
		goto EXIT;
	}

	if (defined $num_l2_cos && defined $num_l3_cos) {
		$num_cos = $num_l2_cos <= $num_l3_cos ? $num_l3_cos : $num_l2_cos;
	} elsif (defined $num_l2_cos) {
		$num_cos = $num_l2_cos;
	} else {
		$num_cos = $num_l3_cos;
	}

	for (my $cpu_id = 0; $cpu_id < $num_cores; $cpu_id++) {
		($result, $cos_id) = pqos::pqos_alloc_assoc_get($cpu_id);
		if (0 != $result) {
			print __LINE__, " pqos::pqos_alloc_assoc_get FAILED!\n";
			goto EXIT;
		}

		($result, $socket_id) =
			pqos::pqos_cpu_get_socketid($cpuinfo_p, $cpu_id);
		if (0 != $result) {
			print __LINE__, " pqos::pqos_cpu_get_socketid FAILED!\n";
			goto EXIT;
		}

		$gen_cos_id = generate_cos($num_cos, $cpu_id, $socket_id);
		if (!defined $gen_cos_id) {
			print __LINE__, " generate_cos FAILED!\n";
			goto EXIT;
		}

		if (0 != pqos::pqos_alloc_assoc_set($cpu_id, $gen_cos_id)) {
			print __LINE__, " pqos::pqos_alloc_assoc_set FAILED!\n";
			goto EXIT;
		}
	}

	for (my $cpu_id = 0; $cpu_id < $num_cores; $cpu_id++) {
		($result, $cos_id) = pqos::pqos_alloc_assoc_get($cpu_id);
		if (0 != $result) {
			print __LINE__, " pqos::pqos_alloc_assoc_get FAILED!\n";
			goto EXIT;
		}

		($result, $socket_id) =
			pqos::pqos_cpu_get_socketid($cpuinfo_p, $cpu_id);
		if (0 != $result) {
			print __LINE__, " pqos::pqos_cpu_get_socketid FAILED!\n";
			goto EXIT;
		}

		$gen_cos_id = generate_cos($num_cos, $cpu_id, $socket_id);
		if (!defined $gen_cos_id) {
			print __LINE__, " generate_cos FAILED!\n";
			goto EXIT;
		}

		if ($cos_id != $gen_cos_id) {
			print __LINE__, ' $cos_id != $gen_cos_id FAILED!', "\n";
			goto EXIT;
		}

		$cos_id = get_msr_assoc($cpu_id);
		if (!defined $cos_id) {
			print __LINE__, " get_msr_assoc FAILED!\n";
			goto EXIT;
		}

		if ($cos_id != $gen_cos_id) {
			print __LINE__, " msr $cos_id != $gen_cos_id FAILED!\n";
			goto EXIT;
		}
	}

	$ret = 0;

EXIT:
	return sprintf("%s: %s", __func__, $ret == 0 ? "PASS" : "FAILED!");
}

# Function to test CoS ways masks configuration
# CoS is configured using libpqos API
# CoS configuration is verified using libpqos API and also by reading MSRs
sub test_way_masks {
	my $l2ca = pqos::pqos_l2ca->new();
	my $l3ca = pqos::pqos_l3ca->new();
	my $gen_ways_mask;
	my $msr_ways_mask;
	my $ret = -1;

	if (!defined $num_sockets ||
		!defined $sockets_a ||
		(!defined $num_l2_cos && !defined $num_l3_cos)) {
		print __LINE__, " No variables defined in test_way_masks, FAILED!\n";
		goto EXIT;
	}

	for (my $l2_idx = 0; $l2_idx < $num_l2_ids; $l2_idx++) {
		my $l2_id = pqos::uint_a_getitem($l2_ids_a, $l2_idx);
		if (!defined $l2_id) {
			print __LINE__, " pqos::uint_a_getitem FAILED!\n";
			goto EXIT;
		}

		for (
			my $cos_id = 0;
			defined $num_l2_cos && $cos_id < $num_l2_cos;
			$cos_id++
			) {

			$gen_ways_mask =
				generate_ways_mask($num_l2_cos, $num_l2_ways, $cos_id, $l2_id);

			if (!defined $gen_ways_mask) {
				print __LINE__, " L2 generate_ways_mask FAILED!\n";
				goto EXIT;
			}

			$l2ca->{ways_mask} = $gen_ways_mask;
			$l2ca->{class_id}  = $cos_id;

			if (0 != pqos::pqos_l2ca_set($l2_id, 1, $l2ca)) {
				print __LINE__, " pqos::pqos_l2ca_set FAILED!\n";
				goto EXIT;
			}
		}
	}

	for (my $socket_idx = 0; $socket_idx < $num_sockets; $socket_idx++) {
		my $socket_id = pqos::uint_a_getitem($sockets_a, $socket_idx);
		if (!defined $socket_id) {
			print __LINE__, " pqos::uint_a_getitem FAILED!\n";
			goto EXIT;
		}

		for (
			my $cos_id = 0;
			defined $num_l3_cos && $cos_id < $num_l3_cos;
			$cos_id++
			) {

			$gen_ways_mask = generate_ways_mask($num_l3_cos,
				$num_l3_ways, $cos_id, $socket_id);

			if (!defined $gen_ways_mask) {
				print __LINE__, " L3 generate_ways_mask FAILED!\n";
				goto EXIT;
			}

			$l3ca->{u}->{ways_mask} = $gen_ways_mask;
			$l3ca->{class_id}       = $cos_id;
			$l3ca->{cdp}            = 0;

			if (0 != pqos::pqos_l3ca_set($socket_id, 1, $l3ca)) {
				print __LINE__, " pqos::pqos_l3ca_set FAILED!\n";
				goto EXIT;
			}
		}
	}

	for (my $socket_idx = 0; $socket_idx < $num_sockets; $socket_idx++) {
		my $socket_id = pqos::uint_a_getitem($sockets_a, $socket_idx);
		if (!defined $socket_id) {
			print __LINE__, " pqos::uint_a_getitem FAILED!\n";
			goto EXIT;
		}

		for (
			my $cos_id = 0;
			defined $num_l2_cos && $cos_id < $num_l2_cos;
			$cos_id++
			) {

			if (0 != pqos::get_l2ca($l2ca, $socket_id, $cos_id)) {
				print __LINE__, " pqos::get_l2ca FAILED!\n";
				goto EXIT;
			}

			$gen_ways_mask = generate_ways_mask($num_l2_cos,
				$num_l2_ways, $cos_id, $socket_id);
			if (!defined $gen_ways_mask) {
				print __LINE__, " L2 generate_ways_mask FAILED!\n";
				goto EXIT;
			}

			if ($l2ca->{ways_mask} != $gen_ways_mask) {
				print __LINE__,
					' $l2ca->{ways_mask} != $gen_ways_mask ',
					"FAILED!\n";
				goto EXIT;
			}

			$msr_ways_mask = get_msr_l2_ways_mask($cos_id, $socket_id);
			if (!defined $msr_ways_mask) {
				print __LINE__, " get_msr_l2_ways_mask FAILED!\n";
				goto EXIT;
			}

			if ($msr_ways_mask != $gen_ways_mask) {
				print __LINE__,
					' L2 $msr_ways_mask != $gen_ways_mask ',
					"FAILED!\n";
				goto EXIT;
			}
		}

		for (
			my $cos_id = 0;
			defined $num_l3_cos && $cos_id < $num_l3_cos;
			$cos_id++
			) {

			if (0 != pqos::get_l3ca($l3ca, $socket_id, $cos_id)) {
				print __LINE__, " pqos::get_l3ca FAILED!\n";
				goto EXIT;
			}

			$gen_ways_mask = generate_ways_mask($num_l3_cos,
				$num_l3_ways, $cos_id, $socket_id);
			if (!defined $gen_ways_mask) {
				print __LINE__, " L3 generate_ways_mask FAILED!\n";
				goto EXIT;
			}

			if ($l3ca->{u}->{ways_mask} != $gen_ways_mask) {
				print __LINE__,
					' $l3ca->{u}->{ways_mask} != $gen_ways_mask',
					"FAILED!\n";
				goto EXIT;
			}

			$msr_ways_mask = get_msr_l3_ways_mask($cos_id, $socket_id);
			if (!defined $msr_ways_mask) {
				print __LINE__, " get_msr_l3_ways_mask FAILED!\n";
				goto EXIT;
			}

			if ($msr_ways_mask != $gen_ways_mask) {
				print __LINE__,
					' L3 $msr_ways_mask != $gen_ways_mask ',
					"FAILED!\n";
				goto EXIT;
			}
		}
	}

	$ret = 0;

EXIT:
	return sprintf("%s: %s", __func__, 0 == $ret ? "PASS" : "FAILED!");
}

# Function to test CMT LLC occupancy monitoring
# CMT is detected and LLC occupancy is polled using libpqos API
sub test_cmt_llc {
	my $ret              = -1;
	my $monitor_p_p      = pqos::new_pqos_monitor_p_p();
	my $llc_mon_data_p_a = pqos::new_pqos_mon_data_p_a($num_cores);
	my $cpu_id_p         = pqos::new_uintp();

	for (my $cpu_id = 0; $cpu_id < $num_cores; $cpu_id++) {
		pqos::pqos_mon_data_p_a_setitem($llc_mon_data_p_a, $cpu_id, undef);
	}

	if (
		0 == pqos::pqos_cap_get_event(
			$cap_p, $pqos::PQOS_MON_EVENT_L3_OCCUP, $monitor_p_p)
		) {
		my $llc_mon_data_p;

		for (my $cpu_id = 0; $cpu_id < $num_cores; $cpu_id++) {
			pqos::uintp_assign($cpu_id_p, $cpu_id);

			$llc_mon_data_p = pqos::new_pqos_mon_data_p();
			pqos::pqos_mon_data_p_a_setitem($llc_mon_data_p_a, $cpu_id,
				$llc_mon_data_p);

			if (
				0 != pqos::pqos_mon_start(
					1, $cpu_id_p, $pqos::PQOS_MON_EVENT_L3_OCCUP,
					undef, $llc_mon_data_p)
				) {
				print __LINE__, " pqos::pqos_mon_start FAILED!\n";
				goto EXIT;
			}
		}

		if (0 != pqos::pqos_mon_poll($llc_mon_data_p_a, $num_cores)) {
			print __LINE__, " pqos::pqos_mon_poll FAILED!\n";
			goto EXIT;
		}

		print "CMT LLC Occupancy:\n";

		for (my $cpu_id = 0; $cpu_id < $num_cores; $cpu_id++) {

			(my $result, my $socket_id) =
				pqos::pqos_cpu_get_socketid($cpuinfo_p, $cpu_id);
			if (0 != $result) {
				print __LINE__, " pqos::pqos_cpu_get_socketid FAILED!\n";
				goto EXIT;
			}

			$llc_mon_data_p =
				pqos::pqos_mon_data_p_a_getitem($llc_mon_data_p_a, $cpu_id);

			print "Socket: ", $socket_id, ", Core: ", $cpu_id,
				", LLC[KB]: ",
				pqos::pqos_mon_data_p_value($llc_mon_data_p)->{values}->{llc} /
				1024,
				"\n";

			if (0 != pqos::pqos_mon_stop($llc_mon_data_p)) {
				print __LINE__, " pqos::pqos_mon_stop FAILED!\n";
				goto EXIT;
			}
		}
	} else {
		print "CMT LLC monitoring capability not detected...\n";
	}

	$ret = 0;

EXIT:
	if (defined $cpu_id_p) {
		pqos::delete_uintp($cpu_id_p);
	}

	if (defined $monitor_p_p) {
		pqos::delete_pqos_monitor_p_p($monitor_p_p);
	}

	if (defined $llc_mon_data_p_a) {
		for (my $cpu_id = 0; $cpu_id < $num_cores; $cpu_id++) {
			my $llc_mon_data_p =
				pqos::pqos_mon_data_p_a_getitem($llc_mon_data_p_a, $cpu_id);
			if (defined $llc_mon_data_p) {
				pqos::delete_pqos_mon_data_p($llc_mon_data_p);
			}
		}

		pqos::delete_pqos_mon_data_p_a($llc_mon_data_p_a);
		$llc_mon_data_p_a = undef;
	}

	return sprintf("%s: %s", __func__, 0 == $ret ? "PASS" : "FAILED!");
}

# Function to reset CAT configuration - for testing purposes only
sub reset_cfg {
	my $l3ca = pqos::pqos_l3ca->new();
	my $l2ca = pqos::pqos_l2ca->new();
	my $cos_id;
	my $ret = -1;

	if ((!defined $num_l2_ways && !defined $num_l3_ways) ||
		!defined $num_cores   ||
		!defined $num_sockets ||
		!defined $sockets_a   ||
		(!defined $num_l2_cos && !defined $num_l3_cos)) {
		print __LINE__, " No variables defined in reset_cfg, FAILED!\n";
		goto EXIT;
	}

	for (my $cpu_id = 0; $cpu_id < $num_cores; $cpu_id++) {
		if (0 != pqos::pqos_alloc_assoc_set($cpu_id, 0)) {
			print __LINE__, " pqos::pqos_alloc_assoc_set FAILED!\n";
			goto EXIT;
		}
	}

	for (my $socket_idx = 0; $socket_idx < $num_sockets; $socket_idx++) {
		my $socket_id = pqos::uint_a_getitem($sockets_a, $socket_idx);
		for (
			$cos_id = 0;
			defined $num_l2_cos && $cos_id < $num_l2_cos;
			$cos_id++
			) {

			if (0 != pqos::get_l2ca($l2ca, $socket_id, $cos_id)) {
				print __LINE__, " pqos::get_l2ca FAILED!\n";
				goto EXIT;
			}

			$l2ca->{ways_mask} = (1 << $num_l2_ways) - 1;

			if (0 != pqos::pqos_l2ca_set($socket_id, 1, $l2ca)) {
				print __LINE__, " pqos::pqos_l2ca_set FAILED!\n";
				goto EXIT;
			}
		}

		for (
			$cos_id = 0;
			defined $num_l3_cos && $cos_id < $num_l3_cos;
			$cos_id++
			) {

			if (0 != pqos::get_l3ca($l3ca, $socket_id, $cos_id)) {
				print __LINE__, " pqos::get_l3ca FAILED!\n";
				goto EXIT;
			}

			$l3ca->{cdp} = 0;
			$l3ca->{u}->{ways_mask} = (1 << $num_l3_ways) - 1;

			if (0 != pqos::pqos_l3ca_set($socket_id, 1, $l3ca)) {
				print __LINE__, " pqos::pqos_l3ca_set FAILED!\n";
				goto EXIT;
			}
		}
	}

	$ret = 0;

EXIT:
	return sprintf("%s: %s", __func__, $ret == 0 ? "PASS" : "FAILED!");
}

(0 == check_msr_tools) or die("MSR reading tool issue...\n");

printf("%d %s\n", __LINE__, init_pqos());
printf("%d %s\n", __LINE__, test_assoc());
printf("%d %s\n", __LINE__, test_way_masks());

print_cfg;

printf("%d %s\n", __LINE__, reset_cfg());
printf("%d %s\n", __LINE__, test_cmt_llc());

shutdown_pqos();
