480 lines
10 KiB
C
480 lines
10 KiB
C
/* MSPDebug - debugging tool for MSP430 MCUs
|
|
* 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 <string.h>
|
|
|
|
#include "output.h"
|
|
#include "output_util.h"
|
|
#include "dis.h"
|
|
#include "simio.h"
|
|
#include "simio_cpu.h"
|
|
#include "simio_device.h"
|
|
|
|
#include "simio_tracer.h"
|
|
#include "simio_timer.h"
|
|
#include "simio_wdt.h"
|
|
#include "simio_hwmult.h"
|
|
#include "simio_gpio.h"
|
|
#include "simio_console.h"
|
|
|
|
static const struct simio_class *const class_db[] = {
|
|
&simio_tracer,
|
|
&simio_timer,
|
|
&simio_wdt,
|
|
&simio_hwmult,
|
|
&simio_gpio,
|
|
&simio_console
|
|
};
|
|
|
|
/* Simulator data. We keep a list of devices on the bus, and the special
|
|
* function registers.
|
|
*
|
|
* Currently, MCLK and SMCLK are tied together, and ACLK runs at a fixed
|
|
* ratio of 1:256 with MCLK. aclk_counter counts fractional cycles.
|
|
*/
|
|
static struct list_node device_list;
|
|
static uint8_t sfr_data[16];
|
|
static int aclk_counter;
|
|
|
|
static void destroy_device(struct simio_device *dev)
|
|
{
|
|
list_remove(&dev->node);
|
|
dev->type->destroy(dev);
|
|
}
|
|
|
|
void simio_init(void)
|
|
{
|
|
list_init(&device_list);
|
|
simio_reset();
|
|
}
|
|
|
|
void simio_exit(void)
|
|
{
|
|
while (!LIST_EMPTY(&device_list))
|
|
destroy_device((struct simio_device *)device_list.next);
|
|
}
|
|
|
|
static const struct simio_class *find_class(const char *name)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_LEN(class_db); i++) {
|
|
const struct simio_class *t = class_db[i];
|
|
|
|
if (!strcasecmp(t->name, name))
|
|
return t;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct simio_device *find_device(const char *name)
|
|
{
|
|
struct list_node *n;
|
|
|
|
for (n = device_list.next; n != &device_list; n = n->next) {
|
|
struct simio_device *dev = (struct simio_device *)n;
|
|
|
|
if (!strcasecmp(dev->name, name))
|
|
return dev;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int cmd_add(char **arg_text)
|
|
{
|
|
const char *type_text = get_arg(arg_text);
|
|
const char *name_text = get_arg(arg_text);
|
|
const struct simio_class *type;
|
|
struct simio_device *dev;
|
|
|
|
if (!(name_text && type_text)) {
|
|
printc_err("simio add: device class and name must be "
|
|
"specified.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (find_device(name_text)) {
|
|
printc_err("simio add: device name is not unique: %s\n",
|
|
name_text);
|
|
return -1;
|
|
}
|
|
|
|
type = find_class(type_text);
|
|
if (!type) {
|
|
printc_err("simio add: unknown type.\n");
|
|
return -1;
|
|
}
|
|
|
|
dev = type->create(arg_text);
|
|
if (!dev) {
|
|
printc_err("simio add: failed to create device.\n");
|
|
return -1;
|
|
}
|
|
|
|
list_insert(&dev->node, &device_list);
|
|
strncpy(dev->name, name_text, sizeof(dev->name));
|
|
dev->name[sizeof(dev->name) - 1] = 0;
|
|
|
|
printc_dbg("Added new device \"%s\" of type \"%s\".\n",
|
|
dev->name, dev->type->name);
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_del(char **arg_text)
|
|
{
|
|
const char *name_text = get_arg(arg_text);
|
|
struct simio_device *dev;
|
|
|
|
if (!name_text) {
|
|
printc_err("simio del: device name must be specified.\n");
|
|
return -1;
|
|
}
|
|
|
|
dev = find_device(name_text);
|
|
if (!dev) {
|
|
printc_err("simio del: no such device: %s\n", name_text);
|
|
return -1;
|
|
}
|
|
|
|
destroy_device(dev);
|
|
printc_dbg("Destroyed device \"%s\".\n", name_text);
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_devices(char **arg_text)
|
|
{
|
|
struct list_node *n;
|
|
|
|
(void)arg_text;
|
|
|
|
for (n = device_list.next; n != &device_list; n = n->next) {
|
|
struct simio_device *dev = (struct simio_device *)n;
|
|
int irq = -1;
|
|
|
|
if (dev->type->check_interrupt)
|
|
irq = dev->type->check_interrupt(dev);
|
|
|
|
printc(" %-10s (type %s", dev->name, dev->type->name);
|
|
if (irq < 0)
|
|
printc(")\n");
|
|
else
|
|
printc(", IRQ pending: %d)\n", irq);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_classes(char **arg_text)
|
|
{
|
|
struct vector v;
|
|
int i;
|
|
|
|
(void)arg_text;
|
|
|
|
vector_init(&v, sizeof(const char *));
|
|
for (i = 0; i < ARRAY_LEN(class_db); i++) {
|
|
if (vector_push(&v, &class_db[i]->name, 1) < 0) {
|
|
printc_err("simio classes: can't allocate memory\n");
|
|
vector_destroy(&v);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
printc("Available device classes:\n");
|
|
namelist_print(&v);
|
|
vector_destroy(&v);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_help(char **arg_text)
|
|
{
|
|
const char *name = get_arg(arg_text);
|
|
const struct simio_class *type;
|
|
|
|
if (!name) {
|
|
printc_err("simio help: you must specify a device class\n");
|
|
return -1;
|
|
}
|
|
|
|
type = find_class(name);
|
|
if (!type) {
|
|
printc_err("simio help: unknown device class: %s\n", name);
|
|
return -1;
|
|
}
|
|
|
|
printc("\x1b[1mDEVICE CLASS: %s\x1b[0m\n\n%s\n", type->name, type->help);
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_config(char **arg_text)
|
|
{
|
|
const char *name = get_arg(arg_text);
|
|
const char *param = get_arg(arg_text);
|
|
struct simio_device *dev;
|
|
|
|
if (!(name && param)) {
|
|
printc_err("simio config: you must specify a device name and "
|
|
"a parameter\n");
|
|
return -1;
|
|
}
|
|
|
|
dev = find_device(name);
|
|
if (!dev) {
|
|
printc_err("simio config: no such device: %s\n", name);
|
|
return -1;
|
|
}
|
|
|
|
if (!dev->type->config) {
|
|
printc_err("simio config: no configuration parameters are "
|
|
"defined for this device\n");
|
|
return -1;
|
|
}
|
|
|
|
return dev->type->config(dev, param, arg_text);
|
|
}
|
|
|
|
static int cmd_info(char **arg_text)
|
|
{
|
|
const char *name = get_arg(arg_text);
|
|
struct simio_device *dev;
|
|
|
|
if (!name) {
|
|
printc_err("simio info: you must specify a device name\n");
|
|
return -1;
|
|
}
|
|
|
|
dev = find_device(name);
|
|
if (!dev) {
|
|
printc_err("simio info: no such device: %s\n", name);
|
|
return -1;
|
|
}
|
|
|
|
if (!dev->type->info) {
|
|
printc_err("simio config: no information available\n");
|
|
return -1;
|
|
}
|
|
|
|
return dev->type->info(dev);
|
|
}
|
|
|
|
int cmd_simio(char **arg_text)
|
|
{
|
|
const char *subcmd = get_arg(arg_text);
|
|
static const struct {
|
|
const char *name;
|
|
int (*func)(char **arg_text);
|
|
} cmd_table[] = {
|
|
{"add", cmd_add},
|
|
{"del", cmd_del},
|
|
{"devices", cmd_devices},
|
|
{"classes", cmd_classes},
|
|
{"help", cmd_help},
|
|
{"config", cmd_config},
|
|
{"info", cmd_info}
|
|
};
|
|
int i;
|
|
|
|
if (!subcmd) {
|
|
printc_err("simio: a subcommand is required\n");
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_LEN(cmd_table); i++)
|
|
if (!strcasecmp(cmd_table[i].name, subcmd))
|
|
return cmd_table[i].func(arg_text);
|
|
|
|
printc_err("simio: unknown subcommand: %s\n", subcmd);
|
|
return -1;
|
|
}
|
|
|
|
void simio_reset(void)
|
|
{
|
|
struct list_node *n;
|
|
|
|
memset(sfr_data, 0, sizeof(sfr_data));
|
|
aclk_counter = 0;
|
|
|
|
for (n = device_list.next; n != &device_list; n = n->next) {
|
|
struct simio_device *dev = (struct simio_device *)n;
|
|
const struct simio_class *type = dev->type;
|
|
|
|
if (type->reset)
|
|
type->reset(dev);
|
|
}
|
|
}
|
|
|
|
#define IO_REQUEST_FUNC(name, method, datatype) \
|
|
int name(address_t addr, datatype data) { \
|
|
struct list_node *n; \
|
|
int ret = 1; \
|
|
\
|
|
for (n = device_list.next; n != &device_list; n = n->next) { \
|
|
struct simio_device *dev = (struct simio_device *)n; \
|
|
const struct simio_class *type = dev->type; \
|
|
\
|
|
if (type->method) { \
|
|
int r = type->method(dev, addr, data); \
|
|
\
|
|
if (r < ret) \
|
|
ret = r; \
|
|
} \
|
|
} \
|
|
\
|
|
return ret; \
|
|
}
|
|
#define IO_REQUEST_FUNC_S(name, method, datatype) \
|
|
static IO_REQUEST_FUNC(name, method, datatype)
|
|
|
|
IO_REQUEST_FUNC(simio_write, write, uint16_t)
|
|
IO_REQUEST_FUNC(simio_read_device, read, uint16_t *)
|
|
IO_REQUEST_FUNC_S(simio_write_b_device, write_b, uint8_t)
|
|
IO_REQUEST_FUNC_S(simio_read_b_device, read_b, uint8_t *)
|
|
|
|
int simio_read(address_t addr, uint16_t *data)
|
|
{
|
|
addr &= ~1;
|
|
if (addr < 16) {
|
|
*data = ((uint16_t)sfr_data[addr]) |
|
|
(((uint16_t)sfr_data[addr + 1]) << 8);
|
|
return 0;
|
|
|
|
} else if (addr >= 0x100 && addr < 0x110) {
|
|
/* most MSPs map SFR at 0x100 */
|
|
*data = ((uint16_t)sfr_data[addr - 0x100]) |
|
|
(((uint16_t)sfr_data[addr - 0x100 + 1]) << 8);
|
|
return 0;
|
|
}
|
|
|
|
*data = 0;
|
|
return simio_read_device(addr, data);
|
|
}
|
|
|
|
int simio_write_b(address_t addr, uint8_t data)
|
|
{
|
|
if (addr < 16) {
|
|
sfr_data[addr] = data;
|
|
return 0;
|
|
|
|
} else if (addr >= 0x100 && addr < 0x110) {
|
|
/* most MSPs map SFR at 0x100 */
|
|
sfr_data[addr - 0x100] = data;
|
|
return 0;
|
|
}
|
|
|
|
return simio_write_b_device(addr, data);
|
|
}
|
|
|
|
int simio_read_b(address_t addr, uint8_t *data)
|
|
{
|
|
if (addr < 16) {
|
|
*data = sfr_data[addr];
|
|
return 0;
|
|
|
|
} else if (addr >= 0x100 && addr < 0x110) {
|
|
/* most MSPs map SFR at 0x100 */
|
|
*data = sfr_data[addr - 0x100];
|
|
return 0;
|
|
}
|
|
|
|
*data = 0;
|
|
return simio_read_b_device(addr, data);
|
|
}
|
|
|
|
int simio_check_interrupt(void)
|
|
{
|
|
int irq = -1;
|
|
struct list_node *n;
|
|
|
|
for (n = device_list.next; n != &device_list; n = n->next) {
|
|
struct simio_device *dev = (struct simio_device *)n;
|
|
const struct simio_class *type = dev->type;
|
|
|
|
if (type->check_interrupt) {
|
|
int i = type->check_interrupt(dev);
|
|
|
|
if (i > irq)
|
|
irq = i;
|
|
}
|
|
}
|
|
|
|
return irq;
|
|
}
|
|
|
|
void simio_ack_interrupt(int irq)
|
|
{
|
|
struct list_node *n;
|
|
|
|
for (n = device_list.next; n != &device_list; n = n->next) {
|
|
struct simio_device *dev = (struct simio_device *)n;
|
|
const struct simio_class *type = dev->type;
|
|
|
|
if (type->ack_interrupt)
|
|
type->ack_interrupt(dev, irq);
|
|
}
|
|
}
|
|
|
|
void simio_step(uint16_t status_register, int cycles)
|
|
{
|
|
int clocks[SIMIO_NUM_CLOCKS] = {0};
|
|
struct list_node *n;
|
|
|
|
aclk_counter += cycles;
|
|
|
|
clocks[SIMIO_MCLK] = cycles;
|
|
clocks[SIMIO_SMCLK] = cycles;
|
|
clocks[SIMIO_ACLK] = aclk_counter >> 8;
|
|
|
|
aclk_counter &= 0xff;
|
|
|
|
if (status_register & MSP430_SR_CPUOFF)
|
|
clocks[SIMIO_MCLK] = 0;
|
|
|
|
if (status_register & MSP430_SR_SCG1)
|
|
clocks[SIMIO_SMCLK] = 0;
|
|
|
|
if (status_register & MSP430_SR_OSCOFF)
|
|
clocks[SIMIO_ACLK] = 0;
|
|
|
|
for (n = device_list.next; n != &device_list; n = n->next) {
|
|
struct simio_device *dev = (struct simio_device *)n;
|
|
const struct simio_class *type = dev->type;
|
|
|
|
if (type->step)
|
|
type->step(dev, status_register, clocks);
|
|
}
|
|
}
|
|
|
|
uint8_t simio_sfr_get(address_t which)
|
|
{
|
|
if (which > sizeof(sfr_data))
|
|
return 0;
|
|
|
|
return sfr_data[which];
|
|
}
|
|
|
|
void simio_sfr_modify(address_t which, uint8_t mask, uint8_t bits)
|
|
{
|
|
if (which > sizeof(sfr_data))
|
|
return;
|
|
|
|
sfr_data[which] = (sfr_data[which] & ~mask) | bits;
|
|
}
|