/*
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; version 2.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should find a copy of v2 of the GNU General Public License somewhere on
 * your Linux system; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * Copyright (C) 2012 Intel corporation
 */

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

/*
 * Definition of /proc/pid/pagemap
 * Bits 0-54  page frame number (PFN) if present
 * Bits 0-4   swap type if swapped
 * Bits 5-54  swap offset if swapped
 * Bits 55-60 page shift (page size = 1<<page shift)
 * Bit  61    reserved for future use
 * Bit  62    page swapped
 * Bit  63    page present
 */

struct pagemaps {
	unsigned long long	pfn:55;
	unsigned long long	pgshift:6;
	unsigned long long	rsvd:1;
	unsigned long long	swapped:1;
	unsigned long long	present:1;
};


/*
 * get information about address from /proc/{pid}/pagemap
 */
unsigned long long vtop(unsigned long long addr)
{
	static int pagesize;
	struct pagemaps pinfo;
	unsigned int pinfo_size = sizeof pinfo;
	long offset;
	int fd, pgmask;
	char pagemapname[64];

	if (!pagesize)
		pagesize = getpagesize();
	offset = addr / pagesize * pinfo_size;
	sprintf(pagemapname, "/proc/%d/pagemap", getpid());
	fd = open(pagemapname, O_RDONLY);
	if (fd == -1) {
		perror(pagemapname);
		return 0;
	}
	if (pread(fd, (void*)&pinfo, pinfo_size, offset) != pinfo_size) {
		perror(pagemapname);
		close(fd);
		return 0;
	}
	close(fd);
	if (!pinfo.present)
		return ~0ull;
	pgmask = (1 << pinfo.pgshift) - 1;
	return (pinfo.pfn << pinfo.pgshift) | (addr & pgmask);
}

int main()
{
	char *p;
	unsigned long long phys, newphys;

	p = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	if (p == MAP_FAILED) {
		perror("mmap");
		return 1;
	}
	*p = '*'; /* make kernel allocate page */
	phys = vtop((unsigned long long)p);

	printf("allocated page: virtual = %p physical = 0x%llx\n", p, phys);
	fflush(stdout);

	for (;;) {
		newphys = vtop((unsigned long long)p);
		if (phys != newphys) {
			printf("Page was replaced. New physical address = 0x%llx\n", newphys);
			fflush(stdout);
			phys = newphys;
		}
		sleep(1);
	}
}
