/*
 * (c) Copyright IBM Corp. 2005 All Rights Reserved
 *
 * Physical Memory Information Module
 *
 * 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; either version 2 of the License, or
 * at your option any later version.
 *
 * 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 have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

/**
 * @file phys_parser.c
 *
 * @brief Parser for the physical page structures.
 *
 * This part provides the source for the physical page parser
 * that scans and formats the physical page list.
 *
 * @author International Business Machines
 * @author Paul Movall <movall@us.ibm.com>
 *
 * @version Current Version: 1.0
 *
 * @date Current Date: 01/2005
 *
 * @version 0.1, 11/2003: File created by Paul Movall <movall@us.ibm.com>
 * @version 1.0, 01/2005: Miscellaneous cleanup for publish
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/param.h>
#include "td.h"

/**
 * @struct phys_page_struct
 * @brief A physical page structure.
 *
 * These structures are built from the map information provided
 * by the input td file. Each page is counted and is attempted
 * to be associated with a file.
 */
/**
 * @typedef phys_page_t
 * @brief Typedef for the @c phys_page_struct structure.
 */
typedef struct phys_page_struct {
	uint32_t addr;		/**< The physical address of the page.	*/
	uint32_t pte_flags;	/**< The PTE flags for this page.	*/
	uint32_t pg_flags;	/**< The kernel page struct flags.	*/
	uint32_t count;		/**< Usage count of this page.		*/
	uint32_t vma_flags;	/**< The flags for the VMA to which
				 * this page belongs.			*/
	char name[PATH_MAX];	/**< Name of associated file.		*/
	struct phys_page_struct *next;	/**< Pointer to next in list.	*/
} phys_page_t;

/**
 * @brief Global for head of ordered (by address) list of pages.
 */
phys_page_t *head_phys;

/** @def PTE_PAGE_MASK The mask for what part of the PTE are the
 * physical address and which are flags.
 */
#define PTE_PAGE_MASK 0xFFFFF000

/**
 * @brief Add a physical page structure to the list.
 *
 * This function will search the global page list and if the page
 * is not present, will add it to the list. It maintains the list
 * in order by the physical address.
 *
 * It also returns an indication of whether the page was new to
 * the list or already in the list.
 *
 * @param new_phys The physical page to add to the list.
 *
 * @retval TRUE The page was already in the list.
 * @retval FALSE The page was new to the list and has been added.
 */
int add_phys_page(phys_page_t *new_phys) {
	phys_page_t *cur_phys, *prev_phys;
	int match;

	for (prev_phys = cur_phys = head_phys, match = FALSE;
	     ( (cur_phys != NULL) &&
	       (!match) &&
	       (cur_phys->addr <= new_phys->addr) );
	     prev_phys = cur_phys, cur_phys = cur_phys->next) {
		if (cur_phys->addr == new_phys->addr) {
			match = TRUE;
		}
	}

	if (!match) {
		if (head_phys == NULL) {
			head_phys = new_phys;
		} else if (cur_phys != NULL) {
			new_phys->next = cur_phys;
			if (cur_phys == head_phys) {
				head_phys = new_phys;
			} else {
				prev_phys->next = new_phys;
			}
		} else {
			new_phys->next = cur_phys;
			prev_phys->next = new_phys;
		}
	}

	return(match);
}

/**
 * @brief Build the physical page list from the task list.
 *
 * This function walks all of the mappings in all of the VMAs for all
 * of the tasks in the global task list, adding any new physical
 * pages to the list.
 *
 * @return None.
 */
void build_phys_list(void) {
	task_t *cur_task;
	vma_t *cur_vma;
	map_t *cur_map;
	phys_page_t *this_page;

	if (head_phys == NULL) {
		for (cur_task = head_task.head;
		     cur_task != NULL;
		     cur_task = cur_task->next) {
			for (cur_vma = cur_task->vma_head;
			     cur_vma != NULL;
			     cur_vma = cur_vma->next) {

				for (cur_map = cur_vma->map_head;
				     cur_map != NULL;
				     cur_map = cur_map->next) {
					this_page = (phys_page_t *)
					  malloc(sizeof(phys_page_t));
					memset(this_page,
					       0,
					       sizeof(phys_page_t));
					this_page->next = NULL;

					this_page->addr =
					  cur_map->phys & PTE_PAGE_MASK;
					this_page->pte_flags =
					  cur_map->phys & ~PTE_PAGE_MASK;
					this_page->vma_flags =
					  cur_vma->vma_flags;
					this_page->pg_flags =
					  cur_map->flags;
					this_page->count =
					  cur_map->count;
					strcpy(this_page->name,
					       ( (cur_vma->vma_flags &
						  (VMA_FLAGS_BSS |
						   VMA_FLAGS_STACK)) ?
						 cur_vma->assoc_name :
						 cur_vma->name)
					       );

					if (add_phys_page(this_page)) {
						/*
						 * this page matched one already
						 * in list so free memory
						 */
						free(this_page);
					} else {
						/*
						 * got added to list,
						 * leave alone
						 */
					}
				}
			}
		}
	}
}

