PGZ Loader

Added PGZ loader and a utility to create PGZ files from Motorola SREC. Removed SREC loader.
This commit is contained in:
Peter Weingartner 2021-10-04 18:14:45 -04:00
parent f06a24d8d3
commit 37f96ce1d5
14 changed files with 5703 additions and 5200 deletions

View file

@ -0,0 +1,7 @@
# Sample: HelloPGZ #
One of the simple executable formats that the FoenixMCP supports is the PGZ format inherited from the original Foenix stock kernel. This assembly demo uses the VASM assembler to create a simple Motorola SREC file that prints "Hello, world." This SREC file can be converted to PGZ using the SRECPGZ C program included in the utilities folder of this repository.
## Building ##
Make sure that the VASM binaries are in your executable path and then use the `build.bat` file to create the SREC file.

View file

@ -0,0 +1,2 @@
@echo off
vasmm68k_mot -Fsrec -s37 -exec=start -L hello.lst -o hello.s37 hello.s

View file

@ -0,0 +1,55 @@
F00:0001 ;;;
F00:0002 ;;; Sample PGX Program
F00:0003 ;;;
F00:0004
F00:0005 org $010000
F00:0006
F00:0007 start: move.l #$13,d0 ; sys_chan_write
S01:00010000: 70 13
F00:0008 clr.l d1 ; Channel #0
S01:00010002: 72 00
F00:0009 move.l #greet,d2 ; Pointer to message
S01:00010004: 24 3C 00 01 00 14
F00:0010 move.l #greet_end-greet+1,d3 ; Length of message
S01:0001000A: 76 0E
F00:0011 trap #15
S01:0001000C: 4E 4F
F00:0012
F00:0013 done: clr.l d0 ; sys_exit
S01:0001000E: 70 00
F00:0014 clr.l d1 ; Return value = 0
S01:00010010: 72 00
F00:0015 trap #15
S01:00010012: 4E 4F
F00:0016
F00:0017 greet: dc.b "Hello, world!"
S01:00010014: 48 65 6C 6C 6F 2C 20 77 6F 72 6C 64 21
F00:0018 greet_end: dc.b 0
S01:00010021: 00
F00:0019
Sections:
S01 seg10000
Sources:
F00 hello.s
Symbols:
done EXPR(65550=0x1000e) UNUSED ABS
greet_end EXPR(65569=0x10021) ABS
greet EXPR(65556=0x10014) ABS
start EXPR(65536=0x10000) UNUSED ABS
_MOVEMBYTES EXPR(0=0x0) INTERNAL
MOVEMSIZE EXPR(0=0x0) INTERNAL
_MOVEMREGS EXPR(0=0x0) INTERNAL
__LINE__ EXPR(19=0x13) INTERNAL
__FO EXPR(0=0x0) INTERNAL
__RS EXPR(0=0x0) INTERNAL
REPTN EXPR(-1=0xffffffff) INTERNAL
__VASM EXPR(1=0x1) INTERNAL
__UNIXFS EXPR(0=0x0) INTERNAL
There have been no errors.

View file

18
samples/HelloPGZ/hello.s Normal file
View file

@ -0,0 +1,18 @@
;;;
;;; Sample PGX Program
;;;
org $010000
start: move.l #$13,d0 ; sys_chan_write
clr.l d1 ; Channel #0
move.l #greet,d2 ; Pointer to message
move.l #greet_end-greet+1,d3 ; Length of message
trap #15
done: clr.l d0 ; sys_exit
clr.l d1 ; Return value = 0
trap #15
greet: dc.b "Hello, world!"
greet_end: dc.b 0

View file

@ -0,0 +1,4 @@
S00B00007365673130303030C4
S3250001000070137200243C00010014760E4E4F700072004E4F48656C6C6F2C20776F726C6467
S307000100202100B6
S70500010000F9

View file

