view loadtools/romload.c @ 904:5041bcb8140f

tchtools/fc-vm2hex.c: update header comment for new situation
author Mychaela Falconia <falcon@freecalypso.org>
date Wed, 28 Dec 2022 08:41:57 +0000
parents ecea01f65146
children
line wrap: on
line source

/*
 * This module implements the communication protocol for pushing our
 * IRAM-loadable code to the Calypso ROM bootloader.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/errno.h>
#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <termios.h>
#include <unistd.h>
#include "../libserial/baudrate.h"
#include "srecreader.h"

extern int errno;

extern char *target_ttydev;
extern int target_fd;
extern struct baudrate baud_rate_table[];
extern struct baudrate *find_baudrate_by_name();

struct srecreader iramimage;
struct baudrate *romload_baud_rate = baud_rate_table;	/* 1st entry default */

static int beacon_interval = 13;	/* in milliseconds */
static int beacon_timeout;		/* ditto */

static u_char beacon_cmd[2] = {'<', 'i'};

static u_char param_cmd[11] = {'<', 'p',
			0x00,	/* baud rate select code (115200) */
			0x00,	/* DPLL setup: leave it off like on power-up, */
				/* OsmocomBB does the same thing */
			0x00, 0x04,	/* chip select timing (WS) settings */
					/* our setting matches both OsmocomBB */
					/* and what the ROM runs with */
					/* before receiving this command */
			0x22,	/* FFFF:F900 register config, low byte */
				/* OsmocomBB sends 0x00 here, but I've chosen */
				/* 0x22 to match the setting of this register */
				/* used by the boot ROM before this command. */
			0x00, 0x01, 0xD4, 0xC0	/* UART timeout */
				/* I've chosen the same value as what the */
				/* boot ROM runs with before getting this cmd */
};

static u_char write_cmd[1024] = {'<', 'w', 0x01, 0x01};
static u_char cksum_cmd[3]    = {'<', 'c'};
static u_char branch_cmd[6]   = {'<', 'b'};

static uint32_t write_cmd_endaddr;
static unsigned write_cmd_datalen;
static unsigned write_block_count;
static uint16_t image_cksum;

#define	MAX_WRITE_LEN	0x3F6

#define	INTERMEDIATE_TIMEOUT	500	/* ms to wait for responses */
#define	SERIAL_FLUSH_DELAY	200	/* also in ms */

/*
 * The following function should be called by command line option
 * parsers upon encountering the -i option.
 */
set_beacon_interval(arg)
	char *arg;
{
	int i;

	i = atoi(arg);
	if (i < 2 || i > 500) {
		fprintf(stderr, "invalid -i argument specified\n");
		exit(1);
	}
	beacon_interval = i;
}

/*
 * The following function should be called by command line option
 * parsers upon encountering the -t option.
 */
set_romload_timeout(arg)
	char *arg;
{
	beacon_timeout = atoi(arg);
}

/*
 * The following function should be called by command line option
 * parsers upon encountering the -b option.
 */
set_romload_baudrate(arg)
	char *arg;
{
	struct baudrate *br;

	br = find_baudrate_by_name(arg);
	if (!br)
		exit(1);	/* error msg already printed */
	if (br->bootrom_code < 0) {
		fprintf(stderr,
		"baud rate of %s is not supported by the Calypso boot ROM\n",
			br->name);
		exit(1);
	}
	romload_baud_rate = br;
}

/*
 * The following functions alter some of the parameters sent to the
 * boot ROM in the <p command.
 */
set_romload_pll_conf(byte)
{
	param_cmd[3] = byte;
}

set_romload_rhea_cntl(byte)
{
	param_cmd[6] = byte;
}

static int
expect_response(timeout)
{
	char buf[2];
	fd_set fds;
	struct timeval tv;
	int pass, cc;

	for (pass = 0; pass < 2; ) {
		FD_ZERO(&fds);
		FD_SET(target_fd, &fds);
		tv.tv_sec = 0;
		tv.tv_usec = timeout * 1000;
		cc = select(target_fd+1, &fds, NULL, NULL, &tv);
		if (cc < 0) {
			if (errno == EINTR)
				continue;
			perror("select");
			exit(1);
		}
		if (cc < 1)
			return(-1);
		cc = read(target_fd, buf + pass, 2 - pass);
		if (cc <= 0) {
			perror("read after successful select");
			exit(1);
		}
		if (pass == 0 && buf[0] != '>')
			continue;
		pass += cc;
	}
	return(buf[1]);
}

