/* Simple assembler V1.06 24/2/08
   See sasm.h for docs
   Copyright 2008 Jeffrey Lee
   This file is part of WOUM.
   WOUM 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.
   WOUM 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 WOUM.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef _SASM_C
#define _SASM_C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "kernel.h"
#include "swis.h"

#include "sasm.h"
#include "error.h"

int sasm_capabilities = 0;

static int shift(int rm,int type,int amt)
{
	if (amt == 0)
		type = LSL;
	return (type << 5) + ((amt & 31) << 7) + rm;
}

static int shift2(int rm,int type,int rs)
{
	return (type << 5) + 128 + (rs << 8) + rm;
}

int sasm_getcapabilities()
{
	_kernel_swi_regs regs;
	regs.r[0] = regs.r[1] = 0;
	_kernel_swi(OS_Byte,&regs,&regs);
	if(!regs.r[0])
		return 0;
	char *s = (char *) (regs.r[0]+4);
	float ver;
	if(sscanf(s,"RISC OS %f",&ver) != 1)
		return 0; /* Not RISC OS? */
	if(ver < 3.5) /* Pre RiscPC, so likely no MRC */
		return 0;
	int cpuid = sasm_getcpuid();
	/* Now attempt to identify CPU architecture */
	if((cpuid & 0xF000) == 0)
		return 0; /* Pre-ARM7. Nothing interesting here. */
	if((cpuid & 0xF000) == 0x7000)
		return SASM_M; /* ARM7. Just long mul which can be used reliably (I hope!) */
	/* Else, we can get the architecture... */
	switch((cpuid >> 16) & 0xF)
	{
		case 1: /* 4 */
		case 2: /* 4T */
			/* It's possible this is an A9home - if so, we'll be able to support halfwords. Use OS_ReadSysInfo to check. */
			if(ver >= 4.00)
			{
				regs.r[0] = 8;
				if(!_kernel_swi(OS_ReadSysInfo,&regs,&regs))
					if(regs.r[0] == 7)
						return SASM_H+SASM_M;
			}
			return SASM_M;
		case 3: /* 5 */
		case 4: /* 5T */
			return SASM_H+SASM_M; /* This is post-StrongARM, so post-RiscPC, so should support halfwords properly */ 
		case 5: /* 5TE */
		case 6: /* 5TEJ */
		case 7: /* 6 */
			return SASM_H+SASM_M+SASM_E+SASM_PLD;
		default: /* New architecture - it's likely to be 6+, but we'll take the safe route */
			return 0;
	}
}

void sasm_b(int *a,int c,int l,int *d)
{
	int o;
	*a = (c << 28) + 0xa000000 + (l << 24);
	o = ((int) d)-((int) a); /* Branch offset */
	o -= 8; /* Account for prefetch */
	o = o >> 2; /* Shift across */
	o &= 0xFFFFFF; /* Cut to fit */
	*a += o;
}

void sasm_alu(int *a,int c,int o,int rd,int rn,int rm,int type,int amt)
{
	if ((o == TEQ) || (o == TST) || (o == CMP) || (o == CMN))
		o += S;
	*a = (c << 28) + (o << 20) + (rn << 16) + (rd << 12) + shift(rm,type,amt);
}

void sasm_alu2(int *a,int c,int o,int rd,int rn,int rm,int type,int rs)
{
	if ((o == TEQ) || (o == TST) || (o == CMP) || (o == CMN))
		o += S;
	*a = (c << 28) + (o << 20) + (rn << 16) + (rd << 12) + shift2(rm,type,rs);
}

void sasm_alu3(int *a,int c,int o,int rd,int rn,int val)
{
	int r;
	if ((o == TEQ) || (o == TST) || (o == CMP) || (o == CMN))
		o += S;
	*a = (c << 28) + (1 << 25) + (o << 20) + (rn << 16) + (rd << 12);
	/* Add op2 */
	r = 0;
	while ((val & 255) != val)
	{
		r++;
		if (val < 0)
			val = (val << 1) + 1;
		else
			val = val << 1;
		if (val < 0)
			val = (val << 1) + 1;
		else
			val = val << 1;
		if (r > 15)
			error("Bad immediate constant in alu3");
	}
	*a += (r << 8) + val;
}

