/*
 * ppm.h - Per-cluster Physical Pages Manager definition.
 *
 * Authors  Ghassan Almaless (2008,2009,2010,2011,2012)
 *          Alain Greiner    (2016,2017,2018,2019)
 *
 * Copyright (c) UPMC Sorbonne Universites
 *
 * This file is part of ALMOS-MKH.
 *
 * ALMOS-kernel 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.0 of the License.
 *
 * ALMOS-kernel 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 ALMOS-kernel; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef _PPM_H_
#define _PPM_H_

#include <hal_kernel_types.h>
#include <list.h>
#include <busylock.h>
#include <remote_queuelock.h>
#include <boot_info.h>
#include <page.h>


/*****************************************************************************************
 * This structure defines the Physical Pages Manager in a cluster.
 * In each cluster, the physical memory bank starts at local physical address 0 and
 * contains an integer number of small pages, defined by the <pages_nr> field in the
 * boot_info structure. It is split in three parts:
 *
 * - the "kernel_code" section contains the kernel code, loaded by the boot-loader.
 *   It starts at local PPN = 0 and the size is defined by the <pages_offset> field
 *   in the boot_info structure.
 * - the local "pages_tbl" section contains the physical page descriptors array.
 *   It starts at local PPN = pages_offset, and it contains one entry per small page.
 *   It is created and initialized by the hal_ppm_create() function. 
 * - The "kernel_heap" section contains all physical pages that are are not in the
 *   "kernel_code" and "pages_tbl" sections, and that have not been reserved.
 *   The reserved pages are defined in the boot_info structure.
 *
 * The main service provided by the PMM is the dynamic allocation of physical pages
 * from the "kernel_heap" section. This low-level allocator implements the buddy 
 * algorithm: an allocated block is an integer number n of small pages, where n 
 * is a power of 2, and ln(n) is called order.
 * This allocator being shared by the local threads, the free_page lists rooted 
 * in the PPM descriptor are protected by a local busylock, because it is used
 * by the idle_thread during kernel_init().
 *
 * Another service is to register the dirty pages in a specific dirty_list, that is
 * also rooted in the PPM, in order to be able to synchronize all dirty pages on disk.
 * This dirty list is protected by a specific remote_queuelock, because it can be
 * modified by a remote thread, but it contains only local pages.
 ****************************************************************************************/

typedef struct ppm_s
{
	busylock_t          free_lock;          /*! lock protecting free_pages[] lists      */
	list_entry_t        free_pages_root[CONFIG_PPM_MAX_ORDER];  /*! roots of free lists */
	uint32_t            free_pages_nr[CONFIG_PPM_MAX_ORDER];    /*! free pages number   */
	page_t            * pages_tbl;          /*! pointer on page descriptors array       */
	uint32_t            pages_nr;           /*! total number of small physical page     */
    remote_queuelock_t  dirty_lock;         /*! lock protecting dirty pages list        */
    list_entry_t        dirty_root;         /*! root of dirty pages list                */
    void              * vaddr_base;         /*! pointer on local physical memory base   */
}
ppm_t;

/************** functions to allocate / release physical pages  *************************/

/*****************************************************************************************
 * This is the low-level physical pages allocation function.
 * It allocates N contiguous physical pages. N is a power of 2.
 * In normal use, it should not be called directly, as the recommended way to get
 * physical pages is to call the generic allocator defined in kmem.h.
 *****************************************************************************************
 * @ order        : ln2( number of 4 Kbytes pages)
 * @ returns a pointer on the page descriptor if success / NULL otherwise
 ****************************************************************************************/
page_t * ppm_alloc_pages( uint32_t order );

/*****************************************************************************************
 * This is the low-level physical pages release function. It takes the lock protecting
 * the free_list before register the released page in the relevant free_list.
 * In normal use, you do not need to call it directly, as the recommended way to free
 * physical pages is to call the generic allocator defined in kmem.h.
 *****************************************************************************************
 * @ page         : pointer to the page descriptor to be released
 ****************************************************************************************/
void ppm_free_pages( page_t * page );

/*****************************************************************************************
 * This function does the same as the ppm_free_page() function, without taking the lock.
 * It is used by the hal_ppm_init() function to initialize the pages_tbl[] array, when
 * there is no concurrent access issue.
 *****************************************************************************************
 * @ page         : pointer to the page descriptor to be released
 ****************************************************************************************/
void ppm_free_pages_nolock( page_t * page );

/*****************************************************************************************
 * This function check if a page descriptor pointer is valid.
 *****************************************************************************************
 * @ page         : pointer on a page descriptor
 * @ returns true if valid / false otherwise.
 ****************************************************************************************/
inline bool_t ppm_page_is_valid( page_t * page );