static
send_beacons()
{
	int time_accum;

	printf("Sending beacons to %s\n", target_ttydev);
	for (time_accum = 0; ; ) {
		write(target_fd, beacon_cmd, sizeof beacon_cmd);
		if (expect_response(beacon_interval) == 'i')
			return 0;
		time_accum += beacon_interval;
		if (beacon_timeout && time_accum >= beacon_timeout) {
			fprintf(stderr,
				"Timeout waiting for boot ROM response\n");
			exit(1);
		}
	}
}

static int
expect_add_response(buf, expect_bytes, timeout)
	u_char *buf;
{
	fd_set fds;
	struct timeval tv;
	int pass, cc;

	for (pass = 0; pass < expect_bytes; ) {
		FD_ZERO(&fds);
		FD_SET(target_fd, &fds);
		tv.tv_sec = 0;
		tv.tv_usec = timeout * 1000;
		cc = select(target_fd+1, &fds, NULL, NULL, &tv);
		if (cc < 0) {
			if (errno == EINTR)
				continue;
			perror("select");
			exit(1);
		}
		if (cc < 1)
			return(-1);
		cc = read(target_fd, buf + pass, expect_bytes - pass);
		if (cc <= 0) {
			perror("read after successful select");
			exit(1);
		}
		pass += cc;
	}
	return(0);
}

static uint32_t
compute_block_cksum()
{
	uint32_t sum;
	int i, llen;

	sum = write_cmd_datalen + 5;
	llen = write_cmd_datalen + 4;
	for (i = 0; i < llen; i++)
		sum += write_cmd[i+6];
	return sum;
}

static void
send_write_cmd()
{
	int resp;

	write_cmd[4] = write_cmd_datalen >> 8;
	write_cmd[5] = write_cmd_datalen & 0xFF;
	write(target_fd, write_cmd, write_cmd_datalen + 10);
	/* update our checksum accumulator */
	image_cksum += ~compute_block_cksum() & 0xFF;
	/* collect response */
	resp = expect_response(INTERMEDIATE_TIMEOUT);
	if (resp != 'w') {
		fprintf(stderr, "Block #%lu: ", write_block_count);
		if (resp < 0)
			fprintf(stderr, "No response to <w command\n");
		else if (isprint(resp))
			fprintf(stderr,
			"Got >%c in response to <w command; expected >w\n",
				resp);
		else
			fprintf(stderr,
			"Got > %02X in response to <w command; expected >w\n",
				resp);
		exit(1);
	}
	putchar('.');
	fflush(stdout);
	write_block_count++;
}