@ -614,6 +614,159 @@ unsigned short atoi_hex(char * hex) {
return (atoi_hex_1(hex[0]) << 4 | atoi_hex_1(hex[1])); return (atoi_hex_1(hex[0]) << 4 | atoi_hex_1(hex[1]));
} }
/* Loader for the PGZ binary file format
* Supports both the original 24-bit PGZ format and the new 32-bit PGZ format
*
* The PGZ format:
* First byte: ASCII "Z" for 24-bit PGZ, ASCII "z" for 32-bit PGZ
* Remaining bytes are segments of two types:
* 1) Data segment.
* Each data segment starts with an address, which is either 24-bit or 32-bit long, depending on
* which flavor of PGZ is used. The address expressed in little-endian form and is the starting
* address of the destination for this data block.
* Immediately following the address is the size of the block, also either 24-bits or 32-bits,
* and also in little-endian form. This size is the number of bytes in the data segment.
* Immediately following the size are the data bytes (as many as were specified in the size field)
*
* 2) Start segment.
* A data segment consists of the same address and size fields as the data segment, but the size
* is 0. The start segment specifies the starting address of the executable. The start segment
* is not required unless the PGZ is intended as an executable file. If none is present, the
* PGZ can be loaded but not executed. There should be at most one start segment in a PGZ file,
* if more than one is present, only the last will be honored.
*
* Inputs:
* path = the path to the file to load
* destination = the destination address (ignored for PGX)
* start = pointer to the long variable to fill with the starting address
* (0 if not an executable, any other number if file is executable
* with a known starting address)
*
* Returns:
* 0 on success, negative number on error
*/
short fsys_pgz_loader(short chan, long destination, long * start) {
unsigned char * chunk = 0;
unsigned char * dest = 0;
long file_idx = 0; /* Offset within the file */
long segment_idx = 0; /* Offset within a segment */
long address = -1; /* Current segment address */
long count = -1; /* Current segment size */
short use_32bits = 0; /* File format is either 24-bit or 32-bit */
short size_idx = 0; /* Expected offset for first byte of the size */
short data_idx = 0; /* Expected offset for the first byte of the data */
short result = 0;
TRACE("fsys_pgx_loader");
/* Allocate the buffer we'll use for reading the file */
chunk = malloc(DEFAULT_CHUNK_SIZE);
if (chunk == 0) {
result = ERR_OUT_OF_MEMORY;
} else {
/* We have our buffer... start reading chunks into it */
while (result == 0) {
/* Try to read a chunk of data */
short n = chan_read(chan, chunk, DEFAULT_CHUNK_SIZE);
if (n > 0) {
int i;
for (i = 0; i < n; i++, file_idx++) {
if (file_idx == 0) {
/* Signature byte... must be either "Z", or "z" */
if (chunk[i] == 'Z') {
/* PGZ 24-bit signature byte */
use_32bits = 0;
size_idx = 3;
data_idx = 6;
} else if (chunk[i] == 'z') {
/* PGZ 32-bit signature byte */
use_32bits = 1;
size_idx = 4;
data_idx = 8;
} else {
/* Signature byte does not match expectation */
return ERR_BAD_BINARY;
}
} else {
/* We're in the segments... */
if ((segment_idx >= 0) && (segment_idx < size_idx)) {
/* We're in the address bytes */
switch (segment_idx) {
case 0:
address = chunk[i];
count = -1;
break;
case 1:
address = address | chunk[i] << 8;
break;
case 2:
address = address | chunk[i] << 16;
log_num(LOG_INFO, "PGZ 24-bit address: ", address);
break;
case 3:
address = address | chunk[i] << 24;
log_num(LOG_INFO, "PGZ 32-bit address: ", address);
break;
}
} else if ((segment_idx >= size_idx) && (segment_idx < data_idx)) {
/* We're in the size bytes */
switch (segment_idx - size_idx) {
case 0:
dest = (unsigned char *)address;
count = chunk[i];
break;
case 1:
count = count | chunk[i] << 8;
break;
case 2:
count = count | chunk[i] << 16;
if (!use_32bits && count == 0) {
*start = address;
}
log_num(LOG_INFO, "PGZ 24-bit count: ", count);
break;
case 3:
count = count | chunk[i] << 24;
if (use_32bits && count == 0) {
*start = address;
}
log_num(LOG_INFO, "PGZ 32-bit count: ", count);
break;
}
} else {
/* We're in the data bytes */
if (segment_idx - data_idx < count) {
*dest++ = chunk[i];
}
}
segment_idx++;
if (count >= 0) {
if (segment_idx >= data_idx + count) {
/* If we've reached the end of the segment, start the next */
segment_idx = 0;
address = -1;
count = -1;
}
}
}
}
} else {
/* We've reached the end of the file */
break;
}
}
}
return result;
}
/* /*
* Loader for the PGX binary file format * Loader for the PGX binary file format
* *
@ -695,151 +848,6 @@ short fsys_pgx_loader(short chan, long destination, long * start) {
return result; return result;
} }
/*
* Loader for the Motorola SREC format
*
* See: https://en.wikipedia.org/wiki/SREC_(file_format)
*
* Inputs:
* path = the path to the file to load
* destination = the destination address (0 for use file's address)
* start = pointer to the long variable to fill with the starting address
* (0 if not an executable, any other number if file is executable
* with a known starting address)
*
* Returns:
* 0 on success, negative number on error
*/
short fsys_srec_loader(short chan, long destination, long * start) {
short n, i, chksum, data_index;
long address;
unsigned char count,data;
unsigned char addr[4];
char line[FYS_SREC_MAX_LINE_LENGTH];
unsigned char * dest;
TRACE("fsys_srec_loader");
/* TODO: verify the check sum */
while (1) {
chksum = 0;
count = 0;
n = sys_chan_readline(chan, line, FYS_SREC_MAX_LINE_LENGTH);
if (n < 0) {
/* If there was an error, return it */
return n;
} else if (n == 0) {
/* If we got nothing back, we're finished */
break;
} else if (n > 6) {
/* Only process the line if it starts with S and has more than six characters */
if (line[0] = 'S') {
/* Get the count and start the check sum */
count = atoi_hex(&line[1]);
chksum = count;
/* Determine how to process the line based on its type */
switch (line[1]) {
case '1':
/* 16-bit data line: S1nnaaaadd...ddcc */
addr[1] = atoi_hex(&line[4]);
addr[0] = atoi_hex(&line[6]);
dest = (unsigned char *)(addr[1] << 8 | addr[0]);
data_index = 8;
count -= 2;
chksum += addr[1] + addr[0];
/* Copy the data */
for (i = 0; i < count * 2; i += 2) {
data = atoi_hex(&line[data_index + i]);
chksum += data;
*dest++ = data;
}
break;
case '2':
/* 24-bit address data line: S2nnaaaaaadd..ddcc */
addr[2] = atoi_hex(&line[4]);
addr[1] = atoi_hex(&line[6]);
addr[0] = atoi_hex(&line[8]);
dest = (unsigned char *)(addr[2] << 16 | addr[1] << 8 | addr[0]);
data_index = 8;
count -= 2;
chksum += addr[2] + addr[1] + addr[0];
/* Copy the data */
for (i = 0; i < count * 2; i += 2) {
data = atoi_hex(&line[data_index + i]);
chksum += data;
*dest++ = data;
}
break;
case '3':
/* 32-bit address data line: S2nnaaaaaaaadd..ddcc */
addr[3] = atoi_hex(&line[4]);
addr[2] = atoi_hex(&line[6]);
addr[1] = atoi_hex(&line[8]);
addr[0] = atoi_hex(&line[10]);
dest = (unsigned char *)(addr[3] << 24 | addr[2] << 16 | addr[1] << 8 | addr[0]);
data_index = 8;
count -= 2;
chksum += addr[3] + addr[2] + addr[1] + addr[0];
/* Copy the data */
for (i = 0; i < count * 2; i += 2) {
data = atoi_hex(&line[data_index + i]);
chksum += data;
*dest++ = data;
}
break;
case '5':
/* 16-bit starting address */
addr[1] = atoi_hex(&line[4]);
addr[0] = atoi_hex(&line[6]);
*start = (long)(addr[1] << 8 | addr[0]);
chksum += addr[2] + addr[1] + addr[0];
break;
case '6':
/* 24-bit starting address */
addr[2] = atoi_hex(&line[4]);
addr[1] = atoi_hex(&line[6]);
addr[0] = atoi_hex(&line[8]);
*start = (long)(addr[2] << 16 | addr[1] << 8 | addr[0]);
chksum += addr[2] + addr[1] + addr[0];
break;
case '7':
/* 32-bit starting address */
addr[3] = atoi_hex(&line[4]);
addr[2] = atoi_hex(&line[6]);
addr[1] = atoi_hex(&line[8]);
addr[0] = atoi_hex(&line[10]);
*start = (long)(addr[3] << 24 | addr[2] << 16 | addr[1] << 8 | addr[0]);
chksum += addr[3] + addr[2] + addr[1] + addr[0];
break;
default:
/* Ignore any other kind of line */
count = 0;
break;
}
}
}
}
/* If we get here, we should have loaded the file successfully */
return 0;
}
/* /*
* Load a file into memory at the designated destination address. * Load a file into memory at the designated destination address.
* *
@ -1026,7 +1034,7 @@ short fsys_init() {
} }
/* Register the built-in binary file loaders */ /* Register the built-in binary file loaders */
fsys_register_loader("PRS", fsys_srec_loader); fsys_register_loader("PGZ", fsys_pgz_loader);
fsys_register_loader("PGX", fsys_pgx_loader); fsys_register_loader("PGX", fsys_pgx_loader);
/* Register the channel driver for files. */ /* Register the channel driver for files. */

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,27 @@
# SRECPGZ
This project is a simple C program to convert Motorola SREC hex files to Foenix PGZ executable binaries.
## Usage:
The program takes one option and two required path parameters:
1. The `--large` switch if present generates a 32-bit PGZ file. If not, the old 24-bit PGZ format will be used.
1. The first path is the input SREC file
2. The second path is the output binary file.
```
srecpgz [--large] <input srec file> <output bin file>
```
## PGZ Format
The output format will depend on whether or not the `--large` option was used. By default, 24-bit address and count fields are used.
If the `--large` option is enabled, 32-bit address and count fields are used.
* The first byte is the header or signature byte. For 24-bit PGZ, an uppercase 'Z' is used. For 32-bit PGZ, a lowercase 'z' is used.
* After the signature can come any number of blocks. Each block has two mandatory fields, and an optional data field:
1. The first field is the address of the block in memory. The address is specified in 24-bit or 32-bit, little endian format.
1. The second field is the size of the data block. The size is specified in 24-bit 32-bit, little endian format.
1. If the size (_n_) is non-zero, immediately after the size field are _n_ bytes of data. These are the data bytes to be loaded into that address block of memory.
* Each block immediately follows the one before it. So if a block has a size of zero, the next block starts immediately after the last size byte. If the block has a non-zero size, the next block starts immediately after the data field.
* A block with a size of 0 and no data field specifies the starting address for the executable (the address field specifies the starting address). At least one starting address block must be contained in the PGZ file for it to be executable. If more than one starting address block is present, the last starting address block is taken to be the correct starting address.

