/* Copyright (C) 2008 - Ruben Niederhagen
 * 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 3
 * 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, you will find it online at
 * http://www.gnu.org/licenses/gpl.html
 */

#include <stdio.h>
#include <unistd.h>
#include <libspe2.h>
#include <stdint.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>

#include "include/iolaos.h"
#include "src/common/common.h"

typedef struct ppu_pthread_data
{
	spe_context_ptr_t speid;
	pthread_t pthread;
	unsigned int entry;
	unsigned int flags;
	void *argp;
	void *envp;
	spe_stop_info_t stopinfo;
} ppu_pthread_data_t;

struct spe_handle
{
	uint8_t spe_threads;

	ppu_pthread_data_t *ppdata;
	uint32_t *phys_id;

	addr64_t *ls;

	local_parameters_t *local_parameters;

	global_parameters_t global_parameters __attribute__((aligned (16)));

	uint8_t barrier_cacheline[128] __attribute__((aligned (128)));
}; // typedef spe_handle_t in include/ppu/tools.h

//gets the physical ID (0...15) of a given SPE by parsing /spu/spethread-{PID}-{SPEID}/phys-id
int getPhysID(spe_context_ptr_t tSpeID)
{
	int tPhysID = -1, tPid = getpid();
	char fname[80];
	FILE *fp;

	sprintf(fname, "/spu/spethread-%d-%ld/phys-id", tPid, (long int)tSpeID);
	fp = fopen( fname, "r");
	if (fp != NULL)
	{
		char line[80];
		fread( line, sizeof line, 1, fp);
		if(strncmp(line, "0x", 2) == 0) {
			//Hexencoded
			sscanf(&line[2], "%x", &tPhysID);
		} else {
			//Decimal
			tPhysID = strtol(line, (char**)NULL, 0);
		}
		fclose(fp);
	}

	return tPhysID;
}

//launch 1 spe in a ppu thread
void *ppu_pthread_function(void *arg)
{
	ppu_pthread_data_t *datap = (ppu_pthread_data_t *)arg;
	int rc;

	do
	{
		rc = spe_context_run(datap->speid, &datap->entry,
				datap->flags, datap->argp, datap->envp, &datap->stopinfo);
	}
	while (rc > 0); /* until exit or error, while stop & signal */
	pthread_exit(NULL);

	return NULL;
}

spe_handle_t* init_spes(spe_program_handle_t *program, uint8_t spe_threads, 
								void *globals, uint16_t globals_size,
								void *locals, uint16_t locals_size)
{
	int i;

	spe_handle_t* spe_handle;

	posix_memalign((void**)&spe_handle, 128, sizeof(spe_handle_t));

	spe_handle->spe_threads = spe_threads;

	posix_memalign((void**)&spe_handle->local_parameters, 16,
						sizeof(local_parameters_t)*spe_handle->spe_threads);

	posix_memalign((void**)&spe_handle->ls, 16,
						sizeof(addr64_t)*((spe_handle->spe_threads & 1) == 1 ? 
													spe_handle->spe_threads + 1 : 
													spe_handle->spe_threads));

	posix_memalign((void**)&spe_handle->ppdata, 16, sizeof(ppu_pthread_data_t)*spe_handle->spe_threads); 

	posix_memalign((void**)&spe_handle->phys_id, 16, sizeof(uint32_t)*spe_handle->spe_threads);

	spe_handle->global_parameters.global_data.p = globals;
	spe_handle->global_parameters.global_data_size = globals_size;
	spe_handle->global_parameters.barrier_cacheline.p = &spe_handle->barrier_cacheline;
#ifndef __powerpc64__
	spe_handle->global_parameters.global_data.pad = 0;
	spe_handle->global_parameters.local_stores.pad = 0;
	spe_handle->global_parameters.barrier_cacheline.pad = 0;
#endif

	for(i=0; i < spe_handle->spe_threads; i++)
	{
		spe_handle->ppdata[i].speid = spe_context_create(0, NULL);

		spe_program_load(spe_handle->ppdata[i].speid, program);

		spe_handle->local_parameters[i].id = i;
		spe_handle->local_parameters[i].local_data_size = locals_size;
		spe_handle->local_parameters[i].local_data.p = locals + i * locals_size;
#ifndef __powerpc64__
		spe_handle->local_parameters[i].local_data.pad = 0;
#endif

		spe_handle->ppdata[i].entry = SPE_DEFAULT_ENTRY;
		spe_handle->ppdata[i].flags = 0;
		// address of start of data
		spe_handle->ppdata[i].argp = (void*)(&spe_handle->local_parameters[i]);
		spe_handle->ppdata[i].envp = (void*)(&spe_handle->global_parameters);

#ifndef __powerpc64__
		spe_handle->ls[i].pad = 0;
#endif
		spe_handle->ls[i].p = spe_ls_area_get(spe_handle->ppdata[i].speid);
	}

	spe_handle->global_parameters.local_stores.p = (void*)&spe_handle->ls[0];
	spe_handle->global_parameters.spe_threads = spe_handle->spe_threads;

	return spe_handle;
}

void start_spes(spe_handle_t* spe_handle)
{
	int i, j;

	//create a pthread for every spe-thread by calling ppu_thread_function
	for(i=0; i < spe_handle->spe_threads; i++)
	{
		pthread_create(&spe_handle->ppdata[i].pthread, NULL, 
							&ppu_pthread_function, &spe_handle->ppdata[i]);
	}

	//the spe-thread must be running to get the physical id
	for (i = 0; i < spe_handle->spe_threads; i++)
	{
		int id = -1;

		while ((id < 0) || (id > 15))
		{
			id = getPhysID(spe_handle->ppdata[i].speid);
		}

		spe_handle->phys_id[i] = id;
	}

	//signal the spe-threads to execute
	for (i = 0; i < spe_handle->spe_threads; i++)
		for (j = 0; j < spe_handle->spe_threads; j++)
			spe_in_mbox_write(spe_handle->ppdata[i].speid, &(spe_handle->phys_id[j]), 1,
									SPE_MBOX_ALL_BLOCKING);
}

void join_spes(spe_handle_t* spe_handle)
{
	int i;

	for(i=0; i < spe_handle->spe_threads; i++)
	{
		if (pthread_join (spe_handle->ppdata[i].pthread, NULL))
		{
			perror("Failed pthread_join");
			exit (1);
		}
	}
}

void free_spes(spe_handle_t* spe_handle)
{
	free(spe_handle->local_parameters);
	free(spe_handle->ls);
	free(spe_handle->ppdata);
	free(spe_handle->phys_id);

	free(spe_handle);
}