perform_romload()
{
	int resp;
	unsigned long rec_count;
	u_char addresp[2];
	static int zero = 0;

	if (open_srec_file(&iramimage) < 0)
		exit(1);
	set_fixed_baudrate("19200");
	ioctl(target_fd, FIONBIO, &zero);
	send_beacons();
	printf("Got beacon response, attempting download\n");

	usleep(SERIAL_FLUSH_DELAY * 1000);
	tcflush(target_fd, TCIFLUSH);
	param_cmd[2] = romload_baud_rate->bootrom_code;
	write(target_fd, param_cmd, sizeof param_cmd);
	resp = expect_response(INTERMEDIATE_TIMEOUT);
	if (resp != 'p') {
		if (resp < 0)
			fprintf(stderr, "No response to <p command\n");
		else if (isprint(resp))
			fprintf(stderr,
			"Got >%c in response to <p command; expected >p\n",
				resp);
		else
			fprintf(stderr,
			"Got > %02X in response to <p command; expected >p\n",
				resp);
		exit(1);
	}
	resp = expect_add_response(addresp, 2, INTERMEDIATE_TIMEOUT);
	if (resp < 0 || addresp[0] != 0x00 || addresp[1] != 0x04) {
		fprintf(stderr,
		"error: extra bytes after >p not received as expected\n");
		exit(1);
	}
	printf("<p command successful, switching to %s baud\n",
		romload_baud_rate->name);
	set_serial_baudrate(romload_baud_rate);
	usleep(SERIAL_FLUSH_DELAY * 1000);
	tcflush(target_fd, TCIFLUSH);

	write_cmd_datalen = 0;
	write_block_count = 0;
	image_cksum = 0;
	for (rec_count = 0; ; ) {
		if (read_s_record(&iramimage) < 0)
			exit(1);
		switch (iramimage.record_type) {
		case '0':
			if (iramimage.lineno == 1)
				continue;
			fprintf(stderr,
		"%s: S0 record found in line %d (expected in line 1 only)\n",
				iramimage.filename, iramimage.lineno);
			exit(1);
		case '3':
		case '7':
			if (s3s7_get_addr_data(&iramimage) < 0)
				exit(1);
			break;
		default:
			fprintf(stderr,
				"%s line %d: S%c record type not supported\n",
				iramimage.filename, iramimage.lineno,
				iramimage.record_type);
			exit(1);
		}
		if (iramimage.record_type == '7')
			break;
		/* must be S3 */
		if (iramimage.datalen < 1) {
			fprintf(stderr,
				"%s line %d: S3 record has zero data length\n",
				iramimage.filename, iramimage.lineno);
			exit(1);
		}
		/* get to work */
		if (!rec_count)
			printf("Sending image payload\n");
		if (write_cmd_datalen &&
		    (iramimage.addr != write_cmd_endaddr ||
		     write_cmd_datalen + iramimage.datalen > MAX_WRITE_LEN)) {
			send_write_cmd();
			write_cmd_datalen = 0;
		}
		if (!write_cmd_datalen) {
			bcopy(iramimage.record + 1, write_cmd + 6, 4);
			write_cmd_endaddr = iramimage.addr;
		}
		bcopy(iramimage.record + 5, write_cmd + 10 + write_cmd_datalen,
			iramimage.datalen);
		write_cmd_endaddr += iramimage.datalen;
		write_cmd_datalen += iramimage.datalen;
		rec_count++;
	}
	/* got S7 */
	fclose(iramimage.openfile);
	if (!rec_count) {
		fprintf(stderr,
		"%s line %d: S7 without any preceding S3 data records\n",
			iramimage.filename, iramimage.lineno);
		exit(1);
	}
	send_write_cmd();	/* last block */
	putchar('\n');		/* end line of dots */

	/* send <c */
	printf("Sending checksum\n");
	cksum_cmd[2] = ~image_cksum & 0xFF;
	write(target_fd, cksum_cmd, sizeof cksum_cmd);
	resp = expect_response(INTERMEDIATE_TIMEOUT);
	if (resp != 'c') {
		if (resp < 0)
			fprintf(stderr, "No response to <c command\n");
		else if (isprint(resp))
			fprintf(stderr,
			"Got >%c in response to <c command; expected >c\n",
				resp);
		else
			fprintf(stderr,
			"Got > %02X in response to <c command; expected >c\n",
				resp);
		exit(1);
	}
	resp = expect_add_response(addresp, 1, INTERMEDIATE_TIMEOUT);
	if (resp < 0) {
		fprintf(stderr,
		"error: extra byte after >c not received as expected\n");
		exit(1);
	}
	printf("<c command successful, sending <b\n");

	bcopy(iramimage.record + 1, branch_cmd + 2, 4);
	write(target_fd, branch_cmd, sizeof branch_cmd);
	resp = expect_response(INTERMEDIATE_TIMEOUT);
	if (resp != 'b') {
		if (resp < 0)
			fprintf(stderr, "No response to <b command\n");
		else if (isprint(resp))
			fprintf(stderr,
			"Got >%c in response to <b command; expected >b\n",
				resp);
		else
			fprintf(stderr,
			"Got > %02X in response to <b command; expected >b\n",
				resp);
		exit(1);
	}
	printf("<b command successful: downloaded image should now be running!\n");
	return(0);
}