mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-08 00:21:46 +00:00
Should be close to the STM32L476 in the STM32L4 family. Slightly different flash/RAM. It's currently running from the internal clock (HSI) at 16Mhz, we need to upgrade to 80Mhz (or 48Mhz if this is fast enough to save us the PLL locking time). The internal flash write/erase/protection is still not implemented for the whole STM32L4 family. Upgrade the SPI master support and verify that the TX works. Signed-off-by: Vincent Palatin <vpalatin@chromium.org> BRANCH=none BUG=chrome-os-partner:62893 TEST=make BOARD=eve_fp run it on Nucleo-L432KC (STM32L432KC is mostly the same MCU without AES) Change-Id: I87be7d4461aedfbd683ff7bb639c3a6005ee171e Reviewed-on: https://chromium-review.googlesource.com/442466 Commit-Ready: Vincent Palatin <vpalatin@chromium.org> Tested-by: Vincent Palatin <vpalatin@chromium.org> Reviewed-by: Daisuke Nojiri <dnojiri@chromium.org>
950 lines
21 KiB
C
950 lines
21 KiB
C
/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*
|
|
* STM32 SoC system monitor interface tool
|
|
* For Serial, implement proctol v2.0 as defined in:
|
|
* http://www.st.com/st-web-ui/static/active/en/resource/technical/\
|
|
* document/application_note/CD00264342.pdf
|
|
*
|
|
* For i2C, implement protocol v1.0 as defined in:
|
|
* http://www.st.com/st-web-ui/static/active/en/resource/technical/\
|
|
* document/application_note/DM00072315.pdf
|
|
*/
|
|
|
|
/* use cfmakeraw() */
|
|
#define _DEFAULT_SOURCE /* Newer glibc */
|
|
#define _BSD_SOURCE /* Older glibc */
|
|
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <getopt.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <linux/i2c-dev.h>
|
|
#include <termios.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
/* Monitor command set */
|
|
#define CMD_INIT 0x7f /* Starts the monitor */
|
|
|
|
#define CMD_GETCMD 0x00 /* Gets the allowed commands */
|
|
#define CMD_GETVER 0x01 /* Gets the bootloader version */
|
|
#define CMD_GETID 0x02 /* Gets the Chip ID */
|
|
#define CMD_READMEM 0x11 /* Reads memory */
|
|
#define CMD_GO 0x21 /* Jumps to user code */
|
|
#define CMD_WRITEMEM 0x31 /* Writes memory (SRAM or Flash) */
|
|
#define CMD_ERASE 0x43 /* Erases n pages of Flash memory */
|
|
#define CMD_EXTERASE 0x44 /* Erases n pages of Flash memory */
|
|
#define CMD_WP 0x63 /* Enables write protect */
|
|
#define CMD_WU 0x73 /* Disables write protect */
|
|
#define CMD_RP 0x82 /* Enables the read protection */
|
|
#define CMD_RU 0x92 /* Disables the read protection */
|
|
|
|
#define RESP_NACK 0x1f
|
|
#define RESP_ACK 0x79
|
|
|
|
/* Extended erase special parameters */
|
|
#define ERASE_ALL 0xffff
|
|
#define ERASE_BANK1 0xfffe
|
|
#define ERASE_BANK2 0xfffd
|
|
|
|
/* known STM32 SoC parameters */
|
|
struct stm32_def {
|
|
uint16_t id;
|
|
const char *name;
|
|
uint32_t flash_start;
|
|
uint32_t flash_size;
|
|
uint32_t page_size;
|
|
uint32_t cmds_len;
|
|
} chip_defs[] = {
|
|
{0x416, "STM32L15xxB", 0x08000000, 0x20000, 256, 13},
|
|
{0x429, "STM32L15xxB-A", 0x08000000, 0x20000, 256, 13},
|
|
{0x427, "STM32L15xxC", 0x08000000, 0x40000, 256, 13},
|
|
{0x435, "STM32L44xx", 0x08000000, 0x40000, 2048, 13},
|
|
{0x420, "STM32F100xx", 0x08000000, 0x20000, 1024, 13},
|
|
{0x410, "STM32F102R8", 0x08000000, 0x10000, 1024, 13},
|
|
{0x440, "STM32F05x", 0x08000000, 0x10000, 1024, 13},
|
|
{0x444, "STM32F03x", 0x08000000, 0x08000, 1024, 13},
|
|
{0x448, "STM32F07xB", 0x08000000, 0x20000, 2048, 13},
|
|
{0x432, "STM32F37xx", 0x08000000, 0x40000, 2048, 13},
|
|
{0x442, "STM32F09x", 0x08000000, 0x40000, 2048, 13},
|
|
{ 0 }
|
|
};
|
|
|
|
#define DEFAULT_TIMEOUT 4 /* seconds */
|
|
#define DEFAULT_BAUDRATE B38400
|
|
#define PAGE_SIZE 256
|
|
#define INVALID_I2C_ADAPTER -1
|
|
|
|
/* store custom parameters */
|
|
speed_t baudrate = DEFAULT_BAUDRATE;
|
|
int i2c_adapter = INVALID_I2C_ADAPTER;
|
|
const char *serial_port = "/dev/ttyUSB1";
|
|
const char *input_filename;
|
|
const char *output_filename;
|
|
|
|
/* optional command flags */
|
|
enum {
|
|
FLAG_UNPROTECT = 0x01,
|
|
FLAG_ERASE = 0x02,
|
|
FLAG_GO = 0x04,
|
|
FLAG_READ_UNPROTECT = 0x08,
|
|
};
|
|
|
|
typedef struct {
|
|
int size;
|
|
uint8_t *data;
|
|
} payload_t;
|
|
|
|
/* List all possible flash erase functions */
|
|
typedef int command_erase_t(int fd, uint16_t count, uint16_t start);
|
|
command_erase_t command_erase;
|
|
command_erase_t command_ext_erase;
|
|
command_erase_t command_erase_i2c;
|
|
|
|
command_erase_t *erase;
|
|
|
|
static void discard_input(int);
|
|
|
|
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
|
|
|
int open_serial(const char *port)
|
|
{
|
|
int fd, res;
|
|
struct termios cfg, cfg_copy;
|
|
|
|
fd = open(port, O_RDWR | O_NOCTTY);
|
|
if (fd == -1) {
|
|
perror("Unable to open serial port");
|
|
return -1;
|
|
}
|
|
|
|
/* put the tty in "raw" mode at the defined baudrate */
|
|
res = tcgetattr(fd, &cfg);
|
|
if (res == -1) {
|
|
perror("Cannot read tty attributes");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
cfmakeraw(&cfg);
|
|
cfsetspeed(&cfg, baudrate);
|
|
/* serial mode should be 8e1 */
|
|
cfg.c_cflag |= PARENB;
|
|
/* 200 ms timeout */
|
|
cfg.c_cc[VTIME] = 2;
|
|
cfg.c_cc[VMIN] = 0;
|
|
memcpy(&cfg_copy, &cfg, sizeof(cfg_copy));
|
|
|
|
/*
|
|
* tcsetattr() returns success if any of the modifications succeed, so
|
|
* its return value of zero is not an indication of success, one needs
|
|
* to check the result explicitly.
|
|
*/
|
|
tcsetattr(fd, TCSANOW, &cfg);
|
|
if (tcgetattr(fd, &cfg)) {
|
|
perror("Failed to re-read tty attributes");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
if (memcmp(&cfg, &cfg_copy, sizeof(cfg))) {
|
|
/*
|
|
* On some systems the setting which does not come through is
|
|
* the parity. We can try continuing without it when using
|
|
* certain interfaces, let's try.
|
|
*/
|
|
cfg_copy.c_cflag &= ~PARENB;
|
|
if (memcmp(&cfg, &cfg_copy, sizeof(cfg))) {
|
|
/*
|
|
* Something other than parity failed to get set, this
|
|
* is an error.
|
|
*/
|
|
perror("Cannot set tty attributes");
|
|
close(fd);
|
|
return -1;
|
|
} else {
|
|
fprintf(stderr, "Failed to enable parity\n");
|
|
}
|
|
}
|
|
|
|
discard_input(fd); /* in case were were invoked soon after reset */
|
|
return fd;
|
|
}
|
|
|
|
int open_i2c(const int port)
|
|
{
|
|
int fd;
|
|
char filename[20];
|
|
|
|
snprintf(filename, 19, "/dev/i2c-%d", port);
|
|
fd = open(filename, O_RDWR);
|
|
if (fd < 0) {
|
|
perror("Unable to open i2c adapter");
|
|
return -1;
|
|
}
|
|
/*
|
|
* When in I2C mode, the bootloader is listening at address 0x76 (10 bit
|
|
* mode), 0x3B (7 bit mode)
|
|
*/
|
|
if (ioctl(fd, I2C_SLAVE, 0x3B) < 0) {
|
|
perror("Unable to select proper address");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
|
|
static void discard_input(int fd)
|
|
{
|
|
uint8_t buffer[64];
|
|
int res, i;
|
|
|
|
/* Skip in i2c mode */
|
|
if (i2c_adapter != INVALID_I2C_ADAPTER)
|
|
return;
|
|
|
|
/* eat trailing garbage */
|
|
do {
|
|
res = read(fd, buffer, sizeof(buffer));
|
|
if (res > 0) {
|
|
printf("Recv[%d]:", res);
|
|
for (i = 0; i < res; i++)
|
|
printf("%02x ", buffer[i]);
|
|
printf("\n");
|
|
}
|
|
} while (res > 0);
|
|
}
|
|
|
|
int wait_for_ack(int fd)
|
|
{
|
|
uint8_t resp;
|
|
int res;
|
|
time_t deadline = time(NULL) + DEFAULT_TIMEOUT;
|
|
|
|
while (time(NULL) < deadline) {
|
|
res = read(fd, &resp, 1);
|
|
if ((res < 0) && (errno != EAGAIN)) {
|
|
perror("Failed to read answer");
|
|
return -EIO;
|
|
}
|
|
if (res == 1) {
|
|
if (resp == RESP_ACK)
|
|
return 0;
|
|
else if (resp == RESP_NACK) {
|
|
fprintf(stderr, "NACK\n");
|
|
discard_input(fd);
|
|
return -EINVAL;
|
|
} else {
|
|
fprintf(stderr, "Receive junk: %02x\n", resp);
|
|
}
|
|
}
|
|
}
|
|
fprintf(stderr, "Timeout\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
int send_command(int fd, uint8_t cmd, payload_t *loads, int cnt,
|
|
uint8_t *resp, int resp_size, int ack_requested)
|
|
{
|
|
int res, i, c;
|
|
payload_t *p;
|
|
int readcnt = 0;
|
|
uint8_t cmd_frame[] = { cmd, 0xff ^ cmd }; /* XOR checksum */
|
|
|
|
/* Send the command index */
|
|
res = write(fd, cmd_frame, 2);
|
|
if (res <= 0) {
|
|
perror("Failed to write command frame");
|
|
return -1;
|
|
}
|
|
|
|
/* Wait for the ACK */
|
|
if (wait_for_ack(fd) < 0) {
|
|
fprintf(stderr, "Failed to get command 0x%02x ACK\n", cmd);
|
|
return -1;
|
|
}
|
|
|
|
/* Send the command payloads */
|
|
for (p = loads, c = 0; c < cnt; c++, p++) {
|
|
uint8_t crc = 0;
|
|
int size = p->size;
|
|
uint8_t *data = malloc(size + 1), *data_ptr;
|
|
|
|
if (data == NULL) {
|
|
fprintf(stderr,
|
|
"Failed to allocate memory for load %d\n", c);
|
|
return -ENOMEM;
|
|
}
|
|
memcpy(data, p->data, size);
|
|
for (i = 0; i < size; i++)
|
|
crc ^= data[i];
|
|
if (size == 1)
|
|
crc = 0xff ^ crc;
|
|
data[size] = crc;
|
|
size++;
|
|
data_ptr = data;
|
|
while (size) {
|
|
res = write(fd, data_ptr, size);
|
|
if (res < 0) {
|
|
perror("Failed to write command payload");
|
|
free(data);
|
|
return -1;
|
|
}
|
|
size -= res;
|
|
data_ptr += res;
|
|
}
|
|
|
|
/* Wait for the ACK */
|
|
if (wait_for_ack(fd) < 0) {
|
|
fprintf(stderr, "payload %d ACK failed for CMD%02x\n",
|
|
c, cmd);
|
|
free(data);
|
|
return -1;
|
|
}
|
|
free(data);
|
|
}
|
|
|
|
/* Read the answer payload */
|
|
if (resp) {
|
|
while ((resp_size > 0) && (res = read(fd, resp, resp_size))) {
|
|
if (res < 0) {
|
|
perror("Failed to read payload");
|
|
return -1;
|
|
}
|
|
readcnt += res;
|
|
resp += res;
|
|
resp_size -= res;
|
|
}
|
|
|
|
/* Wait for the ACK */
|
|
if (ack_requested) {
|
|
if (wait_for_ack(fd) < 0) {
|
|
fprintf(stderr,
|
|
"Failed to get response to command 0x%02x ACK\n",
|
|
cmd);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
return readcnt;
|
|
}
|
|
|
|
struct stm32_def *command_get_id(int fd)
|
|
{
|
|
int res;
|
|
uint8_t id[3];
|
|
uint16_t chipid;
|
|
struct stm32_def *def;
|
|
|
|
res = send_command(fd, CMD_GETID, NULL, 0, id, sizeof(id), 1);
|
|
if (res > 0) {
|
|
if (id[0] != 1) {
|
|
fprintf(stderr, "unknown ID : %02x %02x %02x\n",
|
|
id[0], id[1], id[2]);
|
|
return NULL;
|
|
}
|
|
chipid = (id[1] << 8) | id[2];
|
|
for (def = chip_defs; def->id; def++)
|
|
if (def->id == chipid)
|
|
break;
|
|
if (def->id == 0)
|
|
def = NULL;
|
|
printf("ChipID 0x%03x : %s\n", chipid, def ? def->name : "???");
|
|
return def;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int init_monitor(int fd)
|
|
{
|
|
int res;
|
|
uint8_t init = CMD_INIT;
|
|
|
|
/* Skip in i2c mode */
|
|
if (i2c_adapter != INVALID_I2C_ADAPTER)
|
|
return 0;
|
|
|
|
printf("Waiting for the monitor startup ...");
|
|
fflush(stdout);
|
|
|
|
while (1) {
|
|
/* Send the command index */
|
|
res = write(fd, &init, 1);
|
|
if (res <= 0) {
|
|
perror("Failed to write command");
|
|
return -1;
|
|
}
|
|
/* Wait for the ACK */
|
|
res = wait_for_ack(fd);
|
|
if (res == 0)
|
|
break;
|
|
if (res == -EINVAL) {
|
|
/* we got NACK'ed, the loader might be already started
|
|
* let's ping it to check
|
|
*/
|
|
if (command_get_id(fd)) {
|
|
printf("Monitor already started.\n");
|
|
return 0;
|
|
}
|
|
}
|
|
if (res < 0 && res != -ETIMEDOUT)
|
|
return -1;
|
|
fflush(stdout);
|
|
}
|
|
printf("Done.\n");
|
|
|
|
/* read trailing chars */
|
|
discard_input(fd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int command_get_commands(int fd, struct stm32_def *chip)
|
|
{
|
|
int res, i;
|
|
uint8_t cmds[64];
|
|
|
|
/*
|
|
* For i2c, we have to request the exact amount of bytes we expect.
|
|
* TODO(gwendal): Broken on device with Bootloader version 1.1
|
|
*/
|
|
res = send_command(fd, CMD_GETCMD, NULL, 0, cmds, chip->cmds_len, 1);
|
|
if (res > 0) {
|
|
if (cmds[0] > sizeof(cmds) - 2) {
|
|
fprintf(stderr, "invalid GET answer (%02x...)\n",
|
|
cmds[0]);
|
|
return -1;
|
|
}
|
|
printf("Bootloader v%d.%d, commands : ",
|
|
cmds[1] >> 4, cmds[1] & 0xf);
|
|
|
|
erase = command_erase;
|
|
for (i = 2; i < 2 + cmds[0]; i++) {
|
|
if (cmds[i] == CMD_EXTERASE)
|
|
erase = command_ext_erase;
|
|
printf("%02x ", cmds[i]);
|
|
}
|
|
|
|
if (i2c_adapter != INVALID_I2C_ADAPTER)
|
|
erase = command_erase_i2c;
|
|
printf("\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int windex;
|
|
static const char wheel[] = {'|', '/', '-', '\\' };
|
|
static void draw_spinner(uint32_t remaining, uint32_t size)
|
|
{
|
|
int percent = (size - remaining)*100/size;
|
|
printf("\r%c%3d%%", wheel[windex++], percent);
|
|
windex %= sizeof(wheel);
|
|
}
|
|
|
|
int command_read_mem(int fd, uint32_t address, uint32_t size, uint8_t *buffer)
|
|
{
|
|
int res;
|
|
uint32_t remaining = size;
|
|
uint32_t addr_be;
|
|
uint8_t cnt;
|
|
payload_t loads[2] = {
|
|
{4, (uint8_t *)&addr_be},
|
|
{1, &cnt}
|
|
};
|
|
|
|
while (remaining) {
|
|
cnt = (remaining > PAGE_SIZE) ? PAGE_SIZE - 1 : remaining - 1;
|
|
addr_be = htonl(address);
|
|
|
|
draw_spinner(remaining, size);
|
|
fflush(stdout);
|
|
res = send_command(fd, CMD_READMEM, loads, 2, buffer, cnt + 1,
|
|
0);
|
|
if (res < 0)
|
|
return -EIO;
|
|
buffer += cnt + 1;
|
|
address += cnt + 1;
|
|
remaining -= cnt + 1;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
int command_write_mem(int fd, uint32_t address, uint32_t size, uint8_t *buffer)
|
|
{
|
|
int res;
|
|
uint32_t remaining = size;
|
|
uint32_t addr_be;
|
|
uint32_t cnt;
|
|
uint8_t outbuf[257];
|
|
payload_t loads[2] = {
|
|
{4, (uint8_t *)&addr_be},
|
|
{sizeof(outbuf), outbuf}
|
|
};
|
|
|
|
while (remaining) {
|
|
cnt = (remaining > PAGE_SIZE) ? PAGE_SIZE : remaining;
|
|
addr_be = htonl(address);
|
|
outbuf[0] = cnt - 1;
|
|
loads[1].size = cnt + 1;
|
|
memcpy(outbuf + 1, buffer, cnt);
|
|
|
|
draw_spinner(remaining, size);
|
|
fflush(stdout);
|
|
res = send_command(fd, CMD_WRITEMEM, loads, 2, NULL, 0, 1);
|
|
if (res < 0)
|
|
return -EIO;
|
|
|
|
buffer += cnt;
|
|
address += cnt;
|
|
remaining -= cnt;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
int command_ext_erase(int fd, uint16_t count, uint16_t start)
|
|
{
|
|
int res;
|
|
uint16_t count_be = htons(count);
|
|
payload_t load = { 2, (uint8_t *)&count_be };
|
|
uint16_t *pages = NULL;
|
|
|
|
if (count < 0xfff0) {
|
|
int i;
|
|
/* not a special value : build a list of pages */
|
|
load.size = 2 * (count + 1);
|
|
pages = malloc(load.size);
|
|
if (!pages)
|
|
return -ENOMEM;
|
|
load.data = (uint8_t *)pages;
|
|
pages[0] = htons(count - 1);
|
|
for (i = 0; i < count; i++)
|
|
pages[i+1] = htons(start + i);
|
|
}
|
|
|
|
res = send_command(fd, CMD_EXTERASE, &load, 1, NULL, 0, 1);
|
|
if (res >= 0)
|
|
printf("Flash erased.\n");
|
|
|
|
if (pages)
|
|
free(pages);
|
|
return res;
|
|
}
|
|
|
|
int command_erase_i2c(int fd, uint16_t count, uint16_t start)
|
|
{
|
|
int res;
|
|
uint16_t count_be = htons(count);
|
|
payload_t load[2] = {
|
|
{ 2, (uint8_t *)&count_be},
|
|
{ 0, NULL},
|
|
};
|
|
int load_cnt = 1;
|
|
uint16_t *pages = NULL;
|
|
|
|
if (count < 0xfff) {
|
|
int i;
|
|
/* not a special value : build a list of pages */
|
|
/*
|
|
* I2c protocol requires 2 messages, the count has to be acked
|
|
* before the addresses can be sent.
|
|
* TODO(gwendal): Still broken on i2c.
|
|
*/
|
|
load_cnt = 2;
|
|
load[1].size = 2 * count;
|
|
pages = malloc(load[1].size);
|
|
if (!pages)
|
|
return -ENOMEM;
|
|
load[1].data = (uint8_t *)pages;
|
|
count_be = htons(count - 1);
|
|
for (i = 0; i < count; i++)
|
|
pages[i] = htons(start + i);
|
|
} else {
|
|
load_cnt = 1;
|
|
}
|
|
|
|
res = send_command(fd, CMD_EXTERASE, load, load_cnt,
|
|
NULL, 0, 1);
|
|
if (res >= 0)
|
|
printf("Flash erased.\n");
|
|
|
|
if (pages)
|
|
free(pages);
|
|
return res;
|
|
}
|
|
|
|
|
|
int command_erase(int fd, uint16_t count, uint16_t start)
|
|
{
|
|
int res;
|
|
uint8_t count_8bit = count;
|
|
payload_t load = { 1, &count_8bit };
|
|
uint8_t *pages = NULL;
|
|
|
|
if (count < 0xff) {
|
|
int i;
|
|
/* not a special value : build a list of pages */
|
|
load.size = count + 1;
|
|
pages = malloc(load.size);
|
|
if (!pages)
|
|
return -ENOMEM;
|
|
load.data = (uint8_t *)pages;
|
|
pages[0] = count - 1;
|
|
for (i = 0; i < count; i++)
|
|
pages[i+1] = start + i;
|
|
}
|
|
|
|
res = send_command(fd, CMD_ERASE, &load, 1, NULL, 0, 1);
|
|
if (res >= 0)
|
|
printf("Flash erased.\n");
|
|
|
|
if (pages)
|
|
free(pages);
|
|
return res;
|
|
}
|
|
|
|
int command_read_unprotect(int fd)
|
|
{
|
|
int res;
|
|
|
|
res = send_command(fd, CMD_RU, NULL, 0, NULL, 0, 1);
|
|
if (res < 0)
|
|
return -EIO;
|
|
|
|
/* Wait for the ACK */
|
|
if (wait_for_ack(fd) < 0) {
|
|
fprintf(stderr, "Failed to get read-protect ACK\n");
|
|
return -EINVAL;
|
|
}
|
|
printf("Flash read unprotected.\n");
|
|
|
|
/* This commands triggers a reset */
|
|
if (init_monitor(fd) < 0) {
|
|
fprintf(stderr, "Cannot recover after RP reset\n");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int command_write_unprotect(int fd)
|
|
{
|
|
int res;
|
|
|
|
res = send_command(fd, CMD_WU, NULL, 0, NULL, 0, 1);
|
|
if (res < 0)
|
|
return -EIO;
|
|
|
|
/* Wait for the ACK */
|
|
if (wait_for_ack(fd) < 0) {
|
|
fprintf(stderr, "Failed to get write-protect ACK\n");
|
|
return -EINVAL;
|
|
}
|
|
printf("Flash write unprotected.\n");
|
|
|
|
/* This commands triggers a reset */
|
|
if (init_monitor(fd) < 0) {
|
|
fprintf(stderr, "Cannot recover after WP reset\n");
|
|
return -EIO;
|
|
}
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
int command_go(int fd, uint32_t address)
|
|
{
|
|
int res;
|
|
uint32_t addr_be = htonl(address);
|
|
payload_t load = { 4, (uint8_t *)&addr_be };
|
|
|
|
res = send_command(fd, CMD_GO, &load, 1, NULL, 0, 1);
|
|
if (res < 0)
|
|
return -EIO;
|
|
|
|
#if 0 /* this ACK should exist according to the documentation ... */
|
|
/* Wait for the ACK */
|
|
if (wait_for_ack(fd) < 0) {
|
|
fprintf(stderr, "Failed to get GO ACK\n");
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
printf("Program started at 0x%08x.\n", address);
|
|
return 0;
|
|
}
|
|
|
|
/* Return zero on success, a negative error value on failures. */
|
|
int read_flash(int fd, struct stm32_def *chip, const char *filename,
|
|
uint32_t offset, uint32_t size)
|
|
{
|
|
int res;
|
|
FILE *hnd;
|
|
uint8_t *buffer = malloc(size);
|
|
|
|
if (!buffer) {
|
|
fprintf(stderr, "Cannot allocate %d bytes\n", size);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
hnd = fopen(filename, "w");
|
|
if (!hnd) {
|
|
fprintf(stderr, "Cannot open file %s for writing\n", filename);
|
|
free(buffer);
|
|
return -EIO;
|
|
}
|
|
|
|
if (!size)
|
|
size = chip->flash_size;
|
|
offset += chip->flash_start;
|
|
printf("Reading %d bytes at 0x%08x\n", size, offset);
|
|
res = command_read_mem(fd, offset, size, buffer);
|
|
if (res > 0) {
|
|
if (fwrite(buffer, res, 1, hnd) != 1)
|
|
fprintf(stderr, "Cannot write %s\n", filename);
|
|
}
|
|
printf("\r %d bytes read.\n", res);
|
|
|
|
fclose(hnd);
|
|
free(buffer);
|
|
return (res < 0) ? res : 0;
|
|
}
|
|
|
|
/* Return zero on success, a negative error value on failures. */
|
|
int write_flash(int fd, struct stm32_def *chip, const char *filename,
|
|
uint32_t offset)
|
|
{
|
|
int res, written;
|
|
FILE *hnd;
|
|
int size = chip->flash_size;
|
|
uint8_t *buffer = malloc(size);
|
|
|
|
if (!buffer) {
|
|
fprintf(stderr, "Cannot allocate %d bytes\n", size);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (!strncmp(filename, "-", sizeof("-")))
|
|
hnd = fdopen(STDIN_FILENO, "r");
|
|
else
|
|
hnd = fopen(filename, "r");
|
|
if (!hnd) {
|
|
fprintf(stderr, "Cannot open file %s for reading\n", filename);
|
|
free(buffer);
|
|
return -EIO;
|
|
}
|
|
res = fread(buffer, 1, size, hnd);
|
|
if (res <= 0) {
|
|
fprintf(stderr, "Cannot read %s\n", filename);
|
|
free(buffer);
|
|
return -EIO;
|
|
}
|
|
fclose(hnd);
|
|
|
|
offset += chip->flash_start;
|
|
printf("Writing %d bytes at 0x%08x\n", res, offset);
|
|
written = command_write_mem(fd, offset, res, buffer);
|
|
if (written != res) {
|
|
fprintf(stderr, "Error writing to flash\n");
|
|
free(buffer);
|
|
return -EIO;
|
|
}
|
|
printf("\rDone.\n");
|
|
|
|
free(buffer);
|
|
return 0;
|
|
}
|
|
|
|
static const struct option longopts[] = {
|
|
{"device", 1, 0, 'd'},
|
|
{"read", 1, 0, 'r'},
|
|
{"write", 1, 0, 'w'},
|
|
{"erase", 0, 0, 'e'},
|
|
{"go", 0, 0, 'g'},
|
|
{"help", 0, 0, 'h'},
|
|
{"unprotect", 0, 0, 'u'},
|
|
{"baudrate", 1, 0, 'b'},
|
|
{"adapter", 1, 0, 'a'},
|
|
{NULL, 0, 0, 0}
|
|
};
|
|
|
|
void display_usage(char *program)
|
|
{
|
|
fprintf(stderr,
|
|
"Usage: %s [-a <i2c_adapter> | [-d <tty>] [-b <baudrate>]]"
|
|
" [-u] [-e] [-U] [-r <file>] [-w <file>] [-g]\n", program);
|
|
fprintf(stderr, "Can access the controller via serial port or i2c\n");
|
|
fprintf(stderr, "Serial port mode:\n");
|
|
fprintf(stderr, "--d[evice] <tty> : use <tty> as the serial port\n");
|
|
fprintf(stderr, "--b[audrate] <baudrate> : set serial port speed "
|
|
"to <baudrate> bauds\n");
|
|
fprintf(stderr, "i2c mode:\n");
|
|
fprintf(stderr, "--a[dapter] <id> : use i2c adapter <id>.\n\n");
|
|
fprintf(stderr, "--u[nprotect] : remove flash write protect\n");
|
|
fprintf(stderr, "--U[nprotect] : remove flash read protect\n");
|
|
fprintf(stderr, "--e[rase] : erase all the flash content\n");
|
|
fprintf(stderr, "--r[ead] <file> : read the flash content and "
|
|
"write it into <file>\n");
|
|
fprintf(stderr, "--w[rite] <file|-> : read <file> or\n\t"
|
|
"standard input and write it to flash\n");
|
|
fprintf(stderr, "--g[o] : jump to execute flash entrypoint\n");
|
|
|
|
exit(2);
|
|
}
|
|
|
|
speed_t parse_baudrate(const char *value)
|
|
{
|
|
int rate = atoi(value);
|
|
|
|
switch (rate) {
|
|
case 9600:
|
|
return B9600;
|
|
case 19200:
|
|
return B19200;
|
|
case 38400:
|
|
return B38400;
|
|
case 57600:
|
|
return B57600;
|
|
case 115200:
|
|
return B115200;
|
|
default:
|
|
fprintf(stderr, "Invalid baudrate %s, using %d\n",
|
|
value, DEFAULT_BAUDRATE);
|
|
return DEFAULT_BAUDRATE;
|
|
}
|
|
}
|
|
|
|
int parse_parameters(int argc, char **argv)
|
|
{
|
|
int opt, idx;
|
|
int flags = 0;
|
|
|
|
while ((opt = getopt_long(argc, argv, "a:b:d:eghr:w:uU?",
|
|
longopts, &idx)) != -1) {
|
|
switch (opt) {
|
|
case 'a':
|
|
i2c_adapter = atoi(optarg);
|
|
break;
|
|
case 'b':
|
|
baudrate = parse_baudrate(optarg);
|
|
break;
|
|
case 'd':
|
|
serial_port = optarg;
|
|
break;
|
|
case 'e':
|
|
flags |= FLAG_ERASE;
|
|
break;
|
|
case 'g':
|
|
flags |= FLAG_GO;
|
|
break;
|
|
case 'h':
|
|
case '?':
|
|
display_usage(argv[0]);
|
|
break;
|
|
case 'r':
|
|
input_filename = optarg;
|
|
break;
|
|
case 'w':
|
|
output_filename = optarg;
|
|
break;
|
|
case 'u':
|
|
flags |= FLAG_UNPROTECT;
|
|
break;
|
|
case 'U':
|
|
flags |= FLAG_READ_UNPROTECT;
|
|
break;
|
|
}
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int ser;
|
|
struct stm32_def *chip;
|
|
int ret = 1;
|
|
int flags;
|
|
|
|
/* Parse command line options */
|
|
flags = parse_parameters(argc, argv);
|
|
|
|
if (i2c_adapter == INVALID_I2C_ADAPTER) {
|
|
/* Open the serial port tty */
|
|
ser = open_serial(serial_port);
|
|
} else {
|
|
ser = open_i2c(i2c_adapter);
|
|
}
|
|
if (ser < 0)
|
|
return 1;
|
|
/* Trigger embedded monitor detection */
|
|
if (init_monitor(ser) < 0)
|
|
goto terminate;
|
|
|
|
chip = command_get_id(ser);
|
|
if (!chip)
|
|
goto terminate;
|
|
|
|
command_get_commands(ser, chip);
|
|
|
|
if (flags & FLAG_READ_UNPROTECT)
|
|
command_read_unprotect(ser);
|
|
if (flags & FLAG_UNPROTECT)
|
|
command_write_unprotect(ser);
|
|
|
|
if (flags & FLAG_ERASE || output_filename) {
|
|
if (!strncmp("STM32L15", chip->name, 8)) {
|
|
/* Mass erase is not supported on STM32L15xx */
|
|
/* command_ext_erase(ser, ERASE_ALL, 0); */
|
|
int i, page_count = chip->flash_size / chip->page_size;
|
|
for (i = 0; i < page_count; i += 128) {
|
|
int count = MIN(128, page_count - i);
|
|
ret = erase(ser, count, i);
|
|
if (ret)
|
|
goto terminate;
|
|
}
|
|
} else {
|
|
ret = erase(ser, 0xFFFF, 0);
|
|
if (ret)
|
|
goto terminate;
|
|
}
|
|
}
|
|
|
|
if (input_filename) {
|
|
ret = read_flash(ser, chip, input_filename,
|
|
0, chip->flash_size);
|
|
if (ret)
|
|
goto terminate;
|
|
}
|
|
|
|
if (output_filename) {
|
|
ret = write_flash(ser, chip, output_filename, 0);
|
|
if (ret)
|
|
goto terminate;
|
|
}
|
|
|
|
/* Run the program from flash */
|
|
if (flags & FLAG_GO)
|
|
command_go(ser, chip->flash_start);
|
|
|
|
/* Normal exit */
|
|
ret = 0;
|
|
terminate:
|
|
/* Close serial port */
|
|
close(ser);
|
|
return ret;
|
|
}
|