/*
 * kern/mcs_sync.c - ticket-based barriers and locks synchronization
 * 
 * Copyright (c) 2008,2009,2010,2011,2012 Ghassan Almaless
 * Copyright (c) 2011,2012 UPMC Sorbonne Universites
 *
 * This file is part of ALMOS-kernel.
 *
 * 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
 */

#include <config.h>
#include <types.h>
#include <mcs_sync.h>
#include <thread.h>
#include <cpu.h>
#include <kdmsg.h>

#define mcs_barrier_flush(_ptr)						\
	do{								\
		cpu_invalid_dcache_line(&(_ptr)->phase);		\
		cpu_invalid_dcache_line(&(_ptr)->ticket);		\
		cpu_invalid_dcache_line(&(_ptr)->ticket2);		\
		cpu_invalid_dcache_line(&(_ptr)->cntr);			\
	}while(0);

/////////////////////////////////////////////////
//FIXME: why all these dcache_invalidate ? [AG]
/////////////////////////////////////////////////

///////////////////////////////////////////
void mcs_barrier_init( mcs_barrier_t * ptr,
                       char          * name,
                       uint32_t        count )
{
	ptr->val.value     = count;
	ptr->phase.value   = 0;
	ptr->cntr.value    = count;
	ptr->ticket.value  = 0;
	ptr->ticket2.value = 0;
	ptr->name          = name;
	
	cpu_wbflush();
	cpu_invalid_dcache_line(&ptr->val);
	mcs_barrier_flush(ptr);
}

///////////////////////////////////////////
void mcs_barrier_wait( mcs_barrier_t * ptr)
{
	register uint32_t   phase;
	register uint32_t   order;
	uint32_t          * current;
	uint32_t          * next;
 
	phase   = ptr->phase.value;
	current = (phase == 0) ? &ptr->ticket.value : &ptr->ticket2.value;
	order   = cpu_atomic_add((void*)&ptr->cntr.value, -1);

	if(order == 1)
	{
		phase            = ~(phase) & 0x1;
		next             =  (phase == 0) ? &ptr->ticket.value : &ptr->ticket2.value;
		ptr->phase.value = phase;
		ptr->cntr.value  = ptr->val.value;
		*next            = 0;
		*current         = 1;
		cpu_wbflush();
		mcs_barrier_flush(ptr);
		return;
	}

	mcs_barrier_flush(ptr);

	while(cpu_load_word(current) == 0)
		;

	cpu_invalid_dcache_line(current);
}

/////////////////////////////////////
void mcs_lock_init( mcs_lock_t * ptr,
                    char       * name )
{
	ptr->cntr.value   = 0;
	ptr->ticket.value = 0;
	ptr->name         = name;
	
	cpu_wbflush();
	cpu_invalid_dcache_line(&ptr->cntr);
	cpu_invalid_dcache_line(&ptr->ticket);
}

////////////////////////////////
void mcs_lock( mcs_lock_t * ptr, 
               uint32_t   * irq_state )
{
	uint32_t ticket;

	cpu_disable_all_irq( irq_state );

	ticket = cpu_atomic_add( &ptr->ticket.value , 1 );

	while(ticket != cpu_load_word(&ptr->cntr.value))
		;

	CURRENT_THREAD->locks_count ++;
}

//////////////////////////////////
void mcs_unlock( mcs_lock_t * ptr, 
                 uint32_t     irq_state )
{
	register uint32_t next;
	volatile uint32_t *val_ptr;
	register struct thread_s *this;

	this	 = CURRENT_THREAD;

	val_ptr  = &ptr->cntr.value;
	next     = ptr->cntr.value + 1;

	cpu_wbflush();
	cpu_invalid_dcache_line((void*)val_ptr);

	*val_ptr = next;

	cpu_wbflush();
	cpu_invalid_dcache_line((void*)val_ptr);

	full_assert(this, this->locks_count > 0);
	this->locks_count --;
	cpu_restore_irq(irq_state);

    // pourquoi pas simplement le code ci-dessous ? [AG]
    // ptr->cntr.value ++;
	// cpu_wbflush();
    // CURRENT_THREAD->locks_count --;
	// cpu_restore_irq( irq_state );
    
}

///////////////////////////////////////
void mcs_lock_remote( mcs_lock_t * ptr,
                      cid_t        cid, 
                      uint32_t   * irq_state )
{
	uint32_t ticket;

	cpu_disable_all_irq( irq_state );

	ticket = remote_atomic_add( &ptr->ticket.value , cid , 1 );

	while( ticket != remote_lw( &ptr->cntr.value , cid ) )
		;

	CURRENT_THREAD->distlocks_count ++;
}

/////////////////////////////////////////
void mcs_unlock_remote( mcs_lock_t * ptr, 
                        cid_t        cid, 
                        uint32_t     irq_state )
{
	register uint32_t next;
	volatile uint32_t *val_ptr;
	register struct thread_s *this;

	this	 = CURRENT_THREAD;
	val_ptr  = &ptr->cntr.value;
	next     = remote_lw((void*)val_ptr, cid) + 1;

	cpu_wbflush();
	cpu_invalid_dcache_line((void*)val_ptr);

	remote_sw((void*)val_ptr, cid, next);

	cpu_wbflush();
	cpu_invalid_dcache_line((void*)val_ptr);

	full_assert(this, this->distlocks_count > 0);
	this->distlocks_count --;
	cpu_restore_irq(irq_state);
}