/**
 * @brief Analyze the task list to count the number of physical pages.
 *
 * This function will parse the input data to count the number of physical pages
 * that are being used. It will first call the function to build the global
 * page list to insure that all of the pages are in the list. It will then
 * walk the global page list, counting the number of structures within it.
 *
 * @param outf Output file handle.
 *
 * @return None
 *
 * @post The output will be performed to @c outf.
 */
void phys_count(FILE *outf) {
	phys_page_t *this_page;
	int pg_count;

	uint32_t prev_addr = 0;

	build_phys_list();

	for (pg_count = 0, this_page = head_phys;
	     this_page != NULL;
	     this_page = this_page->next) {

		if (prev_addr >= this_page->addr) {
			fprintf(stderr,
				"Error parsing physical page list! "
				"Prev: %08lX, Current: %08lX\n",
				prev_addr, this_page->addr);
			exit(-1);
		}

		pg_count++;
	}

	if (!terse) {
		fprintf(outf, "User space pages: ");
	}
	fprintf(outf, "%d\n", pg_count);
}

/** @def TERSE_FORMAT The output format for terse (CSV) mode.		*/
#define TERSE_FORMAT	"%#010lX, %#010lX, %#010lX, %c%c%c%c%c%c, %#010lX, " \
"%d, %s\n"
/** #def VERBOSE_FORMAT The output format for human readable mode	*/
#define VERBOSE_FORMAT	"%#010lX\t%#010lX\t%#010lX\t%c%c%c%c%c%c\t%#010lX\t" \
"%d\t%s\n"

/**
 * @brief Analyze the task list to dump out all of the used physical pages.
 *
 * This function will parse the input data to dump all of  the physical pages
 * that are being used. It will first call the function to build the global
 * page list to insure that all of the pages are in the list. It will then
 * walk the global page list, printing out the various page struct fields.
 *
 * @param outfp Output file handle.
 *
 * @return None
 *
 * @post The output will be performed to @c outfp.
 */
void dump_phys_pages(FILE *outfp) {
	phys_page_t *this_page;
	char *format;

	build_phys_list();

	if (terse) {
		fprintf(outfp,
			"Address, PTE Flags, VMA Flags, VMA Symbols, "
			"Page Flags, Count, Owner\n");
		format = TERSE_FORMAT;
	} else {
		fprintf(outfp,
			"%-8.8s\t%-8.8s\t%-8.8s\t%-8.8s\t%-8.8s\t%-8.8s\t"
			"%-8.8s\n"
			"Address",
			"PTE Flags",
			"VMA Flags",
			"VMA Symbols",
			"Page Flags",
			"Count",
			"Owner");
		format = VERBOSE_FORMAT;
	}

	for (this_page = head_phys;
	     this_page != NULL;
	     this_page = this_page->next) {
		fprintf(outfp, format,
			this_page->addr,
			this_page->pte_flags,
			this_page->vma_flags,
			this_page->vma_flags & VMA_FLAGS_STACK ? 'S' : '.',
			this_page->vma_flags & VMA_FLAGS_EXECUTABLE ? 'X' : '.',
			this_page->vma_flags & VMA_FLAGS_RD ? 'r' : '.',
			this_page->vma_flags & VMA_FLAGS_WR ? 'w' : '.',
			this_page->vma_flags & VMA_FLAGS_EXEC ? 'x' : '.',
			this_page->vma_flags & VMA_FLAGS_BSS ? 'B' : '.',
			this_page->pg_flags,
			this_page->count,
			this_page->name);
	}
}

