/*
* altera.c
*
* altera FPGA driver
*
* Copyright (C) Altera Corporation 1998-2001
* Copyright (C) 2010,2011 NetUP Inc.
* Copyright (C) 2010,2011 Igor M. Liplianin <liplianin@netup.ru>
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <asm/unaligned.h>
#include <linux/ctype.h>
#include <linux/string.h>
#include <linux/firmware.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <misc/altera.h>
#include "altera-exprt.h"
#include "altera-jtag.h"
static int debug = 1;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "enable debugging information");
MODULE_DESCRIPTION("altera FPGA kernel module");
MODULE_AUTHOR("Igor M. Liplianin <liplianin@netup.ru>");
MODULE_LICENSE("GPL");
#define dprintk(args...) \
if (debug) { \
printk(KERN_DEBUG args); \
}
enum altera_fpga_opcode {
OP_NOP = 0,
OP_DUP,
OP_SWP,
OP_ADD,
OP_SUB,
OP_MULT,
OP_DIV,
OP_MOD,
OP_SHL,
OP_SHR,
OP_NOT,
OP_AND,
OP_OR,
OP_XOR,
OP_INV,
OP_GT,
OP_LT,
OP_RET,
OP_CMPS,
OP_PINT,
OP_PRNT,
OP_DSS,
OP_DSSC,
OP_ISS,
OP_ISSC,
OP_DPR = 0x1c,
OP_DPRL,
OP_DPO,
OP_DPOL,
OP_IPR,
OP_IPRL,
OP_IPO,
OP_IPOL,
OP_PCHR,
OP_EXIT,
OP_EQU,
OP_POPT,
OP_ABS = 0x2c,
OP_BCH0,
OP_PSH0 = 0x2f,
OP_PSHL = 0x40,
OP_PSHV,
OP_JMP,
OP_CALL,
OP_NEXT,
OP_PSTR,
OP_SINT = 0x47,
OP_ST,
OP_ISTP,
OP_DSTP,
OP_SWPN,
OP_DUPN,
OP_POPV,
OP_POPE,
OP_POPA,
OP_JMPZ,
OP_DS,
OP_IS,
OP_DPRA,
OP_DPOA,
OP_IPRA,
OP_IPOA,
OP_EXPT,
OP_PSHE,
OP_PSHA,
OP_DYNA,
OP_EXPV = 0x5c,
OP_COPY = 0x80,
OP_REVA,
OP_DSC,
OP_ISC,
OP_WAIT,
OP_VS,
OP_CMPA = 0xc0,
OP_VSC,
};
struct altera_procinfo {
char *name;
u8 attrs;
struct altera_procinfo *next;
};
/* This function checks if enough parameters are available on the stack. */
static int altera_check_stack(int stack_ptr, int count, int *status)
{
if (stack_ptr < count) {
*status = -EOVERFLOW;
return 0;
}
return 1;
}
static void altera_export_int(char *key, s32 value)
{
dprintk("Export: key = \"%s\", value = %d\n", key, value);
}
#define HEX_LINE_CHARS 72
#define HEX_LINE_BITS (HEX_LINE_CHARS * 4)
static void altera_export_bool_array(char *key, u8 *data, s32 count)
{
char string[HEX_LINE_CHARS + 1];
s32 i, offset;
u32 size, line, lines, linebits, value, j, k;
if (count > HEX_LINE_BITS) {
dprintk("Export: key = \"%s\", %d bits, value = HEX\n",
key, count);
lines = (count + (HEX_LINE_BITS - 1)) / HEX_LINE_BITS;
for (line = 0; line < lines; ++line) {
if (line < (lines - 1)) {
linebits = HEX_LINE_BITS;
size = HEX_LINE_CHARS;
offset = count - ((line + 1) * HEX_LINE_BITS);
} else {
linebits =
count - ((lines - 1) * HEX_LINE_BITS);
size = (linebits + 3) / 4;
offset = 0L;
}
string[size] = '\0';
j = size - 1;
value = 0;
for (k = 0; k < linebits; ++k) {
i = k + offset;
if (data[i >> 3] & (1 << (i & 7)))
value |= (1 << (i & 3));
if ((i & 3) == 3) {
sprintf(&string[j], "%1x", value);
value = 0;
--j;
}
}
if ((k & 3) > 0)
sprintf(&string[j], "%1x", value);
dprintk("%s\n", string);
}
} else {
size = (count + 3) / 4;
string[size] = '\0';
j = size - 1;
value = 0;
for (i = 0; i < count; ++i) {
if (data[i >> 3] & (1 << (i & 7)))
value |= (1 << (i & 3));
if ((i & 3) == 3) {
sprintf(&string[j], "%1x", value);
value = 0;
--j;
}
}
if ((i & 3) > 0)
sprintf(&string[j], "%1x", value);
dprintk("Export: key = \"%s\", %d bits, value = HEX %s\n",
key, count, string);
}
}
static int altera_execute(struct altera_state *astate,
u8 *p,
s32 program_size,
s32 *error_address,
int *exit_code,
int *format_version)
{
struct altera_config *aconf = astate->config;
char *msg_buff = astate->msg_buff;
long *stack = astate->stack;
int status = 0;
u32 first_word = 0L;
u32 action_table = 0L;
u32 proc_table = 0L;
u32 str_table = 0L;
u32 sym_table = 0L;
u32 data_sect = 0L;
u32 code_sect = 0L;
u32 debug_sect = 0L;
u32 action_count = 0L;
u32 proc_count = 0L;
u32 sym_count = 0L;
long *vars = NULL;
s32 *var_size = NULL;
char *attrs = NULL;
u8 *proc_attributes = NULL;
u32 pc;
u32 opcode_address;
u32 args[3];
u32 opcode;
u32 name_id;
u8 charbuf[4];
long long_tmp;
u32 variable_id;
u8 *charptr_tmp;
u8 *charptr_tmp2;
long *longptr_tmp;
int version = 0;
int delta = 0;
int stack_ptr = 0;
u32 arg_count;
int done = 0;
int bad_opcode = 0;
u32 count;
u32 index;
u32 index2;
s32 long_count;
s32 long_idx;
s32 long_idx2;
u32 i;
u32 j;
u32 uncomp_size;
u32 offset;
u32 value;
int current_proc = 0;
int reverse;
char *name;
dprintk("%s\n", __func__);
/* Read header information */
if (program_size > 52L) {
first_word = get_unaligned_be32(&p[0]);
version = (first_word & 1L);
*format_version = version + 1;
delta = version * 8;
action_table = get_unaligned_be32(&p[4]);
proc_table = get_unaligned_be32(&p[8]);
str_table = get_unaligned_be32(&p[4 + delta]);
sym_table = get_unaligned_be32(&p[16 + delta]);
data_sect = get_unaligned_be32(&p[20 + delta]);
code_sect = get_unaligned_be32(&p[24 + delta]);
debug_sect = get_unaligned_be32(&p[28 + delta]);
action_count = get_unaligned_be32(&p[40 + delta]);
proc_count = get_unaligned_be32(&p[44 + delta]);
sym_count = get_unaligned_be32(&p[48 + (2 * delta)]);
}
if ((first_word != 0x4A414D00L) && (first_word != 0x4A414D01L)) {
done = 1;
status = -EIO;
goto exit_done;
}
if (sym_count <= 0)
goto exit_done;
vars = kzalloc(sym_count * sizeof(long), GFP_KERNEL);
if (vars == NULL)
status = -ENOMEM;
if (status == 0) {
var_size = kzalloc(sym_count * sizeof(s32), GFP_KERNEL);
if (var_size == NULL)
status = -ENOMEM;
}
if (status == 0) {
attrs = kzalloc(sym_count, GFP_KERNEL);
if (attrs == NULL)
status = -ENOMEM;
}
if ((status == 0) && (version > 0)) {
proc_attributes = kzalloc(proc_count, GFP_KERNEL);
if (proc_attributes == NULL)
status = -ENOMEM;
}
if (status != 0)
goto exit_done;
delta = version * 2;
for (i = 0; i < sym_count; ++i) {
offset = (sym_table + ((11 + delta) * i));
value = get_unaligned_be32(&p[offset + 3 + delta]);
attrs[i] = p[offset];
/*
* use bit 7 of attribute byte to indicate that
* this buffer was dynamically allocated
* and should be freed later
*/
attrs[i] &= 0x7f;
var_size[i] = get_unaligned_be32(&p[offset + 7 + delta]);
/*
* Attribute bits:
* bit 0: 0 = read-only, 1 = read-write
* bit 1: 0 = not compressed, 1 = compressed
* bit 2: 0 = not initialized, 1 = initialized
* bit 3: 0 = scalar, 1 = array
* bit 4: 0 = Boolean, 1 = integer
* bit 5: 0 = declared variable,
* 1 = compiler created temporary variable
*/
if ((attrs[i] & 0x0c) == 0x04)
/* initialized scalar variable */
vars[i] = value;
else if ((attrs[i] & 0x1e) == 0x0e) {
/* initialized compressed Boolean array */
uncomp_size = get_unaligned_le32(&p[data_sect + value]);
/* allocate a buffer for the uncompressed data */
vars[i] = (long)kzalloc(uncomp_size, GFP_KERNEL);
if (vars[i] == 0L)
status = -ENOMEM;
else {
/* set flag so buffer will be freed later */
attrs[i] |= 0x80;
/* uncompress the data */
if (altera_shrink(&p[data_sect + value],
var_size[i],