You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
piklab/src/piklab-prog/cli_interactive.cpp

340 lines
16 KiB

/***************************************************************************
* Copyright (C) 2006 Nicolas Hadacek <hadacek@kde.org> *
* *
* 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. *
***************************************************************************/
#include "cli_interactive.h"
#if defined(HAVE_READLINE)
# include <readline/readline.h>
# include <readline/history.h>
#else
# include <stdio.h>
#endif
#include <signal.h>
#include <tqtimer.h>
#include <stdlib.h>
#include "progs/base/generic_prog.h"
#include "cmdline.h"
#include "cli_prog_manager.h"
#include "common/cli/cli_log.h"
#include "devices/base/device_group.h"
#include "devices/pic/base/pic_register.h"
#include "devices/pic/prog/pic_prog.h"
#include "coff/base/text_coff.h"
#include "devices/pic/prog/pic_debug.h"
//-----------------------------------------------------------------------------
const CLI::CommandData CLI::INTERACTIVE_COMMAND_DATA[] = {
{ "property-list", NoCommandProperty, I18N_NOOP("Display the list of available properties.") },
{ "register-list", NoCommandProperty, I18N_NOOP("Display the list of registers.") },
{ "variable-list", NoCommandProperty, I18N_NOOP("Display the list of variables.") },
{ "range-list", NoCommandProperty, I18N_NOOP("Display the list of memory ranges.") },
{ "help", NoCommandProperty, I18N_NOOP("Display help.") },
{ "quit", NoCommandProperty, I18N_NOOP("Quit.") },
{ "set", NoCommandProperty, I18N_NOOP("Set property value: \"set <property> <value>\" or \"<property> <value>\".") },
{ "unset", NoCommandProperty, I18N_NOOP("Unset property value: \"unset <property>\".") },
{ "get", NoCommandProperty, I18N_NOOP("Get property value: \"get <property>\" or \"<property>\".") },
{ "disconnect", NeedProgrammer, I18N_NOOP("Disconnect programmer.") },
{ "start", NeedDevice | NeedProgrammer, I18N_NOOP("Start or restart debugging session.") },
{ "step", NeedDevice | NeedProgrammer, I18N_NOOP("Step one instruction.") },
{ "x", NeedDevice | NeedProgrammer, I18N_NOOP("Read or set a register or variable: \"x PORTB\" or \"x W 0x1A\".") },
{ "break", NeedDevice | NeedProgrammer, I18N_NOOP("Set a breakpoint \"break e 0x04\" or list all breakpoints \"break\".") },
{ "clear", NeedDevice | NeedProgrammer, I18N_NOOP("Clear a breakpoint \"clear 0\", \"clear e 0x04\", or clear all breakpoints \"clear all\".") },
{ "raw-com", NeedDevice | NeedProgrammer, I18N_NOOP("Write and read raw commands to port from given file.") },
{ 0, NoCommandProperty, 0 }
};
const CLI::PropertyData CLI::PROPERTY_DATA[] = {
{ "programmer", "programmer <name>", "p", I18N_NOOP("Programmer to use."), "programmer-list", I18N_NOOP("Return the list of supported programmers.") },
{ "hardware", "hardware <name>", "h", I18N_NOOP("Programmer hardware configuration to use (for direct programmer)."), "hardware-list", I18N_NOOP("Return the list of supported programmer hardware configurations.") },
{ "device", "device <name>", "d", I18N_NOOP("Target device."), "device-list", I18N_NOOP("Return the list of supported devices.") },
{ "format", "format <name>", "f", I18N_NOOP("Hex output file format."), "format-list", I18N_NOOP("Return the list of supported hex file formats.") },
{ "port", "port <name>", "t", I18N_NOOP("Programmer port (\"usb\" or device such as \"/dev/ttyS0\")"), "port-list", I18N_NOOP("Return the list of detected ports.") },
{ "firmware-dir", "firmware-dir <dir>", 0, I18N_NOOP("Firmware directory."), 0, 0 },
{ "target-self-powered", "target-self-powered <true|false>", 0, I18N_NOOP("Set if target device is self-powered."), 0, 0 },
{ "hex", 0, 0, I18N_NOOP("Hex file to be used for programming."), 0, 0 },
{ "coff", 0, 0, I18N_NOOP("COFF file to be used for debugging."), 0, 0 },
{ "processor", 0, 0, I18N_NOOP("Same as \"device\"."), 0, 0 },
{ 0, 0, 0, 0, 0, 0 }
};
//-----------------------------------------------------------------------------
void CLI::Interactive::signalHandler(int n)
{
if ( n!=SIGINT ) return;
fprintf(stdout, "<CTRL C> Break\n");
fflush(stdout);
Programmer::Base *prog = Programmer::manager->programmer();
if ( prog && prog->state()==Programmer::Running ) Programmer::manager->halt();
_interactive->redisplayPrompt();
}
CLI::Interactive::Interactive(TQObject *parent)
: TQObject(parent, "interactive")
{
setView(_view);
::signal(SIGINT, signalHandler);
#if defined(HAVE_READLINE)
using_history();
#else
_stdin.open(IO_ReadOnly, stdin);
#endif
TQTimer::singleShot(0, this, TQ_SLOT(displayPrompt()));
}
void CLI::Interactive::redisplayPrompt()
{
#if defined(HAVE_READLINE)
rl_forced_update_display();
#else
fprintf(stdout, "> ");
fflush(stdout);
#endif
}
void CLI::Interactive::displayPrompt()
{
#if defined(HAVE_READLINE)
char *line = readline("> ");
_input = TQString(line);
if ( !_input.isEmpty() ) add_history(line);
free(line);
#else
fprintf(stdout, "> ");
fflush(stdout);
char buffer[1000];
_stdin.readLine(buffer, 1000);
_input = TQString(buffer);
#endif
lineRead();
}
CLI::ExitCode CLI::Interactive::processLine(const TQString &s)
{
TQStringList words = TQStringList::split(" ", s);
if ( words.count()==0 ) return ARG_ERROR;
if ( words[0]=="command-list" || words[0]=="property-list" || words[0]=="range-list"
|| isPropertyList(words[0]) ) return static_cast<Main *>(_main)->list(words[0]);
if ( words[0]=="register-list" ) return registerList();
if ( words[0]=="variable-list" ) return variableList();
if ( isProperty(words[0]) ) {
if ( words.count()==1 ) words.prepend("get");
else if ( words.count()==2 ) words.prepend("set");
else return errorExit(i18n("This command takes no or one argument"), ARG_ERROR);
}
if ( findCommand(words[0])!=OK ) return ARG_ERROR;
const CommandData *data = findCommandData(words[0]);
if ( words[0]=="quit" ) return EXITING;
if ( words[0]=="set" ) {
if ( words.count()==2 ) return static_cast<Main *>(_main)->executeSetCommand(words[1], TQString());
if ( words.count()!=3 ) return errorExit(i18n("This command takes two arguments."), ARG_ERROR);
return static_cast<Main *>(_main)->executeSetCommand(words[1], words[2]);
}
if ( words[0]=="unset" ) {
if ( words.count()!=2 ) return errorExit(i18n("This command takes one argument."), ARG_ERROR);
return static_cast<Main *>(_main)->executeSetCommand(words[1], TQString());
}
if ( words[0]=="get" ) {
if ( words.count()!=2 ) return errorExit(i18n("This command takes one argument."), ARG_ERROR);
TQString s = static_cast<Main *>(_main)->executeGetCommand(words[1]);
if ( !s.isEmpty() ) log(Log::LineType::Normal, words[1] + ": " + s);
return OK;
}
if ( words[0]=="help" ) return static_cast<Main *>(_main)->list("command-list");
if ( words[0]=="x" ) {
if ( words.count()!=2 && words.count()!=3 ) return errorExit(i18n("This command takes one or two argument."), ARG_ERROR);
return executeRegister(words[1], words.count()==3 ? words[2] : TQString());
}
if ( words[0]=="break" ) {
if ( words.count()==3 ) {
if ( words[1]=="e" ) {
bool ok;
ulong address = fromAnyLabel(words[2], &ok);
if ( !ok ) return errorExit(i18n("Number format not recognized."), ARG_ERROR);
PURL::Url dummy;
Breakpoint::Data data(dummy, address);
if ( Breakpoint::list().contains(data) ) return okExit(i18n("Breakpoint already set at %1.").arg(toHexLabel(address, nbChars(NumberBase::Hex, address))));
Breakpoint::list().append(data);
Breakpoint::list().setAddress(data, address);
Breakpoint::list().setState(data, Breakpoint::Active);
return okExit(i18n("Breakpoint set at %1.").arg(toHexLabel(address, nbChars(NumberBase::Hex, address))));
}
return errorExit(i18n("The first argument should be \"e\"."), ARG_ERROR);
}
if ( words.count()==1 ) {
uint nb = Breakpoint::list().count();
if ( nb==0 ) return okExit(i18n("No breakpoint set."));
log(Log::LineType::Normal, i18n("Breakpoints:"));
uint nbc = 0;
if (_device) nbc = _device->nbCharsAddress();
else for (uint i=0; i<nb; i++) {
Address address = Breakpoint::list().address(Breakpoint::list().data(i));
nbc = TQMAX(nbc, nbChars(NumberBase::Hex, address.toUInt()));
}
for (uint i=0; i<nb; i++) {
Address address = Breakpoint::list().address(Breakpoint::list().data(i));
log(Log::LineType::Normal, TQString(" #%1: %2").arg(i).arg(toHexLabel(address, nbc)));
}
return OK;
}
if ( words.count()!=1 && words.count()!=3 ) return errorExit(i18n("This command takes no or two argument."), ARG_ERROR);
return errorExit(i18n("Arguments not recognized."), ARG_ERROR);
}
if ( words[0]=="clear" ) {
if ( words.count()!=2 ) return errorExit(i18n("This command takes one argument."), ARG_ERROR);
if ( words[1]=="all" ) {
Breakpoint::list().clear();
return okExit(i18n("All breakpoints removed."));
}
bool ok;
uint i = words[1].toUInt(&ok);
if ( !ok ) return errorExit(i18n("Argument should be breakpoint index."), ARG_ERROR);
if ( i>=Breakpoint::list().count() ) return errorExit(i18n("Breakpoint index too large."), ARG_ERROR);
Breakpoint::Data data = Breakpoint::list().data(i);
Address address = Breakpoint::list().address(data);
Breakpoint::list().remove(data);
return okExit(i18n("Breakpoint at %1 removed.").arg(toHexLabelAbs(address)));
}
if ( words[0]=="raw-com" ) {
if ( words.count()!=2 ) return errorExit(i18n("This command needs a commands filename."), ARG_ERROR);
} else if ( words[0]=="program" || words[0]=="read" || words[0]=="verify" || words[0]=="erase" || words[0]=="blank_check" ) {
uint hexi = 1;
if ( words.count()>=2 && words[1]=="-r" ) {
hexi = 3;
if ( words.count()==2 ) return errorExit(i18n("You need to specify the range."), ARG_ERROR);
ExitCode code = static_cast<Main *>(_main)->extractRange(words[2]);
if ( code!=OK ) return code;
}
if ( data->properties & (InputHex|OutputHex) ) {
if ( uint(words.count())>(hexi+1) ) return errorExit(i18n("Too many arguments."), ARG_ERROR);
if ( uint(words.count())==(hexi+1) )_hexUrl = PURL::Url(runDirectory(), words[hexi]);
if ( _hexUrl.isEmpty() ) return errorExit(i18n("This command needs an hex filename."), ARG_ERROR);
} else if ( uint(words.count())>hexi ) return errorExit(i18n("Too many arguments."), ARG_ERROR);
} else if ( words.count()!=1 ) return errorExit(i18n("This command takes no argument."), ARG_ERROR);
ExitCode code = static_cast<Main *>(_main)->prepareCommand(words[0]);
if ( code!=OK ) return code;
if ( words[0]=="raw-com" ) return executeRawCommands(words[1]);
return static_cast<Main *>(_main)->executeCommand(words[0]);
}
void CLI::Interactive::lineRead()
{
TQString s = _input.stripWhiteSpace();
_input = TQString();
if ( processLine(s)==EXITING ) {
tqApp->exit(OK);
return;
}
TQTimer::singleShot(0, this, TQ_SLOT(displayPrompt()));
}
CLI::ExitCode CLI::Interactive::registerList()
{
if ( _device==0 ) return errorExit(i18n("No device specified."), NOT_SUPPORTED_ERROR);
if ( _device->group().name()!="pic" ) return errorExit(i18n("No register."), NOT_SUPPORTED_ERROR);
const Pic::Data &data = static_cast<const Pic::Data &>(*_device);
const Coff::Object *coff = Debugger::manager->coff();
const Pic::RegistersData &rdata = data.registersData();
log(Log::LineType::Normal, i18n("Special Function Registers:"));
TQValueVector<Pic::RegisterNameData> list = Pic::sfrList(data);
for (uint i=0; i<uint(list.count()); i++) log(Log::LineType::Normal, TQString(" %1: %2").arg(toHexLabel(list[i].data().address(), rdata.nbCharsAddress())).arg(list[i].label()));
log(Log::LineType::Normal, i18n("General Purpose Registers:"));
list = Pic::gprList(data, coff);
for (uint i=0; i<uint(list.count()); i++) log(Log::LineType::Normal, TQString(" %1: %2").arg(toHexLabel(list[i].data().address(), rdata.nbCharsAddress())).arg(list[i].label()));
return OK;
}
CLI::ExitCode CLI::Interactive::variableList()
{
if ( _device==0 ) return errorExit(i18n("No device specified."), NOT_SUPPORTED_ERROR);
const Coff::Object *coff = Debugger::manager->coff();
if ( coff==0 ) return errorExit(i18n("No COFF file specified."), NOT_SUPPORTED_ERROR);
if ( _device->group().name()!="pic" ) return errorExit(i18n("No register."), NOT_SUPPORTED_ERROR);
const Pic::Data &data = static_cast<const Pic::Data &>(*_device);
const Pic::RegistersData &rdata = data.registersData();
TQValueVector<Pic::RegisterNameData> list = Pic::variableList(data, *coff);
if ( list.count()==0 ) log(Log::LineType::Normal, i18n("No variable."));
for (uint i=0; i<uint(list.count()); i++) log(Log::LineType::Normal, TQString(" %1: %2").arg(toHexLabel(list[i].data().address(), rdata.nbCharsAddress())).arg(list[i].label()));
return OK;
}
Address CLI::Interactive::findRegisterAddress(const TQString &name)
{
const Pic::Data &data = static_cast<const Pic::Data &>(*_device);
const Coff::Object *coff = Debugger::manager->coff();
const Pic::RegistersData &rdata = data.registersData();
bool ok;
Address address = fromAnyLabel(name, &ok);
if (ok) {
if ( address>rdata.addressFromIndex(rdata.nbRegisters()-1) ) return Address();
return address;
}
TQValueVector<Pic::RegisterNameData> sfrs = Pic::sfrList(data);
for (uint i=0; i<uint(sfrs.count()); i++) if ( name.lower()==sfrs[i].label().lower() ) return sfrs[i].data().address();
if ( coff==0 ) return Address();
TQMap<TQString, Address> variables = coff->variables();
if ( variables.contains(name) ) return variables[name];
return Address();
}
CLI::ExitCode CLI::Interactive::executeRegister(const TQString &name, const TQString &value)
{
if ( Debugger::manager->debugger()==0 ) return errorExit(i18n("You need to start the debugging session first (with \"start\")."), ARG_ERROR);
const Pic::Data &data = static_cast<const Pic::Data &>(*_device);
const Pic::RegistersData &rdata = data.registersData();
uint nbChars = rdata.nbChars();
bool ok;
ulong v = fromAnyLabel(value, &ok);
if ( !ok ) return errorExit(i18n("Number format not recognized."), ARG_ERROR);
if ( v>maxValue(NumberBase::Hex, nbChars) ) return errorExit(i18n("The given value is too large (max: %1).").arg(toHexLabel(maxValue(NumberBase::Hex, nbChars), nbChars)), ARG_ERROR);
Register::TypeData rtd;
if ( name.lower()=="w" || name.lower()=="wreg" )
rtd = static_cast<Debugger::PicBase *>(Debugger::manager->debugger())->deviceSpecific()->wregTypeData();
else if ( name.lower()=="pc" )
rtd = Debugger::manager->debugger()->pcTypeData();
else {
Address address = findRegisterAddress(name);
if ( !address.isValid() ) return errorExit(i18n("Unknown register or variable name."), ARG_ERROR);
rtd = Register::TypeData(address, rdata.nbChars());
}
if ( value.isEmpty() ) {
if ( !Debugger::manager->readRegister(rtd) ) return ARG_ERROR;
return okExit(i18n("%1 = %2").arg(name.upper()).arg(toHexLabel(Register::list().value(rtd), nbChars)));
}
return (Debugger::manager->writeRegister(rtd, v) ? OK : EXEC_ERROR);
}
CLI::ExitCode CLI::Interactive::executeRawCommands(const TQString &filename)
{
TQFile file(filename);
if ( !file.open(IO_ReadOnly) ) return errorExit(i18n("Could not open filename \"%1\".").arg(filename), ARG_ERROR);
if ( Programmer::manager->programmer()==0 ) {
Programmer::manager->createProgrammer(_device);
if ( !Programmer::manager->programmer()->simpleConnectHardware() ) return EXEC_ERROR;
}
Programmer::Base *programmer = Programmer::manager->programmer();
for (;;) {
TQString s;
if ( file.readLine(s, 1000)==-1 ) break;
bool write = !s.startsWith(" ");
s = s.stripWhiteSpace();
if ( s.isEmpty() ) continue;
if (write) {
if ( !programmer->hardware()->rawWrite(s) ) return EXEC_ERROR;
} else {
TQString rs;
if ( !programmer->hardware()->rawRead(s.length(), rs) ) return EXEC_ERROR;
if ( rs!=s ) log(Log::LineType::Warning, i18n("Read string is different than expected: %1 (%2).").arg(rs).arg(s));
}
}
return okExit(i18n("End of command file reached."));
}
#include "cli_interactive.moc"