/*
	Usage:
	msr - read or write x86 CPU MSRs

	msr [ number [ new_value ] ]

	If no <number> is given, the TSC is read.
	If no <new_value> is provided, the MSR is only read.
	If both <number> and <new_value> are given, the MSR is read and set.

	--------------------------------------------------------------------

	What happens?

	MSRs (aside from the TSC) are only accessible in RING0, i.e., in 
	kernel space. Regular processes, even with I/O privileges, will 
	get you no further than RING1.

	So to access MSRs, you need to operate in a kernel context - but
	how to get there?

	One -rather obvious- way is to attach an interrupt handler. 
	Since it is called from within the kernel, it will have the required 
	privileges and can perform the necessary operations for us.
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <err.h>
#include <sys/neutrino.h>
#include <x86/priv.h>

/* Some known MSRs */
#define MSR_TSC						0x0010
#define MSR_PERF_CTR0				0x00c1
#define MSR_PERF_CTR1				0x00c2
#define MSR_PERF_EVT_SEL0		0x0186
#define MSR_PERF_EVT_SEL1		0x0187
#define MSR_LASTBRANCHFROMIP	0x01db
#define MSR_LASTBRANCHTOIP		0x01dc
#define MSR_LASTINTFROMIP		0x01dd
#define MSR_LASTINTTOIP			0x01de

/* Name <--> number mapping */
typedef struct MsrDef_t {
	const char		*sname;
	const char		*lname;
	uint32_t			idx;
} MsrDef_t;

/* ISR data area */
typedef struct RdWrMsr_t {
	int						set;			/* Write MSR?      */
	uint32_t				nr;				/* MSR number      */
	uint64_t				oval;			/* Old value       */
	uint64_t				nval;			/* New value       */
	struct sigevent		event;			/* Interrupt event */
} RdWrMsr_t;

/* Name <--> number mapping */
static MsrDef_t		MsrDefs[] = {
	{ "tsc",  "TSC",              MSR_TSC              },
	{ "pc0",  "PERF_CTR0",        MSR_PERF_CTR0        },
	{ "pc1",  "PERF_CTR1",        MSR_PERF_CTR1        },
	{ "pes0", "PERF_EVT_SEL0",    MSR_PERF_EVT_SEL0    },
	{ "pes1", "PERF_EVT_SEL1",    MSR_PERF_EVT_SEL1    },
	{ "lbf",  "LASTBRANCHFROMIP", MSR_LASTBRANCHFROMIP },
	{ "lbt",  "LASTBRANCHTOIP",   MSR_LASTBRANCHTOIP   },
	{ "lif",  "LASTINTFROMIP",    MSR_LASTINTFROMIP    },
	{ "lit",  "LASTINTTOIP",      MSR_LASTINTTOIP      },
};


/* The ISR - does the actual work */
static const struct sigevent *IntrHnd( void *area, int size ) {
	RdWrMsr_t  *msr = area;

	msr->oval = rdmsr( msr->nr );

	if ( msr->set )
		wrmsr( msr->nr, msr->nval );

	return &msr->event;
}


/*
	int ReadMsr( uint32_t idx, uint64_t *val );

	Read an MSR. The MSR number must be provided in <idx>; upon successful 
	completion, *<val> will contain the MSR value at the time of the ISR.

	Returns:
		EOK - Success
		ENOTSUP - Shouldn't happen
		ETIMEDOUT - Shouldn't happen
		EAGAIN - All kernel interrupt entries are in use.
		EFAULT - A fault occurred when the kernel tried to access the buffers provided.
		EINVAL - The value of intr isn't a valid interrupt number.
		EPERM - The process doesn't have I/O privileges.
*/
int ReadMsr( uint32_t idx, uint64_t *val ) {
	RdWrMsr_t  msr;
	int  id;

	msr.set = 0;
	msr.nr  = idx;
	SIGEV_INTR_INIT( &msr.event );

	if ( -1 != ( id = InterruptAttach( 0, IntrHnd, &msr, sizeof msr, 0 ) ) ) {
		while ( EINTR == ( errno = InterruptWait_r( 0, NULL ) ) )
			;

		if ( val && ( errno == EOK ) )
			*val = msr.oval;

		InterruptDetach_r( id );
	}

	return errno;
}