void sasm_mul(int *a,int c,int s,int rd,int rm,int rs)
{
	if (rd == rm)
		error("rd = rm in mul");
	*a = (c << 28) + (s << 20) + (rd << 16) + (rs << 8) + 0x90 + rm;
}

void sasm_mla(int *a,int c,int s,int rd,int rm,int rs,int rn)
{
	if (rd == rm)
		error("rd = rm in mla");
	*a = (c << 28) + ((s+2) << 20) + (rd << 16) + (rn << 12) + (rs << 8) + 0x90 + rm;
}

void sasm_mem(int *a,int c,int o,int rd,int rn,int rm,int type,int amt)
{
	/* Check SI/H */
	if(o & (H | SI))
		*a = (c << 28) + ((o & (L+W+U+PRE)) << 20) + (rn << 16) + (rd << 12) + 0x90 + (o & (H+SI)) + rm; /* What's SBZ?? */
	else
		*a = (c << 28) + (3 << 25) + (o << 20) + (rn << 16) + (rd << 12) + shift(rm,type,amt);
}

void sasm_mem2(int *a,int c,int o,int rd,int rn,int d)
{
	/* Check SI/H */
	if(o & (H | SI))
		*a = (c << 28) + ((o & (L+W+U+PRE)) << 20) + (1 << 22) + (rn << 16) + (rd << 12) + 0x90 + (o & (H+SI)) + ((d & 0xF0) << 4) + (d & 0xF);
	else
		*a = (c << 28) + (2 << 25) + (o << 20) + (rn << 16) + (rd << 12) + (d & 0xFFF);
}

void sasm_mem3(int *a,int c,int o,int rd,int *d)
{
	int t;
	*a = (c << 28) + (2 << 25) + (o << 20) + (15 << 16) + (rd << 12);
	t = ((int) d)-((int) a); /* Offset */
	t -= 8; /* Account for prefetch */
	if (t >= 0) /* Work out if Up or Down offset needed */
		*a |= (1 << 23);
	else
		t-=t;
	*a += (t & 0xFFF);
	*a |= (1 << 24); /* Set PRE, in case the user hasn't */
}

void sasm_mem4(int *a,int c,int o,int rn,int regs)
{
	*a = (c << 28) + (1 << 27) + (o << 20) + (rn << 16) + regs;
}

void sasm_swi(int *a,int c,int n)
{
	*a = (c << 28) + 0xF000000 + n;
}

void sasm_sync(int *a,int l)
{
	_kernel_swi_regs regs;
	regs.r[0] = 1;
	regs.r[1] = (int) a;
	regs.r[2] = regs.r[1] + l-4;
	_kernel_swi(OS_SynchroniseCodeAreas,&regs,&regs);
}

int sasm_name(int *a,char *n)
{
	int l;
	char *c;
	c = (char *) a;
	l = strlen(n);
	strcpy(c,n);
	c += l;
	do {
		*c = 0;
		c++;
		l++;
	} while (l & 3); /* Go to next word boundary */
	a = (int *) c;
	*a = 0xFF000000 + l; /* Write length field */
	return l+4;
}

void sasm_adr(int *a,int c,int rd,int *d)
{
	int o;
	o = ((int) d)-((int) a); /* Offset */
	o -= 8; /* Prefetch */
	sasm_alu3(a,c,MOV,rd,15,o);
}

char *sasm_dis(int *a)
{
	_kernel_swi_regs regs;
	regs.r[0] = *a;
	regs.r[1] = (int) a;
	_kernel_swi(Debugger_Disassemble,&regs,&regs);
	return (char *) regs.r[1];
}

#endif