/************** functions to translate [ page <->  base <-> ppn ] ***********************/

/*****************************************************************************************
 * Get extended pointer on page base from extended pointer on page descriptor.
 *****************************************************************************************
 * @ page_xp    : extended pointer to page descriptor
 * @ returns extended pointer on page base.
 ****************************************************************************************/
inline xptr_t ppm_page2base( xptr_t page_xp );

/*****************************************************************************************
 * Get extended pointer on page descriptor from extended pointer on page base.
 *****************************************************************************************
 * @ base_xp   : extended pointer to page base.
 * @ returns extended pointer on page descriptor
 ****************************************************************************************/
inline xptr_t ppm_base2page( xptr_t base_xp );

/*****************************************************************************************
 * Get extended pointer on page base from global PPN.
 *****************************************************************************************
 * @ ppn    : global physical page number.
 * @ returns extended pointer on page base.
 ****************************************************************************************/
inline xptr_t ppm_ppn2base( ppn_t ppn );

/*****************************************************************************************
 * Get global PPN from extended pointer on page base.
 *****************************************************************************************
 * @ base_xp   : extended pointer to page base.
 * @ returns global physical page number.
 ****************************************************************************************/
inline ppn_t ppm_base2ppn( xptr_t base_xp );

/*****************************************************************************************
 * Get global PPN from extended pointer on page descriptor.
 *****************************************************************************************
 * @ page_xp   : pointer to page descriptor
 * @ returns global physical page number.
 ****************************************************************************************/
inline ppn_t ppm_page2ppn( xptr_t page_xp );

/*****************************************************************************************
 * Get extended pointer on page descriptor from global PPN.
 *****************************************************************************************
 * @ ppn       : global physical page number
 * @ returns extended pointer on page descriptor.
 ****************************************************************************************/
inline xptr_t ppm_ppn2page( ppn_t ppn );


/*********** debug  functions  **********************************************************/

/*****************************************************************************************
 * This function prints the PPM allocator status in the calling thread cluster.
 *****************************************************************************************
 * string   : character string printed in header
 ****************************************************************************************/
void ppm_print( char * string );

/*****************************************************************************************
 * This function checks PPM allocator consistency.
 *****************************************************************************************
 * @ ppm      : pointer on PPM allocator.
 * @ return 0 if PPM is OK / return -1 if PPM not consistent.
 ****************************************************************************************/
error_t ppm_assert_order( ppm_t * ppm );


/*********** functions to handle dirty pages  *******************************************/

/*****************************************************************************************
 * This function registers a page identified by the <page_xp> argument as dirty. 
 * It can be called by a thread running in any cluster.
 * - it takes the queuelock protecting the PPM dirty_list.
 * - it takes the busylock protecting the page flags.
 * - it test the PG_DIRTY flag in the page descriptor.
 *   . if page already dirty => do nothing
 *   . it page not dirty => set the PG_DIRTY flag and register page in PPM dirty list.
 * - it releases the busylock protecting the page flags.
 * - it releases the queuelock protecting the PPM dirty_list.
 *****************************************************************************************
 * @ page_xp  : extended pointer on page descriptor.
 * @ returns true if page was not dirty / returns false if page was dirty
 ****************************************************************************************/
bool_t ppm_page_do_dirty( xptr_t page_xp );

/*****************************************************************************************
 * This function unregisters a page identified by the <page_xp> argument as dirty. 
 * It can be called by a thread running in any cluster.
 * - it takes the queuelock protecting the PPM dirty_list.
 * - it takes the busylock protecting the page flags.
 * - it test the PG_DIRTY flag in the page descriptor.
 *   . if page not dirty => do nothing
 *   . it page dirty => reset the PG_DIRTY flag and remove page from PPM dirty list.
 * - it releases the busylock protecting the page flags.
 * - it releases the queuelock protecting the PPM dirty_list.
 *****************************************************************************************
 * @ page_xp  : extended pointer on page descriptor.
 * @ returns true if page was dirty / returns false if page was not dirty
 ****************************************************************************************/
bool_t ppm_page_undo_dirty( xptr_t page_xp );

/*****************************************************************************************
 * This function synchronizes (i.e. update the IOC device) all dirty pages in a cluster.
 * - it takes the queuelock protecting the PPM dirty_list.
 * - it scans the PPM dirty list, and for each page:
 *   . it takes the lock protecting the page.
 *   . it removes the page from the PPM dirty_list.
 *   . it reset the PG_DIRTY flag.
 *   . it releases the lock protecting the page.
 * - it releases the queuelock protecting the PPM dirty_list.
 $ The PPM dirty_list is empty when the sync operation completes.
 ****************************************************************************************/
void ppm_sync_dirty_pages( void );

#endif	/* _PPM_H_ */
