473 lines
10 KiB
C
473 lines
10 KiB
C
/* MSPDebug - debugging tool for MSP430 MCUs
|
|
* Copyright (C) 2009-2013 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 "output.h"
|
|
#include "util.h"
|
|
#include "loadbsl.h"
|
|
#include "loadbsl_fw.h"
|
|
#include "bslhid.h"
|
|
|
|
#define BSL_MAX_CORE 62
|
|
#define BSL_MAX_BLOCK 52
|
|
|
|
#define BSL_CMD_RX_BLOCK 0x10
|
|
#define BSL_CMD_RX_BLOCK_FAST 0x1B
|
|
#define BSL_CMD_RX_PASSWORD 0x11
|
|
#define BSL_CMD_ERASE_SEGMENT 0x12
|
|
#define BSL_CMD_UNLOCK_LOCK_INFO 0x13
|
|
#define BSL_CMD_MASS_ERASE 0x15
|
|
#define BSL_CMD_CRC_CHECK 0x16
|
|
#define BSL_CMD_LOAD_PC 0x17
|
|
#define BSL_CMD_TX_BLOCK 0x18
|
|
#define BSL_CMD_TX_VERSION 0x19
|
|
#define BSL_CMD_TX_BUFSIZE 0x1A
|
|
|
|
#define BSL_PACKET_HEADER 0x80
|
|
#define BSL_PACKET_ACK 0x90
|
|
|
|
/* BSL error codes: from SLAU319C ("MSP430 Programming via the Bootstrap
|
|
* Loader").
|
|
*/
|
|
static const char *const bsl_error_table[9] = {
|
|
[0x00] = "Success",
|
|
[0x01] = "Flash write check failed",
|
|
[0x02] = "Flash fail bit set",
|
|
[0x03] = "Voltage change during program",
|
|
[0x04] = "BSL locked",
|
|
[0x05] = "BSL password error",
|
|
[0x06] = "Byte write forbidden",
|
|
[0x07] = "Unknown command",
|
|
[0x08] = "Packet length exceeds buffer size"
|
|
};
|
|
|
|
static const char *bsl_error_message(int code)
|
|
{
|
|
const char *text = NULL;
|
|
|
|
if (code >= 0 && code < ARRAY_LEN(bsl_error_table))
|
|
text = bsl_error_table[code];
|
|
|
|
if (text)
|
|
return text;
|
|
|
|
return "Unknown error code";
|
|
}
|
|
|
|
struct loadbsl_device {
|
|
struct device base;
|
|
transport_t trans;
|
|
};
|
|
|
|
static int send_command(transport_t trans, uint8_t cmd,
|
|
address_t addr, const uint8_t *data, int datalen)
|
|
{
|
|
uint8_t outbuf[BSL_MAX_CORE];
|
|
const int addrlen = (addr != ADDRESS_NONE) ? 3 : 0;
|
|
const int corelen = datalen + addrlen + 1;
|
|
|
|
if (datalen > BSL_MAX_BLOCK) {
|
|
printc_err("loadbsl: send_command: MAX_BLOCK exceeded: %d\n",
|
|
datalen);
|
|
return -1;
|
|
}
|
|
|
|
outbuf[0] = cmd;
|
|
|
|
if (addrlen > 0) {
|
|
outbuf[1] = addr & 0xff;
|
|
outbuf[2] = (addr >> 8) & 0xff;
|
|
outbuf[3] = (addr >> 16) & 0xff;
|
|
}
|
|
|
|
memcpy(outbuf + 1 + addrlen, data, datalen);
|
|
|
|
if (trans->ops->send(trans, outbuf, corelen) < 0) {
|
|
printc_err("loadbsl: send_command failed\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int recv_packet(transport_t trans, uint8_t *data, int max_len)
|
|
{
|
|
uint8_t inbuf[BSL_MAX_CORE];
|
|
int len = trans->ops->recv(trans, inbuf, sizeof(inbuf));
|
|
int type;
|
|
int code;
|
|
|
|
if (len < 0) {
|
|
printc_err("loadbsl: recv_packet: transport error\n");
|
|
return -1;
|
|
}
|
|
|
|
if (len < 1) {
|
|
printc_err("loadbsl: recv_packet: zero-length packet\n");
|
|
return -1;
|
|
}
|
|
|
|
type = inbuf[0];
|
|
if (type == 0x3a) {
|
|
const int data_len = len - 1;
|
|
|
|
if (!data)
|
|
return 0;
|
|
|
|
if (data_len > max_len) {
|
|
printc_err("loadbsl: recv_packet: packet too "
|
|
"long for buffer (%d bytes)\n", data_len);
|
|
return -1;
|
|
}
|
|
|
|
memcpy(data, inbuf + 1, data_len);
|
|
return data_len;
|
|
}
|
|
|
|
if (type != 0x3b) {
|
|
printc_err("loadbsl: recv_packet: unknown packet type: "
|
|
"0x%02x\n", type);
|
|
return -1;
|
|
}
|
|
|
|
if (len < 2) {
|
|
printc_err("loadbsl: recv_packet: missing response code\n");
|
|
return -1;
|
|
}
|
|
|
|
code = inbuf[1];
|
|
if (code) {
|
|
printc_err("loadbsl: recv_packet: BSL error code: %d (%s)\n",
|
|
code, bsl_error_message(code));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Retrieve and display BSL version info. Returns API version byte. */
|
|
static int version_check(transport_t trans)
|
|
{
|
|
uint8_t data[4];
|
|
int r;
|
|
|
|
if (send_command(trans, BSL_CMD_TX_VERSION,
|
|
ADDRESS_NONE, NULL, 0) < 0) {
|
|
printc_err("loadbsl: failed to send TX_VERSION command\n");
|
|
return -1;
|
|
}
|
|
|
|
r = recv_packet(trans, data, 4);
|
|
if (r < 0) {
|
|
printc_err("loadbsl: failed to receive version\n");
|
|
return -1;
|
|
}
|
|
|
|
if (r < 4) {
|
|
printc_err("loadbsl: short version response\n");
|
|
return -1;
|
|
}
|
|
|
|
printc_dbg("BSL version: [vendor: %02x, int: %02x, "
|
|
"API: %02x, per: %02x]\n",
|
|
data[0], data[1], data[2], data[3]);
|
|
|
|
return data[2];
|
|
}
|
|
|
|
static int do_writemem(transport_t trans,
|
|
address_t addr, const uint8_t *mem, address_t len)
|
|
{
|
|
while (len) {
|
|
int plen = len;
|
|
|
|
if (plen > BSL_MAX_BLOCK)
|
|
plen = BSL_MAX_BLOCK;
|
|
|
|
if (send_command(trans, BSL_CMD_RX_BLOCK_FAST,
|
|
addr, mem, plen) < 0) {
|
|
printc_err("loadbsl: failed to write block "
|
|
"to 0x%04x\n", addr);
|
|
return -1;
|
|
}
|
|
|
|
addr += plen;
|
|
mem += plen;
|
|
len -= plen;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rx_password(transport_t trans)
|
|
{
|
|
uint8_t password[32];
|
|
|
|
memset(password, 0xff, sizeof(password));
|
|
if (send_command(trans, BSL_CMD_RX_PASSWORD, ADDRESS_NONE,
|
|
password, sizeof(password)) < 0 ||
|
|
recv_packet(trans, NULL, 0) < 0) {
|
|
printc_err("loadbsl: rx_password failed\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int check_and_load(transport_t trans)
|
|
{
|
|
const struct loadbsl_fw *fw = &loadbsl_fw_usb5xx;
|
|
int api_version = version_check(trans);
|
|
|
|
if ((api_version >= 0) && (api_version != 0x80))
|
|
return 0;
|
|
|
|
printc_dbg("Uploading BSL firmware (%d bytes at address 0x%04x)...\n",
|
|
fw->size, fw->prog_addr);
|
|
|
|
if (do_writemem(trans, fw->prog_addr, fw->data, fw->size) < 0) {
|
|
printc_err("loadbsl: firmware upload failed\n");
|
|
return -1;
|
|
}
|
|
|
|
printc_dbg("Starting new firmware (PC: 0x%04x)...\n", fw->entry_point);
|
|
|
|
if (send_command(trans, BSL_CMD_LOAD_PC, fw->entry_point,
|
|
NULL, 0) < 0) {
|
|
printc_err("loadbsl: PC load failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if (trans->ops->suspend && trans->ops->resume &&
|
|
trans->ops->suspend(trans) < 0) {
|
|
printc_err("loadbsl: transport suspend failed\n");
|
|
return -1;
|
|
}
|
|
|
|
printc_dbg("Done, waiting for startup\n");
|
|
delay_ms(1000);
|
|
|
|
if (trans->ops->suspend && trans->ops->resume &&
|
|
trans->ops->resume(trans) < 0) {
|
|
printc_err("loadbsl: transport resume failed\n");
|
|
return -1;
|
|
}
|
|
|
|
if (rx_password(trans) < 0) {
|
|
printc_err("loadbsl: failed to unlock new firmware\n");
|
|
return -1;
|
|
}
|
|
|
|
return version_check(trans);
|
|
}
|
|
|
|
static void loadbsl_destroy(device_t base)
|
|
{
|
|
struct loadbsl_device *dev = (struct loadbsl_device *)base;
|
|
static const uint8_t puc_word[] = {0, 0};
|
|
|
|
/* Write 0x0000 to WDTCTL, triggering a PUC */
|
|
if (send_command(dev->trans, BSL_CMD_RX_BLOCK_FAST,
|
|
0x15c, puc_word, sizeof(puc_word)) < 0)
|
|
printc_err("warning: loadbsl: failed to trigger PUC\n");
|
|
|
|
dev->trans->ops->destroy(dev->trans);
|
|
free(dev);
|
|
}
|
|
|
|
static int loadbsl_readmem(device_t base, address_t addr,
|
|
uint8_t *mem, address_t len)
|
|
{
|
|
struct loadbsl_device *dev = (struct loadbsl_device *)base;
|
|
|
|
while (len) {
|
|
int plen = len;
|
|
uint8_t len_param[2];
|
|
int r;
|
|
|
|
if (plen > BSL_MAX_BLOCK)
|
|
plen = BSL_MAX_BLOCK;
|
|
|
|
len_param[0] = plen & 0xff;
|
|
len_param[1] = plen >> 8;
|
|
|
|
if (send_command(dev->trans, BSL_CMD_TX_BLOCK,
|
|
addr, len_param, 2) < 0)
|
|
goto fail;
|
|
|
|
r = recv_packet(dev->trans, mem, plen);
|
|
if (r < 0)
|
|
goto fail;
|
|
|
|
if (r < plen) {
|
|
printc_err("loadbsl: short response to "
|
|
"memory read\n");
|
|
return -1;
|
|
}
|
|
|
|
addr += plen;
|
|
mem += plen;
|
|
len -= plen;
|
|
}
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
printc_err("loadbsl: failed to read block from 0x%04x\n", addr);
|
|
return -1;
|
|
}
|
|
|
|
static int loadbsl_writemem(device_t base, address_t addr,
|
|
const uint8_t *mem, address_t len)
|
|
{
|
|
struct loadbsl_device *dev = (struct loadbsl_device *)base;
|
|
|
|
return do_writemem(dev->trans, addr, mem, len);
|
|
}
|
|
|
|
static int loadbsl_getregs(device_t base, address_t *regs)
|
|
{
|
|
(void)base;
|
|
(void)regs;
|
|
|
|
printc_err("loadbsl: register fetch is not implemented\n");
|
|
return -1;
|
|
}
|
|
|
|
static int loadbsl_setregs(device_t base, const address_t *regs)
|
|
{
|
|
(void)base;
|
|
(void)regs;
|
|
|
|
printc_err("loadbsl: register store is not implemented\n");
|
|
return -1;
|
|
}
|
|
|
|
static int loadbsl_erase(device_t base, device_erase_type_t type,
|
|
address_t addr)
|
|
{
|
|
struct loadbsl_device *dev = (struct loadbsl_device *)base;
|
|
|
|
switch (type) {
|
|
case DEVICE_ERASE_ALL:
|
|
printc_err("loadbsl: ERASE_ALL not supported\n");
|
|
return -1;
|
|
|
|
case DEVICE_ERASE_MAIN:
|
|
if (send_command(dev->trans, BSL_CMD_MASS_ERASE,
|
|
ADDRESS_NONE, NULL, 0) < 0 ||
|
|
recv_packet(dev->trans, NULL, 0) < 0) {
|
|
printc_err("loadbsl: ERASE_MAIN failed\n");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case DEVICE_ERASE_SEGMENT:
|
|
if (send_command(dev->trans, BSL_CMD_ERASE_SEGMENT,
|
|
addr, NULL, 0) < 0 ||
|
|
recv_packet(dev->trans, NULL, 0) < 0) {
|
|
printc_err("loadbsl: ERASE_SEGMENT failed\n");
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int loadbsl_ctl(device_t base, device_ctl_t type)
|
|
{
|
|
(void)base;
|
|
|
|
switch (type) {
|
|
case DEVICE_CTL_HALT:
|
|
case DEVICE_CTL_RESET:
|
|
return 0;
|
|
|
|
default:
|
|
printc_err("loadbsl: CPU control is not possible\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static device_status_t loadbsl_poll(device_t base)
|
|
{
|
|
(void)base;
|
|
|
|
return DEVICE_STATUS_HALTED;
|
|
}
|
|
|
|
static device_t loadbsl_open(const struct device_args *args)
|
|
{
|
|
struct loadbsl_device *dev;
|
|
|
|
if (args->flags & DEVICE_FLAG_TTY) {
|
|
printc_err("loadbsl: this driver does not support "
|
|
"tty access\n");
|
|
return NULL;
|
|
}
|
|
|
|
dev = malloc(sizeof(*dev));
|
|
memset(dev, 0, sizeof(*dev));
|
|
|
|
dev->base.type = &device_loadbsl;
|
|
dev->base.max_breakpoints = 0;
|
|
|
|
dev->trans = bslhid_open(args->path, args->requested_serial);
|
|
if (!dev->trans) {
|
|
free(dev);
|
|
return NULL;
|
|
}
|
|
|
|
if (rx_password(dev->trans) < 0) {
|
|
printc_dbg("loadbsl: retrying password...\n");
|
|
|
|
if (rx_password(dev->trans) < 0) {
|
|
dev->trans->ops->destroy(dev->trans);
|
|
free(dev);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (check_and_load(dev->trans) < 0) {
|
|
dev->trans->ops->destroy(dev->trans);
|
|
free(dev);
|
|
return NULL;
|
|
}
|
|
|
|
return &dev->base;
|
|
}
|
|
|
|
const struct device_class device_loadbsl = {
|
|
.name = "load-bsl",
|
|
.help = "Loadable USB BSL driver (USB 5xx/6xx).",
|
|
.open = loadbsl_open,
|
|
.destroy = loadbsl_destroy,
|
|
.readmem = loadbsl_readmem,
|
|
.writemem = loadbsl_writemem,
|
|
.erase = loadbsl_erase,
|
|
.getregs = loadbsl_getregs,
|
|
.setregs = loadbsl_setregs,
|
|
.ctl = loadbsl_ctl,
|
|
.poll = loadbsl_poll
|
|
};
|