BIN
utilities/srecpgz/hello.pgz Normal file

Binary file not shown.

View file

@ -0,0 +1,4 @@
S00B00007365673130303030C4
S3250001000070137200243C00010014760E4E4F700072004E4F48656C6C6F2C20776F726C6467
S307000100202100B6
S70500010000F9

BIN
utilities/srecpgz/srecpgx Normal file

Binary file not shown.

363
utilities/srecpgz/srecpgx.c Normal file
View file

@ -0,0 +1,363 @@
/*
* A simple utility to convert Motorola SREC to the Foenix PGZ file format
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_BUFFER 128
enum {
STAT_ADDRESS_OVERFLOW = -3, /* The address field was too big for 24-bit format */
STAT_COUNT_OVERFLOW = -2, /* The count field was too big for 24-bit format */
STAT_GOOD = 0, /* The record is good and may be used */
STAT_NO_RECORD, /* The line parsed contained no record or an ignored record type */
STAT_EOF, /* There is no more data in the file */
STAT_BAD_ADDRESS, /* There was a parse error in the address field */
STAT_BAD_DATA, /* There was a parse error in the data field */
STAT_BAD_CHKSUM, /* There was a parse error in the checksum field */
STAT_MISMATCH_CHKSUM, /* The checksum does not match the line */
STAT_ERROR /* General file read error */
};
/*
* Structure to contain a record from the SREC file
*/
typedef struct s_srecord {
short status;
short type;
short count;
short binary_count;
long address;
char data[MAX_BUFFER];
short checksum;
} t_srecord, *p_srecord;
/* Size of the address and count fields: 0 = 24-bit, 1 = 32-bit */
short use_32bits = 0;
/*
* Convert a hex digit to a binary number
*/
short hex_convert(char c) {
if ((c >= '0') && (c <= '9')) {
return (c - '0');
} else if ((c >= 'a') && (c <= 'f')) {
return (c - 'a' + 10);
} else if ((c >= 'A') && (c <= 'F')) {
return (c - 'A' + 10);
} else {
/* There was a problem... return -1 as the result */
return -1;
}
}
/*
* Convert a string of hex digits to a binary number
*
* Inputs:
* raw = the source string
* count = the number of characters to parse
*
* Returns:
* the binary number read, -1 if there was an error
*/
long parse_hex(char * raw, short count) {
int i;
long result = 0;
for (i = 0; i < count; i++) {
short n = hex_convert(raw[i]);
if (n == -1) {
return -1;
} else {
result = result * 16 + n;
}
}
return result;
}
/*
* Parse the data field
*
* Inputs:
* raw = the source string of hex digits (must be the start of the data field)
* data = the buffer to fill with the binary data
* count = the number of chars to put into data
*/
short parse_data(char * raw, char * data, short count) {
int i;
for (i = 0; i < count; i++) {
char * digits = &raw[2*i];
long n = parse_hex(digits, 2);
if (n == -1) {
return -1;
} else {
data[i] = (char)(n & 0xff);
}
}
return 0;
}
/*
* Read a record from the input stream and fill it out
*
* Inputs:
* fd = FILE pointer for the SREC file to read
* r = pointer to the record structure to fill out
*/
void read_record(FILE * fd, p_srecord r) {
char buffer[MAX_BUFFER];
short n;
int i;
r->status = STAT_NO_RECORD;
char * result = fgets(buffer, MAX_BUFFER, fd);
if (result == 0) {;
r->status = STAT_EOF;
} else if (strlen(buffer) >= 10) {
if ((buffer[0] == 'S') && isdigit(buffer[1]) && (buffer[1] >= '0') && (buffer[1] <= '9')) {
r->status = STAT_GOOD;
r->type = buffer[1] - '0';
r->count = (short)parse_hex(&buffer[2], 2);
r->checksum = (short)parse_hex(&buffer[strlen(buffer)-2], 2);
for (i = 0; i < MAX_BUFFER; i++) {
r->data[i] = 0;
}
switch (r->type) {
case 1:
/* Data record, 16-bit address */
r->binary_count = r->count - 3;
r->address = parse_hex(&buffer[4], 4);
n = parse_data(&buffer[8], r->data, r->binary_count);
if (n == -1) {
r->status = STAT_BAD_DATA;
}
break;
case 2:
/* Data record, 24-bit address */
r->binary_count = r->count - 4;
r->address = parse_hex(&buffer[4], 6);
n = parse_data(&buffer[10], r->data, r->binary_count);
if (n == -1) {
r->status = STAT_BAD_DATA;
}
break;
case 3:
/* Data record, 32-bit address */
r->binary_count = r->count - 5;
r->address = parse_hex(&buffer[4], 8);
n = parse_data(&buffer[12], r->data, r->binary_count);
if (n == -1) {
r->status = STAT_BAD_DATA;
}
break;
case 7:
/* Start address, 32-bit */
r->address = parse_hex(&buffer[4], 8);
r->binary_count = 0;
break;
case 8:
/* Start address, 24-bit */
r->address = parse_hex(&buffer[4], 6);
r->binary_count = 0;
break;
case 9:
/* Start address, 16-bit */
r->address = parse_hex(&buffer[4], 4);
r->binary_count = 0;
break;
default:
/* Unsupported type, return null */
r->status = STAT_NO_RECORD;
break;
}
}
}
}
/*
* Format the record for binary output
*
* Inputs:
* out = FILE pointer for the output binary file
* r = record to be output
*/
short write_record(FILE * out, p_srecord r) {
int i;
char buffer[MAX_BUFFER];
short count_idx; /* offset to count field in binary output */
short data_idx; /* offset to data field in binary output */
fprintf(stderr, "{type=%d, addr=%08x, count=%08x}\n", r->type, (int)r->address, (int)r->binary_count);
if (use_32bits) {
count_idx = 4;
data_idx = 8;
} else {
count_idx = 3;
data_idx = 6;
}
/* Clear out the buffer */
for (i = 0; i < MAX_BUFFER; i++) {
buffer[i] = 0;
}
/* Set the address of the record */
if (use_32bits) {
buffer[3] = (char)((r->address >> 24) & 0xff);
} else if ((r->address >> 24) != 0) {
return STAT_ADDRESS_OVERFLOW;
}
buffer[2] = (char)((r->address >> 16) & 0xff);
buffer[1] = (char)((r->address >> 8) & 0xff);
buffer[0] = (char)((r->address) & 0xff);
/* Set the count of the record */
if (use_32bits) {
buffer[count_idx+3] = (char)((r->binary_count >> 24) & 0xff);
} else if ((r->binary_count >> 24) != 0) {
return STAT_COUNT_OVERFLOW;
}
buffer[count_idx+2] = (char)((r->binary_count >> 16) & 0xff);
buffer[count_idx+1] = (char)((r->binary_count >> 8) & 0xff);
buffer[count_idx] = (char)((r->binary_count) & 0xff);
/* Fill out the data */
for (i = 0; i < r->binary_count; i++) {
buffer[data_idx+i] = r->data[i];
}
int result = write(fileno(out), buffer, r->binary_count + data_idx);
if (result == -1) {
perror("Error writing file");
return -1;
}
return 0;
}
int main(int argc, char * argv[]) {
FILE * in_file;
FILE * out_file;
short in_file_arg;
short out_file_arg;
char signature[1];
t_srecord r;
int line_number = 0;
int keep_going = 1;
short n;
switch (argc) {
case 3:
in_file_arg = 1;
out_file_arg = 2;
use_32bits = 0;
break;
case 4:
if (strcmp(argv[1], "--large") == 0) {
in_file_arg = 2;
out_file_arg = 3;
use_32bits = 1;
} else {
fprintf(stderr, "Usage: srecpgx [--large] <inputfile> <outputfile>\n");
exit(5);
}
break;
default:
fprintf(stderr, "Usage: srecpgx [--large] <inputfile> <outputfile>\n");
exit(5);
}
in_file = fopen(argv[in_file_arg], "r");
if (!in_file) {
fprintf(stderr, "Could not open input file (%d).\n", errno);
exit(6);
}
out_file = fopen(argv[out_file_arg], "w");
if (!out_file) {
perror("Could not open output file");
exit(7);
}
/* Write the signature... use a lower case 'z' to distinguish */
if (use_32bits) {
signature[0] = 'z';
} else {
signature[0] = 'Z';
}
if (write(fileno(out_file), signature, 1) == -1) {
perror("Error writing the output file");
exit(9);
}
while (keep_going) {
read_record(in_file, &r);
line_number++;
switch (r.status) {
case STAT_GOOD:
n = write_record(out_file, &r);
if (n == -1) {
fprintf(stderr, "Error writing the output file on line %d", line_number);
exit(10);
} else if (n == STAT_ADDRESS_OVERFLOW) {
fprintf(stderr, "Address too big for 24-bit binary on line %d", line_number);
exit(11);
} else if (n == STAT_COUNT_OVERFLOW) {
fprintf(stderr, "Count too big for 24-bit binary on line %d", line_number);
exit(12);
}
break;
case STAT_ERROR:
/* There was an error reading the file */
fprintf(stderr, "Error reading the input file on line %d.\n", line_number);
exit(8);
case STAT_EOF:
/* End of file... stop looping, close files, and quit */
fprintf(stderr, "d");
keep_going = 0;
break;
case STAT_BAD_ADDRESS:
fprintf(stderr, "Bad address on line %d.\n", line_number);
exit(1);
case STAT_BAD_DATA:
fprintf(stderr, "Bad data on line %d.\n", line_number);
exit(2);
case STAT_BAD_CHKSUM:
fprintf(stderr, "Bad checksum on line %d.\n", line_number);
exit(3);
case STAT_MISMATCH_CHKSUM:
fprintf(stderr, "Checksum did not match on line %d.\n", line_number);
exit(4);
default:
break;
}
}
fclose(in_file);
fclose(out_file);
return 0;
}