2010-01-09 03:14:30 +00:00
|
|
|
/* MSPDebug - debugging tool for the eZ430
|
|
|
|
* Copyright (C) 2009, 2010 Daniel Beer
|
|
|
|
*
|
|
|
|
* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
2010-03-20 03:04:21 +00:00
|
|
|
#include <ctype.h>
|
2010-01-09 03:14:30 +00:00
|
|
|
#include "device.h"
|
2010-03-20 02:17:33 +00:00
|
|
|
#include "dis.h"
|
|
|
|
#include "util.h"
|
2010-08-16 23:07:03 +00:00
|
|
|
#include "output.h"
|
2010-04-30 04:01:03 +00:00
|
|
|
#include "sim.h"
|
2011-03-09 23:06:05 +00:00
|
|
|
#include "simio_cpu.h"
|
2010-01-09 03:14:30 +00:00
|
|
|
|
|
|
|
#define MEM_SIZE 65536
|
2010-07-02 02:22:52 +00:00
|
|
|
#define MEM_IO_END 0x200
|
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
struct sim_device {
|
|
|
|
struct device base;
|
2010-03-20 03:04:21 +00:00
|
|
|
|
2010-07-02 02:22:52 +00:00
|
|
|
uint8_t memory[MEM_SIZE];
|
|
|
|
uint16_t regs[DEVICE_NUM_REGS];
|
2010-03-20 03:04:21 +00:00
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
int running;
|
2010-07-02 02:22:52 +00:00
|
|
|
uint16_t current_insn;
|
2010-04-30 04:01:03 +00:00
|
|
|
};
|
2010-03-20 03:04:21 +00:00
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
#define MEM_GETB(dev, offset) ((dev)->memory[offset])
|
|
|
|
#define MEM_SETB(dev, offset, value) ((dev)->memory[offset] = (value))
|
|
|
|
#define MEM_GETW(dev, offset) \
|
|
|
|
((dev)->memory[offset] | \
|
|
|
|
((dev)->memory[(offset + 1) & 0xffff] << 8))
|
|
|
|
#define MEM_SETW(dev, offset, value) \
|
|
|
|
do { \
|
2011-08-23 10:59:57 +00:00
|
|
|
(dev)->memory[offset & ~1] = (value) & 0xff; \
|
|
|
|
(dev)->memory[offset | 1] = (value) >> 8; \
|
2010-04-30 04:01:03 +00:00
|
|
|
} while (0);
|
2010-03-20 03:04:21 +00:00
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
static int fetch_operand(struct sim_device *dev,
|
|
|
|
int amode, int reg, int is_byte,
|
2010-05-13 00:57:21 +00:00
|
|
|
uint16_t *addr_ret, uint32_t *data_ret)
|
2010-03-20 02:17:33 +00:00
|
|
|
{
|
2010-05-13 00:57:21 +00:00
|
|
|
uint16_t addr = 0;
|
|
|
|
uint32_t mask = is_byte ? 0xff : 0xffff;
|
2010-03-20 02:17:33 +00:00
|
|
|
|
|
|
|
switch (amode) {
|
|
|
|
case MSP430_AMODE_REGISTER:
|
2010-03-20 03:18:06 +00:00
|
|
|
if (reg == MSP430_REG_R3) {
|
|
|
|
if (data_ret)
|
|
|
|
*data_ret = 0;
|
2010-03-22 21:40:57 +00:00
|
|
|
return 0;
|
2010-03-20 03:18:06 +00:00
|
|
|
}
|
|
|
|
if (data_ret)
|
2010-04-30 04:01:03 +00:00
|
|
|
*data_ret = dev->regs[reg] & mask;
|
2010-03-22 21:40:57 +00:00
|
|
|
return 0;
|
2010-03-20 02:17:33 +00:00
|
|
|
|
|
|
|
case MSP430_AMODE_INDEXED:
|
2010-03-20 03:18:06 +00:00
|
|
|
if (reg == MSP430_REG_R3) {
|
|
|
|
if (data_ret)
|
|
|
|
*data_ret = 1;
|
2010-03-22 21:40:57 +00:00
|
|
|
return 0;
|
2010-03-20 03:18:06 +00:00
|
|
|
}
|
2010-03-20 02:17:33 +00:00
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
addr = MEM_GETW(dev, dev->regs[MSP430_REG_PC]);
|
|
|
|
dev->regs[MSP430_REG_PC] += 2;
|
2010-03-20 02:17:33 +00:00
|
|
|
|
|
|
|
if (reg != MSP430_REG_SR)
|
2010-04-30 04:01:03 +00:00
|
|
|
addr += dev->regs[reg];
|
2010-03-20 02:17:33 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_AMODE_INDIRECT:
|
2010-03-20 03:18:06 +00:00
|
|
|
if (reg == MSP430_REG_SR) {
|
|
|
|
if (data_ret)
|
|
|
|
*data_ret = 4;
|
2010-03-22 21:40:57 +00:00
|
|
|
return 0;
|
2010-03-20 03:18:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (reg == MSP430_REG_R3) {
|
|
|
|
if (data_ret)
|
|
|
|
*data_ret = 2;
|
2010-03-22 21:40:57 +00:00
|
|
|
return 0;
|
2010-03-20 03:18:06 +00:00
|
|
|
}
|
2010-04-30 04:01:03 +00:00
|
|
|
addr = dev->regs[reg];
|
2010-03-20 02:17:33 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_AMODE_INDIRECT_INC:
|
2010-03-20 03:18:06 +00:00
|
|
|
if (reg == MSP430_REG_SR) {
|
|
|
|
if (data_ret)
|
|
|
|
*data_ret = 8;
|
2010-03-22 21:40:57 +00:00
|
|
|
return 0;
|
2010-03-20 03:18:06 +00:00
|
|
|
}
|
|
|
|
if (reg == MSP430_REG_R3) {
|
|
|
|
if (data_ret)
|
|
|
|
*data_ret = mask;
|
2010-03-22 21:40:57 +00:00
|
|
|
return 0;
|
2010-03-20 03:18:06 +00:00
|
|
|
}
|
2010-04-30 04:01:03 +00:00
|
|
|
addr = dev->regs[reg];
|
2011-09-13 01:36:19 +00:00
|
|
|
dev->regs[reg] += (is_byte && reg != MSP430_REG_PC) ? 1 : 2;
|
2010-03-20 02:17:33 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (addr_ret)
|
|
|
|
*addr_ret = addr;
|
|
|
|
|
2010-03-20 03:18:06 +00:00
|
|
|
if (data_ret) {
|
2010-04-30 04:01:03 +00:00
|
|
|
*data_ret = MEM_GETW(dev, addr) & mask;
|
|
|
|
|
2011-03-09 23:06:05 +00:00
|
|
|
if (addr < MEM_IO_END) {
|
2010-04-30 04:01:03 +00:00
|
|
|
int ret;
|
2010-03-22 21:40:57 +00:00
|
|
|
|
2011-03-09 23:06:05 +00:00
|
|
|
if (is_byte) {
|
|
|
|
uint8_t x = *data_ret;
|
|
|
|
|
|
|
|
ret = simio_read_b(addr, &x);
|
|
|
|
*data_ret = x;
|
|
|
|
} else {
|
|
|
|
uint16_t x = *data_ret;
|
|
|
|
|
|
|
|
ret = simio_read(addr, &x);
|
|
|
|
*data_ret = x;
|
|
|
|
}
|
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2010-03-20 03:18:06 +00:00
|
|
|
}
|
2010-03-22 21:40:57 +00:00
|
|
|
|
|
|
|
return 0;
|
2010-03-20 02:17:33 +00:00
|
|
|
}
|
|
|
|
|
2011-03-09 23:06:05 +00:00
|
|
|
static int store_operand(struct sim_device *dev,
|
|
|
|
int amode, int reg, int is_byte,
|
|
|
|
uint16_t addr, uint16_t data)
|
2010-03-20 02:17:33 +00:00
|
|
|
{
|
2010-04-30 04:01:03 +00:00
|
|
|
if (is_byte)
|
|
|
|
MEM_SETB(dev, addr, data);
|
2010-03-20 02:17:33 +00:00
|
|
|
else
|
2010-04-30 04:01:03 +00:00
|
|
|
MEM_SETW(dev, addr, data);
|
|
|
|
|
|
|
|
if (amode == MSP430_AMODE_REGISTER)
|
|
|
|
dev->regs[reg] = data;
|
2011-03-09 23:06:05 +00:00
|
|
|
else if (addr < MEM_IO_END) {
|
|
|
|
if (is_byte)
|
|
|
|
return simio_write_b(addr, data);
|
|
|
|
|
|
|
|
return simio_write(addr, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2010-03-20 02:17:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#define ARITH_BITS (MSP430_SR_V | MSP430_SR_N | MSP430_SR_Z | MSP430_SR_C)
|
|
|
|
|
2010-05-13 00:57:21 +00:00
|
|
|
static int step_double(struct sim_device *dev, uint16_t ins)
|
2010-03-20 02:17:33 +00:00
|
|
|
{
|
2010-05-13 00:57:21 +00:00
|
|
|
uint16_t opcode = ins & 0xf000;
|
2010-03-20 02:17:33 +00:00
|
|
|
int sreg = (ins >> 8) & 0xf;
|
|
|
|
int amode_dst = (ins >> 7) & 1;
|
|
|
|
int is_byte = ins & 0x0040;
|
|
|
|
int amode_src = (ins >> 4) & 0x3;
|
|
|
|
int dreg = ins & 0x000f;
|
2010-05-13 00:57:21 +00:00
|
|
|
uint32_t src_data;
|
|
|
|
uint16_t dst_addr = 0;
|
|
|
|
uint32_t dst_data;
|
|
|
|
uint32_t res_data;
|
|
|
|
uint32_t msb = is_byte ? 0x80 : 0x8000;
|
|
|
|
uint32_t mask = is_byte ? 0xff : 0xffff;
|
2011-03-09 23:43:08 +00:00
|
|
|
int cycles;
|
|
|
|
|
|
|
|
if (amode_dst == MSP430_AMODE_REGISTER && dreg == MSP430_REG_PC) {
|
|
|
|
if (amode_src == MSP430_AMODE_REGISTER ||
|
|
|
|
amode_src == MSP430_AMODE_INDIRECT)
|
|
|
|
cycles = 2;
|
|
|
|
else
|
|
|
|
cycles = 3;
|
|
|
|
} else {
|
|
|
|
if (amode_src == MSP430_AMODE_INDIRECT ||
|
|
|
|
amode_src == MSP430_AMODE_INDIRECT_INC)
|
|
|
|
cycles = 2;
|
|
|
|
else if (amode_src == MSP430_AMODE_INDEXED)
|
|
|
|
cycles = 3;
|
|
|
|
else
|
|
|
|
cycles = 1;
|
|
|
|
|
|
|
|
if (amode_dst == MSP430_AMODE_INDEXED)
|
|
|
|
cycles += 3;
|
|
|
|
}
|
2010-03-20 02:17:33 +00:00
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
if (fetch_operand(dev, amode_src, sreg, is_byte, NULL, &src_data) < 0)
|
2010-03-22 21:40:57 +00:00
|
|
|
return -1;
|
2010-04-30 04:01:03 +00:00
|
|
|
if (fetch_operand(dev, amode_dst, dreg, is_byte, &dst_addr,
|
2010-03-22 21:40:57 +00:00
|
|
|
opcode == MSP430_OP_MOV ? NULL : &dst_data) < 0)
|
|
|
|
return -1;
|
2010-03-20 02:17:33 +00:00
|
|
|
|
|
|
|
switch (opcode) {
|
|
|
|
case MSP430_OP_MOV:
|
|
|
|
res_data = src_data;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_SUB:
|
|
|
|
case MSP430_OP_SUBC:
|
|
|
|
case MSP430_OP_CMP:
|
2010-12-25 09:36:30 +00:00
|
|
|
src_data = (~src_data) & mask;
|
2010-03-20 02:17:33 +00:00
|
|
|
case MSP430_OP_ADD:
|
|
|
|
case MSP430_OP_ADDC:
|
|
|
|
if (opcode == MSP430_OP_ADDC || opcode == MSP430_OP_SUBC)
|
2010-04-30 04:01:03 +00:00
|
|
|
res_data = (dev->regs[MSP430_REG_SR] &
|
2010-03-20 02:17:33 +00:00
|
|
|
MSP430_SR_C) ? 1 : 0;
|
2010-03-20 05:21:29 +00:00
|
|
|
else if (opcode == MSP430_OP_SUB || opcode == MSP430_OP_CMP)
|
2010-03-20 02:17:33 +00:00
|
|
|
res_data = 1;
|
|
|
|
else
|
|
|
|
res_data = 0;
|
|
|
|
|
|
|
|
res_data += src_data;
|
|
|
|
res_data += dst_data;
|
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] &= ~ARITH_BITS;
|
2010-03-20 05:21:29 +00:00
|
|
|
if (!(res_data & mask))
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] |= MSP430_SR_Z;
|
2010-03-20 02:17:33 +00:00
|
|
|
if (res_data & msb)
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] |= MSP430_SR_N;
|
2010-03-20 02:17:33 +00:00
|
|
|
if (res_data & (msb << 1))
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] |= MSP430_SR_C;
|
2010-03-20 02:17:33 +00:00
|
|
|
if (!((src_data ^ dst_data) & msb) &&
|
|
|
|
(src_data ^ dst_data) & msb)
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] |= MSP430_SR_V;
|
2010-03-20 02:17:33 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_DADD:
|
|
|
|
res_data = src_data + dst_data;
|
2010-04-30 04:01:03 +00:00
|
|
|
if (dev->regs[MSP430_REG_SR] & MSP430_SR_C)
|
2010-03-20 02:17:33 +00:00
|
|
|
res_data++;
|
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] &= ~ARITH_BITS;
|
2010-03-20 05:21:29 +00:00
|
|
|
if (!(res_data & mask))
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] |= MSP430_SR_Z;
|
2010-03-20 02:17:33 +00:00
|
|
|
if (res_data == 1)
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] |= MSP430_SR_N;
|
2010-03-20 02:17:33 +00:00
|
|
|
if ((is_byte && res_data > 99) ||
|
|
|
|
(!is_byte && res_data > 9999))
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] |= MSP430_SR_C;
|
2010-03-20 02:17:33 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_BIT:
|
|
|
|
case MSP430_OP_AND:
|
|
|
|
res_data = src_data & dst_data;
|
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] &= ~ARITH_BITS;
|
|
|
|
dev->regs[MSP430_REG_SR] |=
|
2010-03-20 05:21:29 +00:00
|
|
|
(res_data & mask) ? MSP430_SR_C : MSP430_SR_Z;
|
2010-03-20 02:17:33 +00:00
|
|
|
if (res_data & msb)
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] |= MSP430_SR_N;
|
2010-03-20 02:17:33 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_BIC:
|
|
|
|
res_data = dst_data & ~src_data;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_BIS:
|
|
|
|
res_data = dst_data | src_data;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_XOR:
|
|
|
|
res_data = dst_data ^ src_data;
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] &= ~ARITH_BITS;
|
|
|
|
dev->regs[MSP430_REG_SR] |=
|
2010-03-20 05:21:29 +00:00
|
|
|
(res_data & mask) ? MSP430_SR_C : MSP430_SR_Z;
|
2010-03-20 02:17:33 +00:00
|
|
|
if (res_data & msb)
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] |= MSP430_SR_N;
|
2010-03-20 02:17:33 +00:00
|
|
|
if (src_data & dst_data & msb)
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] |= MSP430_SR_V;
|
2010-03-20 02:17:33 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2010-08-16 23:07:03 +00:00
|
|
|
printc_err("sim: invalid double-operand opcode: "
|
2010-03-20 02:17:33 +00:00
|
|
|
"0x%04x (PC = 0x%04x)\n",
|
2010-04-30 04:01:03 +00:00
|
|
|
opcode, dev->current_insn);
|
2010-03-20 02:17:33 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-03-09 23:06:05 +00:00
|
|
|
if (opcode != MSP430_OP_CMP && opcode != MSP430_OP_BIT &&
|
2010-04-30 04:01:03 +00:00
|
|
|
store_operand(dev, amode_dst, dreg, is_byte,
|
2011-03-09 23:06:05 +00:00
|
|
|
dst_addr, res_data) < 0)
|
|
|
|
return -1;
|
2010-03-20 02:17:33 +00:00
|
|
|
|
2011-03-09 23:43:08 +00:00
|
|
|
return cycles;
|
2010-03-20 02:17:33 +00:00
|
|
|
}
|
|
|
|
|
2010-05-13 00:57:21 +00:00
|
|
|
static int step_single(struct sim_device *dev, uint16_t ins)
|
2010-03-20 02:17:33 +00:00
|
|
|
{
|
2010-05-13 00:57:21 +00:00
|
|
|
uint16_t opcode = ins & 0xff80;
|
2010-03-20 02:17:33 +00:00
|
|
|
int is_byte = ins & 0x0040;
|
|
|
|
int amode = (ins >> 4) & 0x3;
|
|
|
|
int reg = ins & 0x000f;
|
2010-05-13 00:57:21 +00:00
|
|
|
uint16_t msb = is_byte ? 0x80 : 0x8000;
|
|
|
|
uint32_t mask = is_byte ? 0xff : 0xffff;
|
|
|
|
uint16_t src_addr = 0;
|
|
|
|
uint32_t src_data;
|
2010-05-22 04:09:28 +00:00
|
|
|
uint32_t res_data = 0;
|
2011-03-09 23:43:08 +00:00
|
|
|
int cycles = 1;
|
2010-03-20 02:17:33 +00:00
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
if (fetch_operand(dev, amode, reg, is_byte, &src_addr, &src_data) < 0)
|
2010-03-22 21:40:57 +00:00
|
|
|
return -1;
|
2010-03-20 02:17:33 +00:00
|
|
|
|
2011-03-09 23:43:08 +00:00
|
|
|
if (amode == MSP430_AMODE_INDEXED)
|
|
|
|
cycles = 4;
|
|
|
|
else if (amode == MSP430_AMODE_REGISTER)
|
|
|
|
cycles = 1;
|
|
|
|
else
|
|
|
|
cycles = 5;
|
|
|
|
|
2010-03-20 02:17:33 +00:00
|
|
|
switch (opcode) {
|
|
|
|
case MSP430_OP_RRC:
|
|
|
|
case MSP430_OP_RRA:
|
|
|
|
res_data = (src_data >> 1) & ~msb;
|
|
|
|
if (opcode == MSP430_OP_RRC) {
|
2010-04-30 04:01:03 +00:00
|
|
|
if (dev->regs[MSP430_REG_SR] & MSP430_SR_C)
|
2010-03-20 02:17:33 +00:00
|
|
|
res_data |= msb;
|
|
|
|
} else {
|
|
|
|
res_data |= src_data & msb;
|
|
|
|
}
|
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] &= ~ARITH_BITS;
|
2010-03-20 05:21:29 +00:00
|
|
|
if (!(res_data & mask))
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] |= MSP430_SR_Z;
|
2010-03-20 02:17:33 +00:00
|
|
|
if (res_data & msb)
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] |= MSP430_SR_N;
|
2010-03-20 02:17:33 +00:00
|
|
|
if (src_data & 1)
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] |= MSP430_SR_C;
|
2010-03-20 02:17:33 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_SWPB:
|
|
|
|
res_data = ((src_data & 0xff) << 8) | ((src_data >> 8) & 0xff);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_SXT:
|
|
|
|
res_data = src_data & 0xff;
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] &= ~ARITH_BITS;
|
2010-03-20 02:17:33 +00:00
|
|
|
|
|
|
|
if (src_data & 0x80) {
|
|
|
|
res_data |= 0xff00;
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] |= MSP430_SR_N;
|
2010-03-20 02:17:33 +00:00
|
|
|
}
|
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] |=
|
2010-03-20 05:21:29 +00:00
|
|
|
(res_data & mask) ? MSP430_SR_C : MSP430_SR_Z;
|
2010-03-20 02:17:33 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_PUSH:
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SP] -= 2;
|
|
|
|
MEM_SETW(dev, dev->regs[MSP430_REG_SP], src_data);
|
2011-03-09 23:43:08 +00:00
|
|
|
|
|
|
|
if (amode == MSP430_AMODE_REGISTER)
|
|
|
|
cycles = 3;
|
|
|
|
else if (amode == MSP430_AMODE_INDIRECT ||
|
|
|
|
(amode == MSP430_AMODE_INDIRECT_INC &&
|
|
|
|
reg == MSP430_REG_PC))
|
|
|
|
cycles = 4;
|
|
|
|
else
|
|
|
|
cycles = 5;
|
2010-03-20 02:17:33 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_CALL:
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SP] -= 2;
|
|
|
|
MEM_SETW(dev, dev->regs[MSP430_REG_SP],
|
|
|
|
dev->regs[MSP430_REG_PC]);
|
|
|
|
dev->regs[MSP430_REG_PC] = src_data;
|
2011-03-09 23:43:08 +00:00
|
|
|
|
|
|
|
if (amode == MSP430_AMODE_REGISTER ||
|
|
|
|
amode == MSP430_AMODE_INDIRECT)
|
|
|
|
cycles = 4;
|
|
|
|
else
|
|
|
|
cycles = 5;
|
2010-03-20 02:17:33 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_RETI:
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_SR] =
|
|
|
|
MEM_GETW(dev, dev->regs[MSP430_REG_SP]);
|
|
|
|
dev->regs[MSP430_REG_SP] += 2;
|
|
|
|
dev->regs[MSP430_REG_PC] =
|
|
|
|
MEM_GETW(dev, dev->regs[MSP430_REG_SP]);
|
|
|
|
dev->regs[MSP430_REG_SP] += 2;
|
2011-03-10 01:40:31 +00:00
|
|
|
cycles = 5;
|
2010-03-20 02:17:33 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2010-08-16 23:07:03 +00:00
|
|
|
printc_err("sim: unknown single-operand opcode: 0x%04x "
|
2010-04-30 04:01:03 +00:00
|
|
|
"(PC = 0x%04x)\n", opcode, dev->current_insn);
|
2010-03-20 02:17:33 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opcode != MSP430_OP_PUSH && opcode != MSP430_OP_CALL &&
|
2011-03-09 23:06:05 +00:00
|
|
|
opcode != MSP430_OP_RETI &&
|
|
|
|
store_operand(dev, amode, reg, is_byte, src_addr, res_data) < 0)
|
|
|
|
return -1;
|
2010-03-20 02:17:33 +00:00
|
|
|
|
2011-03-09 23:43:08 +00:00
|
|
|
return cycles;
|
2010-03-20 02:17:33 +00:00
|
|
|
}
|
|
|
|
|
2010-05-13 00:57:21 +00:00
|
|
|
static int step_jump(struct sim_device *dev, uint16_t ins)
|
2010-03-20 02:17:33 +00:00
|
|
|
{
|
2010-05-13 00:57:21 +00:00
|
|
|
uint16_t opcode = ins & 0xfc00;
|
|
|
|
uint16_t pc_offset = (ins & 0x03ff) << 1;
|
|
|
|
uint16_t sr = dev->regs[MSP430_REG_SR];
|
2010-03-20 02:17:33 +00:00
|
|
|
|
|
|
|
if (pc_offset & 0x0400)
|
|
|
|
pc_offset |= 0xff800;
|
|
|
|
|
|
|
|
switch (opcode) {
|
|
|
|
case MSP430_OP_JNZ:
|
|
|
|
sr = !(sr & MSP430_SR_Z);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_JZ:
|
|
|
|
sr &= MSP430_SR_Z;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_JNC:
|
|
|
|
sr = !(sr & MSP430_SR_C);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_JC:
|
|
|
|
sr &= MSP430_SR_C;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_JN:
|
|
|
|
sr &= MSP430_SR_N;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_JGE:
|
2010-05-21 01:17:44 +00:00
|
|
|
sr = ((sr & MSP430_SR_N) ? 1 : 0) ==
|
2010-03-20 02:17:33 +00:00
|
|
|
((sr & MSP430_SR_V) ? 1 : 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_JL:
|
2010-05-21 01:17:44 +00:00
|
|
|
sr = ((sr & MSP430_SR_N) ? 1 : 0) !=
|
2010-03-20 02:17:33 +00:00
|
|
|
((sr & MSP430_SR_V) ? 1 : 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSP430_OP_JMP:
|
|
|
|
sr = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sr)
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_PC] += pc_offset;
|
2010-03-20 02:17:33 +00:00
|
|
|
|
2011-03-09 23:43:08 +00:00
|
|
|
return 2;
|
2010-03-20 02:17:33 +00:00
|
|
|
}
|
|
|
|
|
2011-03-09 23:43:08 +00:00
|
|
|
/* Fetch and execute one instruction. Return the number of CPU cycles
|
|
|
|
* it would have taken, or -1 if an error occurs.
|
|
|
|
*/
|
2010-04-30 04:01:03 +00:00
|
|
|
static int step_cpu(struct sim_device *dev)
|
2010-03-20 02:17:33 +00:00
|
|
|
{
|
2010-05-13 00:57:21 +00:00
|
|
|
uint16_t ins;
|
2010-03-22 21:40:57 +00:00
|
|
|
int ret;
|
2010-03-20 02:17:33 +00:00
|
|
|
|
|
|
|
/* Fetch the instruction */
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->current_insn = dev->regs[MSP430_REG_PC];
|
|
|
|
ins = MEM_GETW(dev, dev->current_insn);
|
|
|
|
dev->regs[MSP430_REG_PC] += 2;
|
2010-03-20 02:17:33 +00:00
|
|
|
|
|
|
|
/* Handle different instruction types */
|
|
|
|
if ((ins & 0xf000) >= 0x4000)
|
2010-04-30 04:01:03 +00:00
|
|
|
ret = step_double(dev, ins);
|
2010-03-20 02:17:33 +00:00
|
|
|
else if ((ins & 0xf000) >= 0x2000)
|
2010-04-30 04:01:03 +00:00
|
|
|
ret = step_jump(dev, ins);
|
2010-03-20 02:17:33 +00:00
|
|
|
else
|
2010-04-30 04:01:03 +00:00
|
|
|
ret = step_single(dev, ins);
|
2010-03-22 21:40:57 +00:00
|
|
|
|
|
|
|
/* If things went wrong, restart at the current instruction */
|
|
|
|
if (ret < 0)
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->regs[MSP430_REG_PC] = dev->current_insn;
|
2010-03-22 21:40:57 +00:00
|
|
|
|
|
|
|
return ret;
|
2010-03-20 02:17:33 +00:00
|
|
|
}
|
|
|
|
|
2011-03-11 00:18:03 +00:00
|
|
|
static void do_reset(struct sim_device *dev)
|
|
|
|
{
|
|
|
|
simio_step(dev->regs[MSP430_REG_SR], 4);
|
|
|
|
memset(dev->regs, 0, sizeof(dev->regs));
|
|
|
|
dev->regs[MSP430_REG_PC] = MEM_GETW(dev, 0xfffe);
|
|
|
|
dev->regs[MSP430_REG_SR] = 0;
|
|
|
|
simio_reset();
|
|
|
|
}
|
|
|
|
|
2011-03-09 23:43:08 +00:00
|
|
|
static int step_system(struct sim_device *dev)
|
|
|
|
{
|
|
|
|
int count = 1;
|
2011-03-10 01:40:31 +00:00
|
|
|
int irq;
|
|
|
|
uint16_t status = dev->regs[MSP430_REG_SR];
|
|
|
|
|
|
|
|
irq = simio_check_interrupt();
|
2011-03-11 00:18:03 +00:00
|
|
|
if (irq == 15) {
|
|
|
|
do_reset(dev);
|
|
|
|
return 0;
|
|
|
|
} else if (((status & MSP430_SR_GIE) && irq >= 0) || irq >= 14) {
|
2011-03-10 01:40:31 +00:00
|
|
|
if (irq >= 16) {
|
|
|
|
printc_err("sim: invalid interrupt number: %d\n", irq);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev->regs[MSP430_REG_SP] -= 2;
|
|
|
|
MEM_SETW(dev, dev->regs[MSP430_REG_SP],
|
|
|
|
dev->regs[MSP430_REG_PC]);
|
|
|
|
|
|
|
|
dev->regs[MSP430_REG_SP] -= 2;
|
|
|
|
MEM_SETW(dev, dev->regs[MSP430_REG_SP],
|
|
|
|
dev->regs[MSP430_REG_SR]);
|
2011-03-09 23:43:08 +00:00
|
|
|
|
2011-03-10 01:40:31 +00:00
|
|
|
dev->regs[MSP430_REG_SR] &=
|
|
|
|
~(MSP430_SR_GIE | MSP430_SR_CPUOFF);
|
|
|
|
dev->regs[MSP430_REG_PC] = MEM_GETW(dev, 0xffe0 + irq * 2);
|
|
|
|
|
|
|
|
simio_ack_interrupt(irq);
|
|
|
|
count = 6;
|
|
|
|
} else if (!(status & MSP430_SR_CPUOFF)) {
|
2011-03-09 23:43:08 +00:00
|
|
|
count = step_cpu(dev);
|
|
|
|
if (count < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-03-10 01:40:31 +00:00
|
|
|
simio_step(status, count);
|
2011-03-09 23:43:08 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-03-20 02:17:33 +00:00
|
|
|
/************************************************************************
|
|
|
|
* Device interface
|
|
|
|
*/
|
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
static void sim_destroy(device_t dev_base)
|
|
|
|
{
|
|
|
|
free(dev_base);
|
|
|
|
}
|
|
|
|
|
2010-08-05 02:43:07 +00:00
|
|
|
static int sim_readmem(device_t dev_base, address_t addr,
|
|
|
|
uint8_t *mem, address_t len)
|
2010-04-30 04:01:03 +00:00
|
|
|
{
|
|
|
|
struct sim_device *dev = (struct sim_device *)dev_base;
|
|
|
|
|
2010-08-05 02:43:07 +00:00
|
|
|
if (addr > MEM_SIZE || (addr + len) < addr ||
|
|
|
|
(addr + len) > MEM_SIZE) {
|
2010-08-16 23:07:03 +00:00
|
|
|
printc_err("sim: memory read out of range\n");
|
2010-08-05 02:43:07 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
if (addr + len > MEM_SIZE)
|
|
|
|
len = MEM_SIZE - addr;
|
|
|
|
|
2011-03-10 03:48:16 +00:00
|
|
|
/* Read byte IO addresses */
|
|
|
|
while (len && (addr < 0x100)) {
|
|
|
|
simio_read_b(addr, mem);
|
|
|
|
mem++;
|
|
|
|
len--;
|
|
|
|
addr++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read word IO addresses */
|
|
|
|
while (len > 2 && (addr < 0x200)) {
|
|
|
|
uint16_t data = 0;
|
|
|
|
|
|
|
|
simio_read(addr, &data);
|
|
|
|
mem[0] = data & 0xff;
|
|
|
|
mem[1] = data >> 8;
|
|
|
|
mem += 2;
|
|
|
|
len -= 2;
|
|
|
|
addr += 2;
|
|
|
|
}
|
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
memcpy(mem, dev->memory + addr, len);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-08-05 02:43:07 +00:00
|
|
|
static int sim_writemem(device_t dev_base, address_t addr,
|
|
|
|
const uint8_t *mem, address_t len)
|
2010-04-30 04:01:03 +00:00
|
|
|
{
|
|
|
|
struct sim_device *dev = (struct sim_device *)dev_base;
|
|
|
|
|
2010-08-05 02:43:07 +00:00
|
|
|
if (addr > MEM_SIZE || (addr + len) < addr ||
|
|
|
|
(addr + len) > MEM_SIZE) {
|
2010-08-16 23:07:03 +00:00
|
|
|
printc_err("sim: memory write out of range\n");
|
2010-08-05 02:43:07 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2010-03-20 02:17:33 +00:00
|
|
|
|
2011-03-10 03:48:16 +00:00
|
|
|
/* Write byte IO addresses */
|
|
|
|
while (len && (addr < 0x100)) {
|
|
|
|
simio_write_b(addr, *mem);
|
|
|
|
mem++;
|
|
|
|
len--;
|
|
|
|
addr++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write word IO addresses */
|
|
|
|
while (len > 2 && (addr < 0x200)) {
|
|
|
|
simio_write(addr, ((uint16_t)mem[1] << 8) | mem[0]);
|
|
|
|
mem += 2;
|
|
|
|
len -= 2;
|
|
|
|
addr += 2;
|
|
|
|
}
|
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
memcpy(dev->memory + addr, mem, len);
|
|
|
|
return 0;
|
|
|
|
}
|
2010-01-09 03:14:30 +00:00
|
|
|
|
2010-08-05 02:43:07 +00:00
|
|
|
static int sim_getregs(device_t dev_base, address_t *regs)
|
2010-01-09 03:14:30 +00:00
|
|
|
{
|
2010-04-30 04:01:03 +00:00
|
|
|
struct sim_device *dev = (struct sim_device *)dev_base;
|
2010-08-05 02:43:07 +00:00
|
|
|
int i;
|
2010-04-30 04:01:03 +00:00
|
|
|
|
2010-08-05 02:43:07 +00:00
|
|
|
for (i = 0; i < DEVICE_NUM_REGS; i++)
|
|
|
|
regs[i] = dev->regs[i];
|
2010-04-30 04:01:03 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-08-05 02:43:07 +00:00
|
|
|
static int sim_setregs(device_t dev_base, const address_t *regs)
|
2010-04-30 04:01:03 +00:00
|
|
|
{
|
|
|
|
struct sim_device *dev = (struct sim_device *)dev_base;
|
2010-08-05 02:43:07 +00:00
|
|
|
int i;
|
2010-04-30 04:01:03 +00:00
|
|
|
|
2010-08-05 02:43:07 +00:00
|
|
|
for (i = 0; i < DEVICE_NUM_REGS; i++)
|
|
|
|
dev->regs[i] = regs[i];
|
2010-04-30 04:01:03 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sim_ctl(device_t dev_base, device_ctl_t op)
|
2010-01-09 03:14:30 +00:00
|
|
|
{
|
2010-04-30 04:01:03 +00:00
|
|
|
struct sim_device *dev = (struct sim_device *)dev_base;
|
|
|
|
|
|
|
|
switch (op) {
|
2010-03-20 02:17:33 +00:00
|
|
|
case DEVICE_CTL_RESET:
|
2011-03-11 00:18:03 +00:00
|
|
|
do_reset(dev);
|
2010-03-22 06:12:51 +00:00
|
|
|
return 0;
|
2010-03-20 02:17:33 +00:00
|
|
|
|
2010-01-09 03:14:30 +00:00
|
|
|
case DEVICE_CTL_HALT:
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->running = 0;
|
2010-01-09 03:14:30 +00:00
|
|
|
return 0;
|
|
|
|
|
2010-03-20 02:17:33 +00:00
|
|
|
case DEVICE_CTL_STEP:
|
2011-03-09 23:43:08 +00:00
|
|
|
return step_system(dev);
|
2010-03-20 02:17:33 +00:00
|
|
|
|
|
|
|
case DEVICE_CTL_RUN:
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->running = 1;
|
2010-03-20 02:17:33 +00:00
|
|
|
return 0;
|
2010-01-09 03:14:30 +00:00
|
|
|
}
|
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
return 0;
|
2010-01-09 03:14:30 +00:00
|
|
|
}
|
|
|
|
|
2010-09-23 03:45:06 +00:00
|
|
|
static int sim_erase(device_t dev_base, device_erase_type_t type,
|
|
|
|
address_t addr)
|
|
|
|
{
|
|
|
|
struct sim_device *dev = (struct sim_device *)dev_base;
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case DEVICE_ERASE_MAIN:
|
|
|
|
memset(dev->memory + 0x2000, 0xff, MEM_SIZE - 0x2000);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DEVICE_ERASE_ALL:
|
2010-09-23 04:08:45 +00:00
|
|
|
memset(dev->memory, 0xff, MEM_SIZE);
|
2010-09-23 03:45:06 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case DEVICE_ERASE_SEGMENT:
|
|
|
|
addr &= ~0x3f;
|
|
|
|
addr &= (MEM_SIZE - 1);
|
|
|
|
memset(dev->memory + addr, 0xff, 64);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
static device_status_t sim_poll(device_t dev_base)
|
2010-01-09 03:14:30 +00:00
|
|
|
{
|
2010-04-30 04:01:03 +00:00
|
|
|
struct sim_device *dev = (struct sim_device *)dev_base;
|
|
|
|
int count = 1000000;
|
2010-03-22 06:12:51 +00:00
|
|
|
|
2010-07-02 02:22:52 +00:00
|
|
|
if (!dev->running)
|
|
|
|
return DEVICE_STATUS_HALTED;
|
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
ctrlc_reset();
|
2010-07-02 02:22:52 +00:00
|
|
|
while (count > 0) {
|
|
|
|
int i;
|
|
|
|
|
2010-07-27 01:08:21 +00:00
|
|
|
for (i = 0; i < dev->base.max_breakpoints; i++) {
|
|
|
|
struct device_breakpoint *bp =
|
|
|
|
&dev->base.breakpoints[i];
|
2010-07-02 02:22:52 +00:00
|
|
|
|
2010-07-27 01:08:21 +00:00
|
|
|
if ((bp->flags & DEVICE_BP_ENABLED) &&
|
2010-07-02 02:22:52 +00:00
|
|
|
dev->regs[MSP430_REG_PC] == bp->addr) {
|
|
|
|
dev->running = 0;
|
|
|
|
return DEVICE_STATUS_HALTED;
|
|
|
|
}
|
2010-03-23 01:37:53 +00:00
|
|
|
}
|
2010-03-20 02:17:33 +00:00
|
|
|
|
2011-03-09 23:43:08 +00:00
|
|
|
if (step_system(dev) < 0) {
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->running = 0;
|
|
|
|
return DEVICE_STATUS_ERROR;
|
2010-03-23 01:37:53 +00:00
|
|
|
}
|
2010-03-20 05:21:29 +00:00
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
if (ctrlc_check())
|
|
|
|
return DEVICE_STATUS_INTR;
|
2010-03-20 02:17:33 +00:00
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
count--;
|
2010-03-20 02:17:33 +00:00
|
|
|
}
|
|
|
|
|
2010-07-02 02:22:52 +00:00
|
|
|
return DEVICE_STATUS_RUNNING;
|
2010-01-09 03:14:30 +00:00
|
|
|
}
|
|
|
|
|
2011-03-15 01:56:07 +00:00
|
|
|
static device_t sim_open(const struct device_args *args)
|
2010-01-09 03:14:30 +00:00
|
|
|
{
|
2010-04-30 04:01:03 +00:00
|
|
|
struct sim_device *dev = malloc(sizeof(*dev));
|
2010-01-09 03:14:30 +00:00
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
if (!dev) {
|
2010-08-16 23:07:03 +00:00
|
|
|
pr_error("can't allocate memory for simulation");
|
2010-04-30 04:01:03 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
2010-01-09 03:14:30 +00:00
|
|
|
|
2010-07-27 01:08:21 +00:00
|
|
|
memset(dev, 0, sizeof(*dev));
|
|
|
|
|
2011-03-15 01:56:07 +00:00
|
|
|
dev->base.type = &device_sim;
|
2010-07-27 01:08:21 +00:00
|
|
|
dev->base.max_breakpoints = DEVICE_MAX_BREAKPOINTS;
|
2010-01-09 03:14:30 +00:00
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
memset(dev->memory, 0xff, sizeof(dev->memory));
|
|
|
|
memset(dev->regs, 0xff, sizeof(dev->regs));
|
2010-01-09 03:14:30 +00:00
|
|
|
|
2010-04-30 04:01:03 +00:00
|
|
|
dev->running = 0;
|
|
|
|
dev->current_insn = 0;
|
2010-07-02 02:22:52 +00:00
|
|
|
|
2010-08-31 03:50:00 +00:00
|
|
|
printc_dbg("Simulation started, 0x%x bytes of RAM\n", MEM_SIZE);
|
2010-04-30 04:01:03 +00:00
|
|
|
return (device_t)dev;
|
2010-01-09 03:14:30 +00:00
|
|
|
}
|
2011-03-15 01:56:07 +00:00
|
|
|
|
|
|
|
const struct device_class device_sim = {
|
|
|
|
.name = "sim",
|
|
|
|
.help = "Simulation mode.",
|
|
|
|
.open = sim_open,
|
|
|
|
.destroy = sim_destroy,
|
|
|
|
.readmem = sim_readmem,
|
|
|
|
.writemem = sim_writemem,
|
|
|
|
.erase = sim_erase,
|
|
|
|
.getregs = sim_getregs,
|
|
|
|
.setregs = sim_setregs,
|
|
|
|
.ctl = sim_ctl,
|
|
|
|
.poll = sim_poll
|
|
|
|
};
|