/*
	int WriteMsr( uint32_t idx, uint64_t nval, uint64_t *oval );

	Write an MSR. The MSR number must be provided in <idx>; upon successful 
	completion, the MSR will have been set to <nval>, and *<oval> will contain 
	the original MSR value at the time of the ISR.

	Returns:
		EOK - Success
		ENOTSUP - Shouldn't happen
		ETIMEDOUT - Shouldn't happen
		EAGAIN - All kernel interrupt entries are in use.
		EFAULT - A fault occurred when the kernel tried to access the buffers provided.
		EINVAL - The value of intr isn't a valid interrupt number.
		EPERM - The process doesn't have I/O privileges.
*/
int WriteMsr( uint32_t idx, uint64_t nval, uint64_t *oval ) {
	RdWrMsr_t  msr;
	int  id;

	msr.set  = 1;
	msr.nr   = idx;
	msr.nval = nval;
	SIGEV_INTR_INIT( &msr.event );

	if ( -1 != ( id = InterruptAttach( 0, IntrHnd, &msr, sizeof msr, 0 ) ) ) {
		while ( EINTR == ( errno = InterruptWait_r( 0, NULL ) ) )
			;

		if ( oval && ( errno == EOK ) )
			*oval = msr.oval;

		InterruptDetach_r( id );
	}

	return errno;
}


/*
	Map a name (short or long) to an MSR index. Short names are handled 
	case-sensitively, long ones case-insensitive.

	Returns:
		The index of the named MSR, or that of MSR_TSC if no match was found.
*/
static uint32_t StrToMsrIdx( const char *s ) {
	int  i;

	if ( isdigit( *s ) )
		return strtoul( s, NULL, 0 );

	for ( i = 0; i < ( sizeof MsrDefs / sizeof MsrDefs[0] ); i++ ) {
		if ( ! strcmp( s, MsrDefs[i].sname ) || ! stricmp( s, MsrDefs[i].lname ) )
			return MsrDefs[i].idx;
	}

	warnx( "MSR '%s' unknown - defaulting to TSC", s );
	fprintf( stderr, "Known MSRs:\n" );

	for ( i = 0; i < ( sizeof MsrDefs / sizeof MsrDefs[0] ); i++ )
		fprintf( stderr, "   %4s / %s\n", MsrDefs[i].sname, MsrDefs[i].lname );

	fprintf( stderr, "\n" );

	return MSR_TSC;
}


/*
	Map an MSR index to a long name.

	Returns:
		The long name of the indexed MSR, or "0x<idx>" if no match was found.
*/
static const char *MsrIdxToStr( uint32_t idx ) {
	static char  buf[30];
	int  i;

	for ( i = 0; i < ( sizeof MsrDefs / sizeof MsrDefs[0] ); i++ ) {
		if ( idx == MsrDefs[i].idx )
			return MsrDefs[i].lname;
	}

	sprintf( buf, "MSR 0x%x", idx );

	return buf;
}


/* No comment. */
int main( int argc, char *argv[] ) {
	uint64_t  nval = 0ULL, oval = 0ULL;
	uint32_t  idx = MSR_TSC;
	int  set = 0;

	/* 'parse' command line */
	if ( argc > 1 ) {
		idx = StrToMsrIdx( argv[1] );

		if ( argc > 2 ) {
			set = 1;
			nval = strtoull( argv[2], NULL, 0 );
		}
	}

	/* Acquire permission */
	ThreadCtl( _NTO_TCTL_IO, 0 );

	/* Now read or write as requested. */
	if ( set ) {
		if ( EOK != ( errno = WriteMsr( idx, nval, &oval ) ) )
			err( EXIT_FAILURE, "WriteMsr(0x%x)", idx );
		else
			printf( "%s : 0x%llx -> 0x%llx\n", MsrIdxToStr( idx ), oval, nval );
	}
	else {
		if ( EOK != ( errno = ReadMsr( idx, &oval ) ) )
			err( EXIT_FAILURE, "ReadMsr(0x%x)", idx );
		else
			printf( "%s : 0x%llx\n", MsrIdxToStr( idx ), oval );
	}

	return EXIT_SUCCESS;
}
