diff --git a/.gitignore b/.gitignore index 81a36c1a..1bfaf20e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,7 @@ xen/qa7/syn/ip/** #q[ak]7.ip_user_files/** #q[ak]7.runs/** #q[ak]7.src/** +*.bin +*.hex +*.mem +*.mif diff --git a/docker/1801bm1.gpg.asc b/docker/1801bm1.gpg.asc new file mode 100644 index 00000000..b93a558b --- /dev/null +++ b/docker/1801bm1.gpg.asc @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEaK3+KhYJKwYBBAHaRw8BAQdAlb3WUnwU6PMkpx9fottfzhq/EcJ1+scevz5g +ngm58RC0JzE4MDFibTEgYXB0IGtlZXBlciA8a2VlcGVyQDE4MDFibTEuY29tPoiT +BBMWCgA7FiEEVZFkoBR03NSQ4jfUjkoohuWp6/QFAmit/ioCGwMFCwkIBwICIgIG +FQoJCAsCBBYCAwECHgcCF4AACgkQjkoohuWp6/QrBQD/Ws0rmbICdEgeeOOkxiu5 +cbU+N8YX1gs8daZN67x6R/kA/jm7gQHFK9yzj8+IksWGsoOL2ZQUAfZ4mDnqkrwI +wZEFuDgEaK3+KhIKKwYBBAGXVQEFAQEHQFAVsfc+Wp6kTr5LxFHWGlIeO8xh5SRM +W7AUuZgqO3tAAwEIB4h4BBgWCgAgFiEEVZFkoBR03NSQ4jfUjkoohuWp6/QFAmit +/ioCGwwACgkQjkoohuWp6/SocwEAir0oEEBa0ghI2pLyF581Un8pl//4wU7SJXnV +18/rB/8BAI52nWGAHIXApNOk9aMLh4vNPENbjYwWO438XhyQl8QJ +=qzaO +-----END PGP PUBLIC KEY BLOCK----- diff --git a/docker/1801bm1.list b/docker/1801bm1.list new file mode 100644 index 00000000..73fc509d --- /dev/null +++ b/docker/1801bm1.list @@ -0,0 +1 @@ +deb https://1801bm1.com/ubuntu bionic main diff --git a/docker/1801bm1.sources b/docker/1801bm1.sources new file mode 100644 index 00000000..eb1d0d64 --- /dev/null +++ b/docker/1801bm1.sources @@ -0,0 +1,5 @@ +Types: deb +URIs: https://1801bm1.com/ubuntu +Suites: noble +Components: main +Signed-By: /usr/share/keyrings/1801bm1.gpg.asc diff --git a/docker/Dockerfile.ubuntu-bionic b/docker/Dockerfile.ubuntu-bionic new file mode 100644 index 00000000..bf1c2086 --- /dev/null +++ b/docker/Dockerfile.ubuntu-bionic @@ -0,0 +1,88 @@ +FROM ubuntu:18.04 as builder +ENV DEBIAN_FRONTEND=noninteractive +ENV LANG=C.UTF-8 +ENV RT11=/usr/local/share/rt11 +RUN apt-get -q update +RUN apt-get -yq install \ + cmake gcc g++ git make jq curl unzip xz-utils \ + libpcre3-dev libedit-dev libpng-dev libpcap-dev \ + libsdl2-dev libsdl2-ttf-dev libvdeplug-dev +RUN apt-get -y dist-upgrade +RUN git clone https://github.com/nzeemin/ukncbtl-utils.git /usr/local/src/ukncbtl-utils +RUN cd /usr/local/src/ukncbtl-utils; \ + make -C rt11dsk V=1 all && install -m 755 rt11dsk/rt11dsk /usr/local/bin/ +# +WORKDIR /usr/local/src/ +# https://gunkies.org/wiki/Installing_RT-11_5.3_on_SIMH +## Get and build SIMH 4.0 +#RUN curl -LO -s https://github.com/simh/simh/archive/refs/heads/master.zip && \ +# unzip -x master.zip && ls -l +#RUN cd simh-master && make pdp11 && install -m 755 BIN/pdp11 /usr/local/bin/ +## Build macro11 tool +#RUN cd /usr/local/src && git clone https://github.com/simh/simtools.git && \ +# cd simtools/crossassemblers/macro11 && make +# Build dectape tool +ADD xen/tools/unix/dectape.c /usr/local/src/ +RUN gcc -o /usr/local/bin/dectape /usr/local/src/dectape.c +# Get and build bin2load +RUN cd /usr/local/src && \ + git clone https://github.com/jguillaumes/retroutils.git && \ + cd retroutils/bin2load && \ + make bin2load +# Runtime/worker container +FROM ubuntu:18.04 as worker +ENV DEBIAN_FRONTEND=noninteractive +ENV LANG=C.UTF-8 +ENV PATH=$PATH:/modelsim/linux:/work/xen/tools/unix +ENV RT11=/usr/local/share/rt11 +# Update APT repo metadata +RUN apt-get -y update +# Install tools and runtime libs for SIMH +RUN apt-get -yq install \ + ca-certificates \ + iverilog git make vim jq curl dos2unix srecord \ + libsdl2-2.0-0 \ + libsdl2-ttf-2.0-0 \ + libpcre3 \ + libedit2 \ + libpng16-16 \ + libpcap0.8 \ + libvdeplug2 \ + >/dev/null +# Enable i386 architecture +RUN dpkg --add-architecture i386 && apt-get -y update +# Install 32-bit libc and other useful tools +RUN apt-get -yq install libc6:i386 gcc-multilib g++-multilib \ + libxft2:i386 libxext6:i386 libncurses5:i386 +COPY --from=builder /usr/local/bin/* /usr/local/bin/ +# COPY --from=builder /usr/local/src/simh-master/BIN/pdp11 /usr/local/bin/ +# COPY --from=builder /usr/local/src/simtools/crossassemblers/macro11/macro11 /usr/local/bin/ +COPY --from=builder /usr/local/src/retroutils/bin2load/bin2load /usr/local/bin/ +# Install binutils and gcc from https://1801bm1.com/ubuntu APT repo +ADD docker/1801bm1.list /etc/apt/sources.list.d/ +ADD docker/1801bm1.gpg.asc /etc/apt/trusted.gpg.d/1801bm1.gpg.asc +RUN apt-get -y update +RUN apt-get -yq install gcc-14-pdp11-cross binutils-pdp11-cross simh +# Make RT-11 stuff +RUN mkdir -p /usr/local/share/rt11/Disks +WORKDIR $RT11 +# 1. Get an empty RL02 disk image where your installation will live. +RUN curl -LO -s http://www.dbit.com/pub/pdp11/empty/rl02.dsk.gz && \ + gunzip rl02.dsk.gz && \ + mv rl02.dsk Disks/empty-rl02.dsk +# 2. Get the Mentec software kit distributed as RL02 image +RUN curl -LO -s http://simh.trailing-edge.com/kits/rtv53swre.tar.Z +RUN zcat rtv53swre.tar.Z |tar -xvf - && rm rtv53swre.tar.Z +ADD xen/rt11/STARTF.COM . +ADD xen/rt11/HALT.MAC . +ADD xen/rt11/HALT.SAV . +ADD xen/rt11/initial.ini . +ADD xen/rt11/boot.ini . +ADD xen/rt11/pdp11-aout.ld . +ADD xen/tools/unix/init_rt11os_image.sh . +ADD xen/tools/unix/comp_mac2sav.sh /usr/local/bin/ +ADD xen/tools/unix/obj2bin.pl /usr/local/bin/ +RUN ./init_rt11os_image.sh +# Optional: Clean up to reduce image size +RUN apt-get -y clean all ; rm -rf /var/cache/apt/ /var/lib/apt/lists/* +WORKDIR /work diff --git a/docker/Dockerfile.ubuntu-noble b/docker/Dockerfile.ubuntu-noble new file mode 100644 index 00000000..6784ecc8 --- /dev/null +++ b/docker/Dockerfile.ubuntu-noble @@ -0,0 +1,86 @@ +FROM ubuntu:24.04 as builder +LABEL org.opencontainers.image.authors="yshestakov@gmail.com" +ENV DEBIAN_FRONTEND=noninteractive +ENV LANG=C.UTF-8 +ENV RT11=/usr/local/share/rt11 +RUN apt-get -q update >/dev/null +RUN apt-get -yq install \ + cmake gcc g++ git make jq curl unzip xz-utils \ + libpcre3-dev libedit-dev libpng-dev libpcap-dev \ + libsdl2-dev libsdl2-ttf-dev libvdeplug-dev \ + >/dev/null +RUN apt-get -y dist-upgrade +RUN git clone https://github.com/nzeemin/ukncbtl-utils.git /usr/local/src/ukncbtl-utils +RUN cd /usr/local/src/ukncbtl-utils; \ + make -C rt11dsk V=1 all && install -m 755 rt11dsk/rt11dsk /usr/local/bin/ +# +WORKDIR /usr/local/src/ +# https://gunkies.org/wiki/Installing_RT-11_5.3_on_SIMH +## Get and build SIMH 4.0 +#RUN curl -LO -s https://github.com/simh/simh/archive/refs/heads/master.zip && \ +# unzip -x master.zip && ls -l +#RUN cd simh-master && make pdp11 && install -m 755 BIN/pdp11 /usr/local/bin/ +## Build macro11 tool +#RUN cd /usr/local/src && git clone https://github.com/simh/simtools.git && \ +# cd simtools/crossassemblers/macro11 && make +# Build dectape tool +ADD xen/tools/unix/dectape.c /usr/local/src/ +RUN gcc -o /usr/local/bin/dectape /usr/local/src/dectape.c +# Get and build bin2load +RUN cd /usr/local/src && \ + git clone https://github.com/jguillaumes/retroutils.git && \ + cd retroutils/bin2load && \ + make bin2load +# Runtime/worker container +FROM ubuntu:24.04 as worker +LABEL org.opencontainers.image.authors="yshestakov@gmail.com" +ENV PATH=$PATH:/work/xen/tools/unix +ENV DEBIAN_FRONTEND=noninteractive +ENV LANG=C.UTF-8 +ENV RT11=/usr/local/share/rt11 +RUN apt-get -y update >/dev/null +# Install tools and runtime libs for SIMH +RUN apt-get -yq install \ + ca-certificates \ + iverilog git make vim jq curl dos2unix srecord \ + libsdl2-2.0-0 \ + libsdl2-ttf-2.0-0 \ + libpcre3 \ + libedit2 \ + libpng16-16t64 \ + libpcap0.8t64 \ + libvdeplug2t64 \ + > /dev/null +# Install binutils and gcc from https://1801bm1.com/ubuntu APT repo +ADD docker/1801bm1.sources /etc/apt/sources.list.d/ +ADD docker/1801bm1.gpg.asc /usr/share/keyrings/1801bm1.gpg.asc +RUN apt-get -y update +RUN apt-get -yq install gcc-14-pdp11-cross binutils-pdp11-cross simh + +COPY --from=builder /usr/local/bin/* /usr/local/bin/ +# COPY --from=builder /usr/local/src/simh-master/BIN/pdp11 /usr/local/bin/ +# COPY --from=builder /usr/local/src/simtools/crossassemblers/macro11/macro11 /usr/local/bin/ +# COPY --from=builder /tmp/binutils / +COPY --from=builder /usr/local/src/retroutils/bin2load/bin2load /usr/local/bin/ +RUN mkdir -p /usr/local/share/rt11/Disks +WORKDIR $RT11 +# 1. Get an empty RL02 disk image where your installation will live. +RUN curl -LO -s http://www.dbit.com/pub/pdp11/empty/rl02.dsk.gz && \ + gunzip rl02.dsk.gz && \ + mv rl02.dsk Disks/empty-rl02.dsk +# 2. Get the Mentec software kit distributed as RL02 image +RUN curl -LO -s http://simh.trailing-edge.com/kits/rtv53swre.tar.Z +RUN zcat rtv53swre.tar.Z |tar -xvf - && rm rtv53swre.tar.Z +ADD xen/rt11/STARTF.COM . +ADD xen/rt11/HALT.MAC . +ADD xen/rt11/HALT.SAV . +ADD xen/rt11/initial.ini . +ADD xen/rt11/boot.ini . +ADD xen/rt11/pdp11-aout.ld . +ADD xen/tools/unix/init_rt11os_image.sh . +ADD xen/tools/unix/comp_mac2sav.sh /usr/local/bin/ +ADD xen/tools/unix/obj2bin.pl /usr/local/bin/ +RUN ./init_rt11os_image.sh +# Optional: Clean up to reduce image size +RUN apt-get -y clean all ; rm -rf /var/cache/apt/ /var/lib/apt/lists/* +WORKDIR /work diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..5be10773 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,83 @@ +# Development env in Docker +The goal of creating such Docker container is to enable automated build and +tests running as part of CI workflow. + +# What is added to the container + +* SIMH 4.0-pre with `EXPECT/SEND` commands added, only `pdp11` binary +* `rt11dsk` tool by Zeemen to copy in/out files into RL02 disks +* `dectape` tool by by Bob Frazier to copy in/out files using "DEC tape" aka `MT0` device in RT-11 +* RT-11 v5.3 (free for hobby use in SIMH), installed OS in the `rt11os.dsk` image +* Icarus Verilog to run test benches (Ubuntu 24.04 based image). +* a few 32-bit runtime libraries to run ModelSim 10.1d from Quartus 13 (externally mounted to the container) +* GCC 4.3.0 for pdp11-aout target (C compiler only) +* bintuils 2.45: pdp11-aout-{as, ld, ar, objcopy, objdump} +* `pdp11-aout.ld` script to produce executable by RT-11 by GNU binutils +* `bin2load` tool (converts BIN to LDA format) + +# Docker image build flow + +1. Create intermedita `builder` image to compile SIMH, `dectape` and `rt11dsk` from sources +2. Create final `worker` image, copy binaries from the `builder` image +3. Download Mentec software kit (RT11 V5.3) distributed as RL02 image +4. Automated installation of RT11 according to the + [Installing RT-11 5.3 on SIMH](https://gunkies.org/wiki/Installing_RT-11_5.3_on_SIMH) document +5. Copying a few helper scripts into the image including `comp_mac2sav.sh` + +The process is scripted into `docker/build_image.sh` script and it used Podman tool. +It should work with `docker` tool as well: + +```sh +docker build -t quay.io/yshestakov/cpu11-tools-noble:latest -f docker/Dockerfile.ubuntu-noble ./ +``` + +Resulting Docker image could be fetched from +[Quay.io/yshestakov/cp11-tools](https://quay.io/repository/yshestakov/cpu11-tools?tab=tags&tag=latest) + +# Usage + +How to run the container and get interfactive shell (`docker` could be used instead of `podman`): + +``` + podman run --rm -ti \ + -v $(pwd):/work \ + quay.io/yshestakov/cpu11-tools-noble:latest \ + /bin/bash $@ +``` + + +## Compile `test.mac` for T11 as an example + + +```sh +docker/run_noble.sh -c 'cd t11/tst; comp_mac2sav.sh test.mac' +``` + +Resulting files will be stored in `t11/tst/out` relative to root of checked out `cpu11.git` repo + +## Run Icarus Verilog for T11 + +```sh +cd ~/work/cpu11/t11/hdl/syn/sim/de0 +~/work/cpu11/docker/run_noble.sh -c 'cd /work/t11/hdl/syn/sim/de0; ./run_iverilog.sh' +``` + +Need to note that `~/work/cpu11` is the directory where `cpu11.git` is checked out + + +## Run external ModelSim for T11 + +The goal is to change dir to the proper directory with `run.do` script for particular CPU11 model +and run it usinv `vsim` command in command-line mode: + +```sh +~/work/cpu11/docker/run_bionic.sh -c 'cd /work/t11/hdl/syn/sim/de0; vsim -c -onfinish exit -do "run.do" < /dev/null' +``` + +Need to note about how ModelSim is mounted to the container. +the `run_bionic.sh` script check for `MODELSIM_DIR` directory where actual ModelSim is installed on the host. +By default it uses `/tank/modelsim-q13/13.0sp1/modelsim_ase` if not defined. + +In the container `$MODELSIM_DIR` is mounted as a volume into `/modelsim` directory. +Also it is assumed `/modelsim/linux` contains ModelSim binaries including `vsim` + diff --git a/docker/build_image_bionic.sh b/docker/build_image_bionic.sh new file mode 100755 index 00000000..cbfed4b9 --- /dev/null +++ b/docker/build_image_bionic.sh @@ -0,0 +1,4 @@ +#!/bin/bash +cd $(dirname $0)/.. +IMG=quay.io/yshestakov/cpu11-tools-bionic:latest +podman build -t $IMG -f docker/Dockerfile.ubuntu-bionic . $@ diff --git a/docker/build_image_noble.sh b/docker/build_image_noble.sh new file mode 100755 index 00000000..fac0b31a --- /dev/null +++ b/docker/build_image_noble.sh @@ -0,0 +1,4 @@ +#!/bin/bash +cd $(dirname $0)/.. +IMG=quay.io/yshestakov/cpu11-tools-noble:latest +podman build -t $IMG -f docker/Dockerfile.ubuntu-noble . $@ diff --git a/docker/run_bionic.sh b/docker/run_bionic.sh new file mode 100755 index 00000000..fbc778af --- /dev/null +++ b/docker/run_bionic.sh @@ -0,0 +1,12 @@ +#!/bin/bash +ROOT=$(realpath $(dirname $0)/..) +WD=$(realpath $(pwd)) +IMG=quay.io/yshestakov/cpu11-tools-bionic:latest +MODELSIM_DIR="${MODELSIM_DIR:-/tank/modelsim-q13/13.0sp1/modelsim_ase}" +podman run --rm -ti \ + -v "$ROOT:/work" \ + -v "$MODELSIM_DIR:/modelsim" \ + -e "EXT_ROOT=$ROOT" \ + -e "WD=$WD" \ + $IMG \ + /bin/bash "$@" diff --git a/docker/run_noble.sh b/docker/run_noble.sh new file mode 100755 index 00000000..1efff184 --- /dev/null +++ b/docker/run_noble.sh @@ -0,0 +1,9 @@ +#!/bin/bash +ROOT=$(realpath $(dirname $0)/..) +WD=$(realpath $(pwd)) +podman run --rm -ti \ + -v "$ROOT:/work" \ + -e "EXT_ROOT=$ROOT" \ + -e "WD=$WD" \ + quay.io/yshestakov/cpu11-tools-noble:latest \ + /bin/bash "$@" diff --git a/t11/hdl/syn/sim/de0/iverilog.cf b/t11/hdl/syn/sim/de0/iverilog.cf new file mode 100644 index 00000000..8b4f1d58 --- /dev/null +++ b/t11/hdl/syn/sim/de0/iverilog.cf @@ -0,0 +1,14 @@ +# +timescale+1ns/100ps ++incdir+../../tbe +#vlog -vlog01compat -work work +incdir+../../tbe {../../tbe/config.v} +../../tbe/config.v +#vlog -vlog01compat -work work +incdir+../../tbe {../../tbe/de0_tb1.v} +../../tbe/de0_tb1.v +#vlog -vlog01compat -work work +incdir+../../tbe {../../../wbc/rtl/t11_plm.v} +../../../wbc/rtl/t11_plm.v +#vlog -vlog01compat -work work +incdir+../../tbe {../../rtl/t11.v} +../../rtl/t11.v +# top module: +#-s tb_t11 +# output file +#-o tb_t11.vvp diff --git a/t11/hdl/syn/sim/de0/run_iverilog.sh b/t11/hdl/syn/sim/de0/run_iverilog.sh new file mode 100755 index 00000000..c05aee00 --- /dev/null +++ b/t11/hdl/syn/sim/de0/run_iverilog.sh @@ -0,0 +1,3 @@ +#!/bin/bash -ex +iverilog -c iverilog.cf -o tb_t11.vvp -s tb_t11 +vvp -n -v ./tb_t11.vvp diff --git a/xen/rt11/HALT.MAC b/xen/rt11/HALT.MAC new file mode 100644 index 00000000..29ad9046 --- /dev/null +++ b/xen/rt11/HALT.MAC @@ -0,0 +1,12 @@ + .TITLE HALT-ME + .MCALL .TTYOUT,.EXIT +PROG:: MOV #MSG,R1 ;STARTING ADDRESS OF STRING +1$: MOVB (R1)+,R0 ;FETCH NEXT CHARACTER + BEQ DONE ;IF ZERO, EXIT LOOP + .TTYOUT ;OTHERWISE PRINT IT + BR 1$ ;REPEAT LOOP +DONE: HALT + .EXIT + +MSG: .ASCIZ /HALT SYSTEM / + .END PROG diff --git a/xen/rt11/HALT.SAV b/xen/rt11/HALT.SAV new file mode 100644 index 00000000..c7b7f8fe Binary files /dev/null and b/xen/rt11/HALT.SAV differ diff --git a/xen/rt11/STARTF.COM b/xen/rt11/STARTF.COM new file mode 100644 index 00000000..a6f305c8 --- /dev/null +++ b/xen/rt11/STARTF.COM @@ -0,0 +1 @@ +set error none diff --git a/xen/rt11/boot.ini b/xen/rt11/boot.ini new file mode 100644 index 00000000..87a7d2e2 --- /dev/null +++ b/xen/rt11/boot.ini @@ -0,0 +1,14 @@ +set cpu 11/23+ 256K noidle +set throttle 10% +set console wru=035 +set tto 8b +attach LPT lpt.txt +set rl0 writeenabled +set rl0 rl02 +attach rl0 -n rt11os.dsk +set rl1 writeenabled +set rl1 rl02 +attach rl1 -n work.dsk +; set rl1 badblock +boot rl0 +exit diff --git a/xen/rt11/initial.ini b/xen/rt11/initial.ini new file mode 100644 index 00000000..231ebe9d --- /dev/null +++ b/xen/rt11/initial.ini @@ -0,0 +1,25 @@ +set cpu 11/23+ 256K noidle +set throttle 10% +set console wru=035 +set tto 8b +attach LPT lpt.txt +set rl0 writeenabled +set rl0 rl02 +attach rl0 -n distribution.dsk +set rl1 writeenabled +set rl1 rl02 +attach rl1 -n distribution-backup.dsk +; set rl1 badblock +;-- +EXPECT "key when ready to continue." SEND "\r"; GO +EXPECT "Type YES or NO and press" SEND "YES\r"; GO +EXPECT "key when ready to continue." SEND "\r"; GO +EXPECT "Type in the date" SEND "01-JAN-91\r"; GO +EXPECT "key when you have mounted the disk" SEND "\r"; GO +EXPECT "key when you have removed the disk." SEND "\r"; DETACH rl1; GO +EXPECT "key when you have mounted the disk" SEND "\r"; ATTACH rl1 -n rt11os.dsk ; GO +EXPECT "Your working system disk will now be bootstrapped." SLEEP 1; SEND "\r"; GO +EXPECT "V5NOTE.TXT can be TYPED or PRINTED." DETACH rl0; ATTACH rl0 -n empty.dsk; SEND "INIT/NOQUERY DL0:\r" ; SEND "DIR DL0:\r"; SLEEP 1; GO +EXPECT "0 Files, 0 Blocks" ; ECHO [Going-to-Exit-Soon]; EXIT; GO +BOOT rl0 +EXIT diff --git a/xen/rt11/pdp11-aout.ld b/xen/rt11/pdp11-aout.ld new file mode 100644 index 00000000..bbc34de4 --- /dev/null +++ b/xen/rt11/pdp11-aout.ld @@ -0,0 +1,26 @@ +OUTPUT_FORMAT("a.out-pdp11") +ENTRY(start) +phys = 00001000; +SECTIONS +{ + .text phys : AT(phys) { + code = .; + crt0.o(.text) + *(.text) + *(.rodata) + . = ALIGN(0100); + } + .data : AT(phys + (data - code)) + { + data = .; + *(.data) + . = ALIGN(0100); + } + .bss : AT(phys + (bss - code)) + { + bss = .; + *(.bss) + . = ALIGN(0100); + } + end = .; +} diff --git a/xen/tools/unix/comp_mac2sav.sh b/xen/tools/unix/comp_mac2sav.sh new file mode 100755 index 00000000..ef1fa39a --- /dev/null +++ b/xen/tools/unix/comp_mac2sav.sh @@ -0,0 +1,94 @@ +#!/bin/bash -e +WORKDSK=/dev/shm/work.dsk +OSDSK=/dev/shm/rt11os.dsk +TAPE=$(mktemp -t XXXX.tap) +######################### +if [ $# -eq 0 ] ; then + echo "Usage: $0 file.mac" >&2 + exit 1 +fi +FN="$1" +shift +if [ ! -e "$FN" ] ; then + echo "$FN: doesn't exist" >&2 + exit 2 +fi +tmpini=$(mktemp -t XXXXXX.ini) +function atexit() +{ + test -e "$tmpini" && rm -f "$tmpini" + test -e "$TAPE" && rm -f "$TAPE" +} +trap atexit EXIT TERM INT +# +LOCAL_FN=$(realpath "$FN") +LOCAL_DIR=$(dirname "$LOCAL_FN") +LOCAL_OUT_DIR=$LOCAL_DIR/out +mkdir -p $LOCAL_OUT_DIR +# echo "LOCAL_FN is $LOCAL_FN" +NN=$(basename ${FN%.*}) + +# Create DEC tape with source +dectape -Io $TAPE +dectape -o "$LOCAL_DIR" $TAPE +### Create STARTF.COM to be executed on start of RT-11 +cat < /dev/shm/STARTF.COM +SET ERROR NONE +DATE $(date +%d-%b-95 |tr a-z A-Z) +COPY MT0:*.* DL1: +MACRO DL1:$NN.MAC /list:DL1:$NN.LST /object:DL1:$NN.OBJ +LINK DL1:$NN.OBJ /EXE:DL1:$NN.SAV +LINK DL1:$NN.OBJ /EXE:DL1:$NN.LDA /LDA +COPY DL1:$NN.* MT0: +R HALT +EOF +#PRINT DL1:$NN.LST +# COPY DL1:$NN.LDA PC: +### ^^^ +RT11=${RT11:-/usr/local/share/rt11} +# copy images to /dev/shm/ +cp "$RT11/Disks/empty-formatted.dsk" "$WORKDSK" +cp $RT11/rt11os.dsk "$OSDSK" +# Add files to rt11dsk and work +unix2dos /dev/shm/STARTF.COM >/dev/null +# rt11dsk a "$WORKDSK" $LOCAL_FN >/dev/null +rt11dsk d "$OSDSK" STARTF.COM >/dev/null +rt11dsk a "$OSDSK" /dev/shm/STARTF.COM >/dev/null + +# Create INI file to run SIMH / pdp11 +cat <$tmpini +set cpu 11/23+ 256K noidle +set throttle 10% +set console wru=035 +set tto 8b +set rl1 writeenabled +set rl1 rl02 +ATTACH rl1 -n $WORKDSK +set rl0 writeenabled +set rl0 rl02 +ATTACH rl0 -n $OSDSK +; EXPECT "MACRO-E-Errors detected:" SEND "PRINT $NN.lst\r"; GO +ATTACH TM0 $TAPE +BOOT rl0 +DETACH TM0 +EXIT +EOF +# Run SIMH / pdp11 +pdp11 $tmpini +# Copy out results +dectape -o $TAPE $LOCAL_OUT_DIR +touch $LOCAL_OUT_DIR/*.* +# exit with 0 (true) of no errors found, else 1 (false) +grep -q "MACRO-E-Errors" $LOCAL_OUT_DIR/*.LST && exit 1 +if egrep -Eq "vm3|f11" $LOCAL_FN ; then + SZ=0x8000 +else + SZ=0x4000 +fi +ls -l $LOCAL_OUT_DIR +base_fn="$LOCAL_OUT_DIR/$NN" +srec_cat $LOCAL_OUT_DIR/$(tr a-z A-Z <<<$NN).LDA -dec_binary -o ${base_fn}.bin -binary +srec_cat ${base_fn}.bin -binary -fill 0x00 0x0000 $SZ -byte-swap 2 -o ${base_fn}.mem --VMem 16 +srec_cat ${base_fn}.bin -binary -fill 0x00 0x0000 $SZ -byte-swap 2 -o ${base_fn}.hex -Intel +srec_cat ${base_fn}.bin -binary -fill 0x00 0x0000 $SZ -byte-swap 2 -o ${base_fn}.mif -Memory_Initialization_File 16 -obs=2 + diff --git a/xen/tools/unix/dectape.c b/xen/tools/unix/dectape.c new file mode 100644 index 00000000..5c6272a2 --- /dev/null +++ b/xen/tools/unix/dectape.c @@ -0,0 +1,2005 @@ +// // +// _ _ // +// __| | ___ ___ | |_ __ _ _ __ ___ ___ // +// / _` | / _ \ / __|| __|/ _` || '_ \ / _ \ / __| // +// | (_| || __/| (__ | |_| (_| || |_) || __/ _| (__ // +// \__,_| \___| \___| \__|\__,_|| .__/ \___|(_)\___| // +// |_| // +// // +////////////////////////////////////////////////////////////////////////////// +// // +// Copyright (c) 2019 by Bob Frazier and S.F.T. Inc. // +// Use, copying, and distribution of this software are licensed according // +// to the GPLv2, LGPLv2, or MIT-like license, as appropriate. See the // +// included 'LICENSE' and 'COPYING' files for more information. // +// // +////////////////////////////////////////////////////////////////////////////// + + +// this program reads/writes FSM formatted magtape data + +#include +#include +#include +#include /* stdarg to make sure I have va_list and other important stuff */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +typedef union _RT11_VOL_HEADER_ +{ + char buf[512]; // raw buffer + + struct + { + char label_identifier[3]; // VOL + char label_number; // typically '1' + char volume_identifier[6]; // volume name, 'RT11A ' typical, pad with ' ' + char accessibility; // ' ' + char reserved26[26]; // 26 spaces + char owner_identifier[3]; // 'D%B' means tape written by DEC PDP-11 + char owner_name[10]; // typically 10 spaces + uint8_t DEC_standard_version; // 1 + char reserved28[28]; // 28 spaces + uint8_t label_standard_version; // 3 + + // rest of it is garbage + } __attribute__((__packed__)); + +} RT11_VOL_HEADER; + +#define SIZEOF_STRUCT_MEMBER(X,Y) sizeof(((X *)(0))->Y) + +typedef union _RT11_FILE_HEADER_ +{ + char buf[512]; // raw buffer + + struct + { + char label_identifier[3]; // HDR + char label_number; // typically '1' + char file_identifier[17]; // 6.3 file name, left justified, padded with ' ' + char file_set_identifier[6]; // 'RT11A ' + char file_section_number[4]; // '0001' + char file_sequence_number[4];// '0001' for first file, sequences upward + char generation_number[4]; // '0001' + char generation_version[2]; // '00' + char creation_date[6]; // ' YYddd' where YY is year (90) and 'ddd' is day in year (032) for example 2/1/1990 + char expiration_date[6]; // ' 00000' indicates an expired file + char accessibility; // ' ' + char block_count[6]; // '000000' + char system_code[13]; // 'DECRT11A ' + char reserved7[7]; // 7 spaces + + // rest of it is garbage + } __attribute__((__packed__)); + +} RT11_FILE_HEADER; + +typedef union _RT11_FILE_EOF_ +{ + char buf[512]; // raw buffer + + struct + { + char label_identifier[3]; // EOF + char label_number; // typically '1' + char file_identifier[17]; // 6.3 file name, left justified, padded with ' ' + char file_set_identifier[6]; // 'RT11A ' + char file_section_number[4]; // '0001' + char file_sequence_number[4];// '0001' for first file, sequences upward + char generation_number[4]; // '0001' + char generation_version[2]; // '00' + char creation_date[6]; // ' YYddd' where YY is year (90) and 'ddd' is day in year (032) for example 2/1/1990 + char expiration_date[6]; // ' 00000' indicates an expired file + char accessibility; // ' ' + char block_count[6]; // 'nnnnnn' = # of 512 byte data blocks since the HDR record (0 if SPFUN used) + char system_code[13]; // 'DECRT11A ' + char reserved7[7]; // 7 spaces + + // rest of it is garbage + } __attribute__((__packed__)); + +} RT11_FILE_EOF; + +// TAPE FORMAT: +// +// * MARKER * +// VOLUME HEADER +// * MARKER* +// +// * MARKER * +// first file header +// * MARKER * +// +// \x00\x00\x00\x00 <-- data marker (?) +// +// * MARKER * +// data block (512 bytes) +// * MARKER * +// +// * MARKER * +// data block (512 bytes) +// * MARKER * +// +// ... (tail end of file is padded with zero bytes) +// +// \x00\x00\x00\x00 <-- data marker (?) +// +// * MARKER * +// first file EOF +// * MARKER * +// +// \x00\x00\x00\x00 <-- data marker (?) +// +// (RINSE AND REPEAT FILE SEQUENCE FOR NEXT FILE) +// +// \x00\x00\x00\x00 <-- data marker (?) +// +// more zero bytes (end of tape) + + + + +uint8_t DATA_MARKER[4]={0, 0, 0, 0}; +uint8_t TAPE_MARKER[4]={0, 2, 0, 0}; + +#define DEBUG_OUTPUT_WARN (iVerbosity > 0) +#define DEBUG_OUTPUT_INFO (iVerbosity > 1) +#define DEBUG_OUTPUT_CHATTY (iVerbosity > 2) +#define DEBUG_OUTPUT_VERBOSE (iVerbosity > 3) + +int iVerbosity = 0; // debug output + +int QueryYesNo(const char *szMessage); // returns non-zero for yes, zero for no + +int read_the_tape(FILE *pTape, const char *szTapeFileName, const char *pOutPath, + int bDirectory, int bOverwrite, int bConfirm, int bValidate, int bFindEndOfTape, int *piSeq); +int read_tape_block(FILE *pTape, void *pHeader); // always 512 byte blocks plus leading/trailing 'TAPE_MARKER' +int write_tape_block(FILE *pTape, void *pHeader); // always 512 byte blocks plus leading/trailing 'TAPE_MARKER' +int write_single_file_to_tape(FILE *pTape, const char *szFileName, int *pnFileSeqNum); +int write_the_tape(FILE *pTape, const char *pTapeFileName, const char *pInputName, int bAppend, int iDriveSize, const char *szLabel); + +int days_since_year_start(int iYear, int iMonth, int iDay); +void mdy_from_days_since_year_start(int iYear, int nDays, int *piMonth, int *piDay); +void rt11_date(const char *pDate, int *piYear, int *piDayInYear); +const char *rt11_date_string(const char *pDate); +void format_rt11_file_name(const char *szSourceFile, char *pBuf, int cbBuf); + +int do_initialize_tape(FILE *pTape, const char *szFileName, int iDriveSize, const char *pLabel); +int initialize_tape(const char *szFileName, int iDriveSize, int bOverwrite, const char *pLabel); + +// file utilities (some are derived code from 'ForkMe') +int FileExists(const char *szFileName); +int IsDirectory(const char *szFileName); +int get_file_RT11_date_time(const char *szFileName, int *pnRTYear, int *pnRTDay); +int set_file_RT11_date_time(const char *szFileName, int nRTYear, int nRTDay); +void *WBAllocDirectoryList(const char *szDirSpec); +void WBDestroyDirectoryList(void *pDirectoryList); +int WBNextDirectoryEntry(void *pDirectoryList, char *szNameReturn, int cbNameReturn, unsigned long *pdwModeAttrReturn); + + + +void usage() +{ + fputs("USAGE:\n" + " dectape -h\n" + " dectape [-v] tapefile\n" + " dectape tapefile directory\n" + " dectape directory tapefile\n" + " where\n" + " tapefile a file that is (or will be) attached to a TMx device\n" + " directory a directory to/from which to write files\n" + " -h prints this 'usage' message\n" + " -v Verbosity level (multiple -v to increase it)\n" + " -n do not overwrite existing files (the default)\n" + " -o DO overwrite existing files\n" + " -q Do not prompt to overwrite files or create a directory\n" + " -V validates a tape (rather than printing the directory)\n" + " -A append to the tape, rather than overwriting\n" + " (this can put duplicate file names on the tape)\n" + " -I Initialize a new tape file\n" + " -S Specify the size for a new tape file (in MB)\n" + " -L Specify the label for a new tape file\n" + "\n" + "To list the file directory of a tape, use\n" + " dectape tapefile\n" + "\n" + "To copy the tape files to a directory, use\n" + " dectape tapefile directory\n" + "\n" + "To copy a directory to a tape file, use\n" + " dectape directory tapefile\n" + "\n" + "This program is supposed to be simple. No complaints.\n\n", + stderr); +} + +static char szTapeLabel[SIZEOF_STRUCT_MEMBER(RT11_VOL_HEADER,owner_name)] = + { 'd','e','c','t','a','p','e',' ',' ',' ' }; + +int main(int argc, char *argv[]) +{ +FILE *pTape = NULL; +char *p1, *pEnd; +const char *p1C; +int i1, iRval; +int bDirectory = 1; +int bOverwrite = 0; +int bAppend = 0; +int bConfirm = 1; +int bValidate = 0; +int bInitialize = 0; +int iDriveSize = 32; + + + // demo - do a directory of the tape + + // get options using 'getopt' so it behaves like every OTHER 'POSIX' utility + while((i1 = getopt(argc, argv, "hvVqonAIS:L:")) + != -1) + { + switch(i1) + { + case 'h': + usage(); + exit(1); + + case 'v': + iVerbosity++; + break; + + case 'V': + bValidate = 1; + break; + + case 'q': + bConfirm = 0; + break; + + case 'n': + bOverwrite = 0; + break; + + case 'o': + bOverwrite = 1; + break; + + case 'A': + bAppend = 1; + break; + + case 'I': + bInitialize = 1; + break; + + case 'S': + iDriveSize = atoi(optarg); + break; + + case 'L': + pEnd = &(szTapeLabel[sizeof(szTapeLabel)]); + + for(p1C=optarg, p1=szTapeLabel; *p1C && p1 < pEnd; ) + { + *(p1++) = *(p1C++); + } + + // pad with spaces + while(p1 < pEnd) + { + *(p1++) = ' '; + } + + break; + } + } + + argc -= optind; + argv += optind; + + if(argc < 1) + { + usage(); + exit(1); + } + + // see if one is a file, and one is a directory + // if output file, make sure it does not exist unless 'overwrite' + // if output dir, should it be empty?? + + if(argc >= 2) + { + if(bInitialize) + { + fprintf(stderr, "Initialize does not take 2 parameters\n"); + usage(); + exit(1); + } + + if(IsDirectory(argv[1]) && !IsDirectory(argv[0])) + { + bDirectory = 0; // a flag for later on + } + else if(!IsDirectory(argv[1]) && IsDirectory(argv[0])) + { + if(FileExists(argv[1]) && !bOverwrite && !bAppend && !QueryYesNo("Overwrite tape file")) + { + exit(0); // don't do it, but not an error either + } + + if(bAppend && FileExists(argv[1])) + { + pTape = fopen(argv[1], "r+"); // open for read/write access + } + else + { + bAppend = 0; // because the file does not exist + pTape = fopen(argv[1], "w"); // open for write access (destroying old one) + } + + if(!pTape) + { + fprintf(stderr, "Unable to open tape file \"%s\"\n", argv[1]); + exit(2); + } + + iRval = write_the_tape(pTape, argv[1], argv[0], bAppend, iDriveSize, szTapeLabel); + + goto exit_point; + } + else + { + fputs("ERROR - file-to-file operations not (yet) supported\n", stderr); + usage(); + exit(1); + } + } + + if(bInitialize) + { + // TODO: warn if unnecessary params specified?? + + return initialize_tape(argv[0], iDriveSize, bOverwrite, szTapeLabel); + } + + // LIST TAPE DIRECTORY, VALIDATE, OR COPY TAPE TO DIRECTORY + + pTape = fopen(argv[0], "r"); + if(!pTape) + { + fprintf(stderr, "Unable to open tape file \"%s\"\n", argv[0]); + exit(2); + } + + iRval = read_the_tape(pTape, argv[0], (const char *)(bDirectory ? NULL : argv[1]), + bDirectory, bOverwrite, bConfirm, bValidate, 0, NULL); + +exit_point: + fclose(pTape); + return iRval; +} + +int QueryYesNo(const char *szMessage) +{ +char tbuf[256]; +int i1; + + while(!feof(stdin) && !ferror(stdin)) + { + fprintf(stderr, "%s (y/n)?", szMessage); + fgets(tbuf, sizeof(tbuf), stdin); + + // trim any trailing white space + while((i1 = strlen(tbuf)) > 0 && tbuf[i1 - 1] <= ' ') + tbuf[i1 - 1] = 0; + + // trim any lead white space + while((i1 = strlen(tbuf)) > 0 && tbuf[0] <= ' ') + { + if(i1 > 1) + memmove(tbuf, tbuf + 1, i1 - 1); + + tbuf[i1 - 1] = 0; + } + + if(tbuf[0] == 'Y' || tbuf[0] == 'y') + return 1; + + if(tbuf[0] == 'n' || tbuf[0] == 'N') + return 0; + + fprintf(stderr, "Please respond with 'Y' or 'N'\n"); + } + + return 0; // for now (probably won't get here) +} + + + +////////////////////////////////////////////////////////////////////////////// +// // +// _____ ___ __ ___ // +// |_ _|__ _ _ __ ___ |_ _| / // _ \ // +// | | / _` || '_ \ / _ \ | | / /| | | | // +// | || (_| || |_) || __/ | | / / | |_| | // +// |_| \__,_|| .__/ \___| |___|/_/ \___/ // +// |_| // +// // +////////////////////////////////////////////////////////////////////////////// + + +int read_tape_block(FILE *pTape, void *pBlock) +{ +uint8_t marker[4]; // where to read the markers into + + if(!pTape || feof(pTape)) + return -1; + + if(fread(marker, sizeof(marker), 1, pTape) != 1) + return -2; + + if(!memcmp(marker, DATA_MARKER, sizeof(marker))) // end of tape + return 1; // this is acceptable, but "an error" if I don't expect it + + if(memcmp(marker, TAPE_MARKER, sizeof(marker))) + return -3; // missing tape marker + + if(feof(pTape) || fread(pBlock, 512, 1, pTape) != 1) + return -4; + + if(feof(pTape) || fread(marker, sizeof(marker), 1, pTape) != 1) + return -5; + + if(memcmp(marker, TAPE_MARKER, sizeof(marker))) + return -6; + + return 0; // OK! +} + +int write_tape_block(FILE *pTape, void *pBlock) +{ +size_t lPos; + + if(!pTape) + return -1; + + if(fwrite(TAPE_MARKER, 4, 1, pTape) != 1) // write tape marker + return -2; + + if(fwrite(pBlock, 512, 1, pTape) != 1) + return -3; + + if(fwrite(TAPE_MARKER, 4, 1, pTape) != 1) + return -4; + + lPos = ftell(pTape); // where am I right now? + + if(fwrite(DATA_MARKER, 4, 1, pTape) != 1 || // write two data markers here to mark EOT + fwrite(DATA_MARKER, 4, 1, pTape) != 1) + return -5; + + if(fseek(pTape, lPos, SEEK_SET) < 0) // re-position just before data marker + return -6; + + return 0; // OK! +} + +char *make_output_file_name(const char *pOutPath, const char *pFileIdentifier) +{ +char *pRval; +int i1; +const char *p1, *p2, *pEnd; + + pRval = (char *)malloc(PATH_MAX * 2 + 256); + if(!pRval) + return NULL; + + strncpy(pRval, pOutPath, PATH_MAX); + i1 = strlen(pRval); + + if(i1 > 0 && pRval[i1] != '/') + strcat(pRval, "/"); + + i1 = strlen(pRval); + + p1 = pFileIdentifier; // up to 17 + + pEnd = p1 + 17; + + while(*p1 && *p1 > ' ') + p1++; + + p2 = p1; + while(*p2 && p2 < pEnd && *p2 != '.') + p2++; + + if(p1 > pFileIdentifier) + { + memcpy(pRval + i1, pFileIdentifier, p1 - pFileIdentifier); + i1 += p1 - pFileIdentifier; + } + if(p2 < pEnd && *p2 == '.') + { + p1 = p2++; + while(*p2 > ' ' && p2 < pEnd) + p2++; + + if((p2 - p1) > 4) + { + memcpy(pRval + i1, p1, 4); + i1 += 4; + } + else + { + memcpy(pRval + i1, p1, p2 - p1); + i1 += p2 - p1; + } + } + + pRval[i1] = 0; + + return pRval; +} + +FILE *do_open_output_file(const char *pOutPath, const char *pFileIdentifier, int bOverwrite, int bConfirm) +{ +char *pFileName; +FILE *pRval; + + + pFileName = make_output_file_name(pOutPath, pFileIdentifier); + if(!pFileName) + return NULL; + + if(FileExists(pFileName) && !bOverwrite) + { + if(bConfirm) + { + char tbuf[4096]; + snprintf(tbuf, sizeof(tbuf), "Overwrite \"%s\"", pFileName); + + if(!QueryYesNo(tbuf)) + { + free(pFileName); + return NULL; + } + } + + if(truncate(pFileName, 0)) // zero bytes long + { + fprintf(stderr, "Unable to write (truncate) to \"%s\", errno=%d (%xH)\n", + pFileName, errno, errno); + + free(pFileName); + return NULL; + } + } + + pRval = fopen(pFileName, "w"); + + free(pFileName); + return pRval; +} + +int do_set_output_file_date_time(const char *pOutPath, const char *pFileIdentifier, + const char * pCreationDate) +{ +int iRval; +char *pFileName; +int iYear, iDay; + + pFileName = make_output_file_name(pOutPath, pFileIdentifier); + if(!pFileName) + return -1; + + rt11_date(pCreationDate, &iYear, &iDay); + iRval = set_file_RT11_date_time(pFileName, iYear, iDay); + + free(pFileName); + return iRval; +} + + +int read_the_tape(FILE *pTape, const char *szTapeFileName, const char *pOutPath, + int bDirectory, int bOverwrite, int bConfirm, int bValidate, int bFindEndOfTape, int *piSeq) +{ +int iRval = -1, i1, nBlocks, iSeq, nBytesInLastBlock; +int bFoundTapeHeader = 0; +size_t lZeroedZZZ = 0L; +size_t lPos; +FILE *pOutFile; +RT11_VOL_HEADER vol; +RT11_FILE_HEADER file; +RT11_FILE_EOF eof; +uint8_t data[512]; // file data +uint8_t marker[4]; // where to read the markers into +char tbuf[512]; + + + iSeq = 0; + + if(piSeq) + *piSeq = 0; + + fseek(pTape, 0, SEEK_SET); // this function only reads from the beginning of the tape + + i1 = read_tape_block(pTape, &vol); // read volume info + + if(i1 < 0) + { + fprintf(stderr, "Unable to read header on tape file \"%s\"\n", szTapeFileName); + return -9; + } + + if(i1 == 1) // empty tape + { + if(bFindEndOfTape) + { + fseek(pTape, 0, SEEK_SET); + return 1; // to indicate 'empty tape' + } + + // the tape is empty but I still want to output all of the things + // that I normally would before I exit + + if(bValidate) + { + fputs("** NO VOLUME HEADER **\n", stdout); + } + + if(bDirectory && !bValidate) + { + fputs("\nEND OF TAPE\n\n", stdout); + } + + if(bValidate) + { + fputs("** TAPE VALIDATED **\n", stdout); + } + + return 0; + } + + if(!memcmp(vol.label_identifier, "HDR", 3)) + { + memcpy(&file, &vol, sizeof(file)); + + if(bValidate) + { + fputs("** NO VOLUME HEADER **\n", stdout); + } + + bFoundTapeHeader = 0; + goto do_file; + } + + if(memcmp(vol.label_identifier, "VOL", 3) || vol.label_number != '1') + { + // TODO: try again and see if this was a boot block? + // normally I wouldn't want to rely on a boot block without a volume header... + + fprintf(stderr, "Bad volume header - \"%-3.3s\" '%c'\n", + vol.label_identifier, vol.label_number); + + return -8; + } + + bFoundTapeHeader = 1; + + if((bDirectory || bValidate) && !bFindEndOfTape) + { + printf("RT11 TAPE '%-3.3s' '%-10.10s' V%c Label V%c\n", + vol.owner_identifier, vol.owner_name, + vol.DEC_standard_version, vol.label_standard_version); + } + + iSeq = -1; // as a flag, first time around + + while(!feof(pTape)) + { + lPos = ftell(pTape); // position of the current header + + i1 = read_tape_block(pTape, &file); // read file directory 'HDR' info + + if(i1 > 0) + { + if(bDirectory && !bValidate && !bFindEndOfTape) + { + fputs("\nEND OF TAPE\n\n", stdout); + } + + if(bFindEndOfTape) + { + // re-position file pointer back 4 bytes + fseek(pTape, ftell(pTape) - 4, SEEK_SET); + } + + if(iSeq < 0) // first time through? + iSeq = 0; // fix it back + + break; + } + else if(i1 < 0) + { + fprintf(stderr, "Unable to read file header at position %ld\n", (long)ftell(pTape) - 4); + + return -10; + } + else if(memcmp(file.label_identifier, "HDR", 3) || file.label_number != '1') + { + if(iSeq < 0 && memcmp(file.label_identifier, "HDR", 3)) // is it a boot block? + { + // for now assume this is a boot block + + if(bDirectory && !bFindEndOfTape) + { + fputs(" *BOOT BLOCK DETECTED*\n", stdout); + } + + iSeq = 0; + + continue; // read it again + } + else + { + fprintf(stderr, "Invalid file header at position %ld - \"%-3.3s\" '%c'\n", + (long)ftell(pTape) - 4, + file.label_identifier, file.label_number); + } + + return -10; + } + + if(iSeq < 0) // first time through only, as a flag + iSeq = 0; + +do_file: + iSeq++; + + if(piSeq) + *piSeq = iSeq; + + if(DEBUG_OUTPUT_CHATTY) + fprintf(stderr, "*INFO* - iSeq = %d\n", iSeq); + + memset(tbuf, 0, sizeof(tbuf)); + memcpy(tbuf, file.file_sequence_number, sizeof(file.file_sequence_number)); + + lZeroedZZZ = 0L; + + if(iSeq == 1 && atoi(tbuf) == 0) // special case, 'ZEROED.ZZZ' file + { + if(!bFindEndOfTape) + fputs(" ** FOUND ZEROED.ZZZ **\n", stdout); + + if(DEBUG_OUTPUT_CHATTY) + fprintf(stderr, "*INFO* - ZEROED.ZZZ - iSeq = %d\n", iSeq); + + lZeroedZZZ = lPos; // keep track of where the HDR1 for ZEROED.ZZZ began + + iSeq = 0; // reset the sequence number to zero + + if(piSeq) // in case I return early + *piSeq = iSeq; + + // there should be a block of 8 zero bytes, followed by the EOF + } + else if(iSeq != atoi(tbuf) && !bFindEndOfTape) + { + fprintf(stderr, "WARNING: Invalid file seq number in header - %d vs %d\n", + iSeq, atoi(tbuf)); + } + else if(iSeq == 1 && bDirectory && !bValidate && !bFindEndOfTape) + { + fputs(" FILE NAME CREATE DATE BLOCKS TOTAL BYTES\n" + " ================ =========== ====== ===========\n", stdout); + } + + if(fread(marker, sizeof(marker), 1, pTape) != 1 || + memcmp(marker, DATA_MARKER, sizeof(marker))) // need a marker here + { + fprintf(stderr, "missing data marker at position %ld, file \"%-17.17s\"\n", + (long)ftell(pTape) - 4, + file.file_identifier); + + return -11; + } + + if(lZeroedZZZ && // there should be one more data marker + (fread(marker, sizeof(marker), 1, pTape) != 1 || + memcmp(marker, DATA_MARKER, sizeof(marker)))) // need a marker here + { + fprintf(stderr, "missing 2nd data marker at position %ld, file \"%-17.17s\"\n", + (long)ftell(pTape) - 4, + file.file_identifier); + + return -11; + } + + if(pOutPath && !lZeroedZZZ) + { + pOutFile = do_open_output_file(pOutPath, file.file_identifier, bOverwrite, bConfirm); + + if(!pOutFile) + { + fprintf(stderr, "Unable to open \"%s/%-17.17s\" - errno=%d (%xH)\n", + pOutPath, file.file_identifier, errno, errno); + } + } + else + { + pOutFile = NULL; // a flag + } + + nBlocks = 0; + nBytesInLastBlock = 512; // initially + + while(!lZeroedZZZ && !feof(pTape)) + { + lPos = ftell(pTape); // current position + + i1 = read_tape_block(pTape, data); // read file data block + // see if it's an EOF header. Yes, this is a LAME way of doing it, but that's + // the way this thing works. + + if(i1 > 0) // a read error (TODO: should >0 be used instead of 'above' sequence? + { + break; // end of file + } + else if(i1 < 0) + { + fprintf(stderr, "read error at position %ld, file \"%-17.17s\"\n", + (long)lPos, file.file_identifier); + + return -7; + } + + if(pOutFile) + { + if(fwrite(data, sizeof(data), 1, pOutFile) != 1) + { + fprintf(stderr, "ERROR - unable to write to \"%s/%-17.17s\" - errno=%d (%xH)\n", + pOutPath, file.file_identifier, errno, errno); + + // TODO: do I quit? just flag the error?? + } + + // figure out which byte is the last one without a 0 in it + for(nBytesInLastBlock=512; nBytesInLastBlock > 0; nBytesInLastBlock--) + { + if(data[nBytesInLastBlock - 1] != 0) + break; + } + } + + nBlocks++; + } + + if(pOutFile) + { + fflush(pOutFile); // make sure I write it all first... + + if(nBlocks > 0 && nBytesInLastBlock < 512) + { + if(DEBUG_OUTPUT_CHATTY) + fprintf(stderr, "truncating file \"%s/%-17.17s\" to %ld bytes\n", + pOutPath, file.file_identifier, (nBlocks - 1) * 512L + nBytesInLastBlock); + + // set file length to match the last block minus trailing 0 bytes + ftruncate(fileno(pOutFile), (nBlocks - 1) * 512L + nBytesInLastBlock); + } + + fclose(pOutFile); + pOutFile = NULL; + + do_set_output_file_date_time(pOutPath, file.file_identifier, file.creation_date); + } + + if(!lZeroedZZZ && bDirectory && !bValidate && !bFindEndOfTape) + { + // directory output + printf(" %-17.17s %-9.9s %6d %11ld\n", + file.file_identifier, + rt11_date_string(file.creation_date), + nBlocks, (long)(nBlocks * 512)); + } + + i1 = read_tape_block(pTape, &eof); // read file EOF block + + if(i1 > 0) + { + printf("unexpected (missing EOF record)\nEND OF TAPE\n\n"); + + if(lZeroedZZZ && bFindEndOfTape) + fseek(pTape, lPos, SEEK_SET); + + return 1; + } + else if(i1 < 0 || memcmp(eof.label_identifier, "EOF", 3) || eof.label_number != '1') + { + if(i1 < 0) + fprintf(stderr, "Unable to read EOF header at position %ld\n", lPos); + else + fprintf(stderr, "Invalid EOF header at position %ld - \"%-3.3s\" '%c'\n", + lPos, eof.label_identifier, eof.label_number); + + return -12; + } + + memset(tbuf, 0, sizeof(tbuf)); + memcpy(tbuf, eof.block_count, sizeof(eof.block_count)); // make sure tbuf is big enough + + if(memcmp(eof.file_identifier, file.file_identifier, sizeof(eof.file_identifier)) || + atoi(tbuf) != nBlocks) + { + if(!bFindEndOfTape) + printf(" *EOF HEADER MISMATCH* \"%-17.17s\" %s\n", + eof.file_identifier, tbuf); + } + + // there should be a data marker now + + if(fread(marker, sizeof(marker), 1, pTape) != 1) + { + fprintf(stderr, "missing data/tape marker at position %ld, file \"%-17.17s\"\n", + (long)ftell(pTape) - 4, + file.file_identifier); + + return -13; + } + + if(memcmp(marker, DATA_MARKER, sizeof(marker))) // data marker means end of file should be next + { + printf("missing data marker at end of EOF record\n"); + + return -14; + } + + if(lZeroedZZZ && bFindEndOfTape) + { + if(fread(marker, sizeof(marker), 1, pTape) != 1 || + memcmp(marker, DATA_MARKER, sizeof(marker))) // need a marker here + { + fprintf(stderr, "missing 2nd data marker at position %ld, file \"%-17.17s\"\n", + (long)ftell(pTape) - 4, + file.file_identifier); + + return -14; + } + + fseek(pTape, lPos, SEEK_SET); // position to just BEFORE the 'ZEROED.ZZZ' file header + + break; + } + } + + if(bValidate && !bFindEndOfTape) + fputs("** TAPE VALIDATED **\n", stdout); + + if(piSeq) + *piSeq = iSeq; // I'm returning the sequence number of the last file on the tape + + return 0; // success +} + +int write_the_tape(FILE *pTape, const char *szTapeFileName, const char *szInputName, int bAppend, int iDriveSize, const char *szLabel) +{ +int iRval = -1, iSeq=0, i1; +void *pD; +unsigned long dwMode; +char tbuf[PATH_MAX * 2], szDir[PATH_MAX]; + + fseek(pTape, 0, SEEK_SET); + + if(!bAppend) + { +initialize_tape_first: + // initialize the tape file, extend to 32Mb, point just after the tape header + + iRval = do_initialize_tape(pTape, szTapeFileName, iDriveSize, szLabel); + + if(iRval) + { + return iRval; + } + } + else + { + // read through the tape file until I get to the last file, and set the file pointer there + // If the tape is not initialized, initialize it. + + iRval = read_the_tape(pTape, szTapeFileName, NULL, 0, 0, 0, 0, 1, &iSeq); // seeks to end of tape; + // TODO: need the sequence number of the last file - new param + + if(iRval > 0) // an uninitialized tape + { + fprintf(stderr, "WARNING - tape is empty, initializing...\n"); + goto initialize_tape_first; + } + else if(iRval) + { + fprintf(stderr, "ERROR - unable to find end of tape (aborting)\n"); + return iRval; + } + + // right now iSeq should be the sequence number for the last file found + // if there were NO files on the tape, however, it could be negative + // TODO: should I fix this?? + + if(iSeq < 0) // can happen with an empty tape + iSeq = 0; + } + + // do a directory listing of the sub-directory 'pInputName' and write all of + // the files that I find [that are not directories] to the tape, with upper case + // 6.3 file names and RT11 date/times + + strncpy(szDir, szInputName, sizeof(szDir) - 8); + + i1 = strlen(szDir); + + if(szDir[i1 - 1] != '/') + szDir[i1++] = '/'; + + szDir[i1] = '*'; + szDir[i1 + 1] = '.'; + szDir[i1 + 2] = '*'; + szDir[i1 + 3] = 0; + + memcpy(tbuf, szDir, i1); // to build full file name when needed + + pD = WBAllocDirectoryList(szDir); + if(!pD) + { + fprintf(stderr, "ERROR - unable to get directory list for \"%s\", errno=%d (%xH)\n", + szDir, errno, errno); + + return -21; + } + + iRval = 0; + + while(!WBNextDirectoryEntry(pD, tbuf + i1, sizeof(tbuf) - i1 - 1, &dwMode)) + { + if(!S_ISDIR(dwMode) && !S_ISFIFO(dwMode) && !S_ISSOCK(dwMode) // don't copy these + && !S_ISLNK(dwMode)) // for now I also skip symlinks + { + iRval = write_single_file_to_tape(pTape, tbuf, &iSeq); + + if(iRval) + break; + } + } + + WBDestroyDirectoryList(pD); + + return iRval; // for now... +} + +// NOTE: on entry, '*pfnFileSeqNum' is the seq # for the last file on the tape, +// or 0 if the tape is empty. It is pre-incremented before assigning to +// the next file written to the tape. +int write_single_file_to_tape(FILE *pTape, const char *szFileName, int *pnFileSeqNum) +{ +int i1, i2, iRval = -999, nBlocks=0, nRTYear, nRTDay, cb1; +long lFileSize, nBytes; +FILE *pInput; +const char *p1, *p2, *pEnd; +RT11_FILE_HEADER file; +RT11_FILE_EOF eof; +uint8_t buf[512]; // file data +char tbuf[256]; + + if(!FileExists(szFileName) || IsDirectory(szFileName)) + return -1; // file will not be copied onto the tape or does not exist any more + + pInput = fopen(szFileName, "r"); + if(!pInput) + { + fprintf(stderr, "ERROR opening file \"%s\" - errno=%d (%xH)\n", + szFileName, errno, errno); + + return -2; // error opening file + } + + // file is open. build headers for it + // TODO: since name is restricted to 6.3 uppercase I need to keep track of the files that + // are already on the tape, and pick a new name as needed if there's already a match. + // BUT, for NOW, to expedite writing this, I'll just convert to upper case and truncate + // the name and put it on the tape as-is + + memset(&file, 0, sizeof(file)); + memcpy(file.label_identifier, "HDR", sizeof(file.label_identifier)); + file.label_number = '1'; + memcpy(file.file_set_identifier, "RT11A ", sizeof(file.file_set_identifier)); + memcpy(file.file_section_number, "0001", sizeof(file.file_section_number)); + + (*pnFileSeqNum) ++; // increment first + snprintf(tbuf, sizeof(tbuf), "%04d", *pnFileSeqNum); + memcpy(file.file_sequence_number, tbuf, sizeof(file.file_sequence_number)); + + format_rt11_file_name(szFileName, tbuf, sizeof(tbuf)); + memset(file.file_identifier, ' ', sizeof(file.file_identifier)); + memcpy(file.file_identifier, tbuf, 10); // always 10 bytes long + + memcpy(file.generation_version, "00", sizeof(file.generation_version)); + + // get the file's date/time info as RT11 date/time, make it the 'creation date' + get_file_RT11_date_time(szFileName, &nRTYear, &nRTDay); // this returns the year as YYYY not YY + snprintf(tbuf, sizeof(tbuf), "%3d%03d", (nRTYear-1900)%1000, nRTDay); + memcpy(file.creation_date, tbuf, sizeof(file.creation_date)); + + memcpy(file.expiration_date, "000000", sizeof(file.expiration_date)); + file.accessibility = ' '; + memcpy(file.block_count, "000000", sizeof(file.block_count)); + memcpy(file.system_code, "DECRT11A ", sizeof(file.system_code)); + memset(file.reserved7, ' ', sizeof(file.reserved7)); + + // NEXT, do the EOF header. Most fields are duplicates of 'file' + + memcpy(&eof, &file, sizeof(eof)); + // eof fields that are different + memcpy(eof.label_identifier, "EOF", 3); + + // start by writing the file header + + i1 = write_tape_block(pTape, &file); + if(i1) + { + fprintf(stderr, "ERROR - write_tape_block() returns %d, errno=%d (%xH)\n", + i1, errno, errno); + + iRval = i1; + goto the_exit_point; + } + + // next I must write a DATA_MARKER + + if(fwrite(DATA_MARKER, 4, 1, pTape) != 1) // write two data markers here to mark EOT + { +data_marker_error: + fprintf(stderr, "ERROR - can't write data marker, errno=%d (%xH)\n", + errno, errno); + + iRval = -1; + goto the_exit_point; + } + + // next, read 512 byte blocks of the file and write them until I'm done reading it + + fseek(pInput, 0, SEEK_END); + lFileSize = ftell(pInput); + fseek(pInput, 0, SEEK_SET); + + nBlocks = (lFileSize + 511) / 512; // calc # of blocks that I'll need + + for(i1=0, nBytes=0; i1 < nBlocks; i1++, nBytes += 512) + { + memset(buf, 0, sizeof(buf)); + + if(lFileSize > nBytes + 512) + { + cb1 = 512; + } + else + { + cb1 = (int)(lFileSize - nBytes); + } + + i2 = fread(buf, cb1, 1, pInput); + + if(i2 != 1) + { + fprintf(stderr, "READ ERROR on input file \"%s\", i2=%d errno=%d (%xH)\n", + szFileName, i2, errno, errno); + iRval = -2; + + goto the_exit_point; + } + + i2 = write_tape_block(pTape, buf); + if(i2) + { + fprintf(stderr, "ERROR - write_tape_block() returns %d, errno=%d (%xH)\n", + i2, errno, errno); + + iRval = i2; + goto the_exit_point; + } + } + + // next I must write a DATA_MARKER + + if(fwrite(DATA_MARKER, 4, 1, pTape) != 1) // write two data markers here to mark EOT + { + goto data_marker_error; + } + + // update the block count and write EOF + snprintf(tbuf, sizeof(tbuf), "%06d", nBlocks); + memcpy(eof.block_count, tbuf, sizeof(eof.block_count)); + + i1 = write_tape_block(pTape, &eof); + if(i1) + { + fprintf(stderr, "ERROR - write_tape_block() returns %d, errno=%d (%xH)\n", + i1, errno, errno); + + iRval = i1; + goto the_exit_point; + } + + if(fwrite(DATA_MARKER, 4, 1, pTape) != 1) // write two data markers here to mark EOT + { + goto data_marker_error; + } + + iRval = 0; + +the_exit_point: + fclose(pInput); + return iRval; +} + +int do_initialize_tape(FILE *pTape, const char *szFileName, int iDriveSize, const char *pLabel) +{ +int i1; +long lFileSize = 0, lTargetSize = 1024L * 1024L * iDriveSize, lPos; +RT11_VOL_HEADER *pHdr; +char buf[512]; // what I write from + + // build a header + + memset(buf, ' ', sizeof(buf)); // rather than zeros, use white space (no harm) + + pHdr = (RT11_VOL_HEADER *)buf; + + memcpy(pHdr->label_identifier, "VOL", sizeof(pHdr->label_identifier)); + pHdr->label_number = '1'; + memcpy(pHdr->volume_identifier, "RT11A ", sizeof(pHdr->volume_identifier)); + pHdr->accessibility = ' '; + memset(pHdr->reserved26, ' ', sizeof(pHdr->reserved26)); + memcpy(pHdr->owner_identifier, "D%B", sizeof(pHdr->owner_identifier)); + memcpy(pHdr->owner_name, pLabel, sizeof(pHdr->owner_name)); + pHdr->DEC_standard_version = '1'; + memset(pHdr->reserved28, ' ', sizeof(pHdr->reserved28)); + pHdr->label_standard_version = '3'; + + // tape marker, header, tape marker + + if((i1 = fwrite("\x00\x02\x00\x00", 4, 1, pTape)) != 1 || + (i1 = fwrite(buf, sizeof(buf), 1, pTape)) != 1 || + (i1 = fwrite("\x00\x02\x00\x00", 4, 1, pTape)) != 1) + { +write_error: + fprintf(stderr, "ERROR: fwrite() returns %d, errno=%d (%xH)\n", + i1, errno, errno); + + fclose(pTape); + return -1; + } + + lPos = ftell(pTape); // current file postion + + // always write at least one empty buffer minus the size of the marker * 2 + + memset(buf, 0, sizeof(buf)); + + if((i1 = fwrite(buf, sizeof(buf) - 8, 1, pTape)) != 1) + goto write_error; + + lFileSize = sizeof(buf) * 2; // tape and marker and remaining 0 bytes + + while(lFileSize < lTargetSize) + { + if((i1 = fwrite(buf, sizeof(buf), 1, pTape)) != 1) + goto write_error; + + lFileSize += sizeof(buf); + } + + // set the file pointer to "right after the header" + + fseek(pTape, lPos, SEEK_SET); + + return 0; // I am done +} + +int initialize_tape(const char *szFileName, int iDriveSize, int bOverwrite, const char *pLabel) +{ +int iRval; +FILE *pTape; + + + if(!pLabel || !*pLabel) // would be padded with white space if done right + pLabel = "dectape "; + + if(iDriveSize <= 0) + { + fprintf(stderr, "Invalid drive size %d\n", iDriveSize); + + usage(); + + return -1; + } + + // STEP 1: does the file exist?? + + if(FileExists(szFileName)) + { + if(!bOverwrite && !QueryYesNo("Overwrite existing file")) + return 1; // you said 'no' + + // make sure file is zero bytes long + + if(truncate(szFileName, 0)) // zero bytes long + { + fprintf(stderr, "Unable to write (truncate) to \"%s\", errno=%d (%xH)\n", + szFileName, errno, errno); + + return -1; + } + } + + pTape = fopen(szFileName, "w"); + if(!pTape) + { + fprintf(stderr, "Unable to open \"%s\", errno=%d (%xH)\n", + szFileName, errno, errno); + + return -1; + } + + iRval = do_initialize_tape(pTape, szFileName, iDriveSize, pLabel); + + fclose(pTape); + + return iRval; +} + +static const short aDays[13]= +{ + 1, + 1 + 31, + 1 + 31 + 28, + 1 + 31 + 28 + 31, + 1 + 31 + 28 + 31 + 30, + 1 + 31 + 28 + 31 + 30 + 31, + 1 + 31 + 28 + 31 + 30 + 31 + 30, + 1 + 31 + 28 + 31 + 30 + 31 + 30 + 31, + 1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, + 1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + 1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + 1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, + 1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31 +}; +static const short aDaysLeap[13]= +{ + 1, + 1 + 31, + 1 + 31 + 29, + 1 + 31 + 29 + 31, + 1 + 31 + 29 + 31 + 30, + 1 + 31 + 29 + 31 + 30 + 31, + 1 + 31 + 29 + 31 + 30 + 31 + 30, + 1 + 31 + 29 + 31 + 30 + 31 + 30 + 31, + 1 + 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31, + 1 + 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + 1 + 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + 1 + 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, + 1 + 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31 +}; + +int days_since_year_start(int iYear, int iMonth, int iDay) +{ +const short *pD; + + if((iYear % 400) == 0 || + ((iYear % 4) == 0 && (iYear % 100) != 0)) // leap year + { + pD = aDaysLeap; + } + else + { + pD = aDays; + } + + if(iMonth >= 1 && iMonth <= 12) + return pD[iMonth - 1] + iDay - 1; + + return 0; // an error, really +} + +void mdy_from_days_since_year_start(int iYear, int nDays, int *piMonth, int *piDay) +{ +const short *pD; + + if((iYear % 400) == 0 || + ((iYear % 4) == 0 && (iYear % 100) != 0)) // leap year + { + pD = aDaysLeap; + } + else + { + pD = aDays; + } + + for(*piMonth=1; *piMonth < 12; (*piMonth)++) // month is 1-based, as is day in year + { + if(nDays < pD[*piMonth]) // found entry with more days than current one + break; + } + + *piDay = 1 + nDays - pD[*piMonth - 1]; // day is also 1-based + + if(*piMonth < 1 || *piMonth > 12 || *piDay < 1 || *piDay > 31) + { + fprintf(stderr, "ERROR - insane month/days from year start: iYear=%d nDays=%d iMonth=%d iDay=%d\n", + iYear, nDays, *piMonth, *piDay); + } +} + +void rt11_date(const char *pDate, int *piYear, int *piDayInYear) +{ +int iYear, iDay; + + // ignore first character if it's a space, otherwise # of years since 1900 + // format the date from the following information: + // + // YYYddd where 'YYY' is years since 1900, and 'ddd' is days since Jan 1 (inclusive) + + if(*pDate >= '0' && *pDate <= '9') + iYear = 1900 + 100 * (*pDate - '0'); + else + iYear = 1900; + + iYear += 10 * (pDate[1] - '0') + (pDate[2] - '0'); + + iDay = 100 * (pDate[3] - '0') + + 10 * (pDate[4] - '0') + + (pDate[5] - '0'); + + if(iDay < 1 || iDay > 366) + { + fprintf(stderr, "ERROR - insane 'day' value: \"%s\" --> iYear=%d, iDay=%d\n", + pDate, iYear, iDay); + } + + if(piYear) + *piYear = iYear; + + if(piDayInYear) + *piDayInYear = iDay; +} + +const char *rt11_date_string(const char *pDate) +{ +int iYear, iDay, iMonth; +static char szRval[16]; +static const char * const aszMonthNames[13]= +{ + "???","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" +}; + + rt11_date(pDate, &iYear, &iDay); + mdy_from_days_since_year_start(iYear, iDay, &iMonth, &iDay); + + if(iDay < 1 || iDay > 30 || iMonth < 1 || iMonth > 12) + { + fprintf(stderr, "ERROR - insane dates - \"%s\" --> iYear=%d, iMonth=%d iDay=%d\n", + pDate, iYear, iMonth, iDay); + } + + snprintf(szRval, sizeof(szRval), "%02d-%s-%02d", + (iDay % 100), + aszMonthNames[iMonth], // 1-based month + (iYear % 100)); + + return szRval; +} + + +void format_rt11_file_name(const char *szSourceFile, char *pBuf, int cbBuf) +{ +int i1; +const char *p1c; +char *p1, *p2; +char tbuf[64]; +char name[7]; +char ext[4]; + + memset(tbuf, 0, sizeof(tbuf)); + + // the filename must be a 6.3 name, upper case. + p1c = strrchr(szSourceFile, '/'); // if it has a directory in it, find the '/' + if(p1c) + p1c++; + else + p1c = szSourceFile; + + memset(tbuf, 0, sizeof(tbuf)); + memset(name, 0, sizeof(name)); + memset(ext, 0, sizeof(ext)); + + strncpy(tbuf, p1c, sizeof(tbuf)-1); + + // convert to upper case. Name must be RAD50 char, 0-9 A-Z $ ? or space, + // in the format NNNNNN.EEE with trailing white space to justify + + p1 = tbuf; + while(*p1 && *p1 != '.') + { + char c1 = toupper(*p1); + if((c1 < '0' && c1 > '9') && + (c1 < 'A' && c1 > 'Z') && + (c1 != '$' && c1 != '?' && c1 != ' ')) + { + c1 = '?'; // sub in '?' for bad chars + } + + *(p1++) = c1; + } + + i1 = p1 - tbuf; + if(i1 > 6) + i1 = 6; + + if(i1 > 0) + memcpy(name, tbuf, i1); + + if(i1 < 6) + memset(name + i1, ' ', 6 - i1); + + name[6] = 0; + + if(!*p1) + { + fputs("SHIT\n",stderr); + p2 = p1; + } + else + { + p1++; + p2 = p1; + +// fprintf(stderr, " HERE: \"%s\"\n", p1); + + while(*p1) + { + char c1 = toupper(*p1); + if((c1 < '0' && c1 > '9') && + (c1 < 'A' && c1 > 'Z') && + (c1 != '$' && c1 != '?' && c1 != ' ')) + { + c1 = '?'; // sub in '?' for bad chars + } + + *(p1++) = c1; + } + +// fprintf(stderr, " HERE: \"%s\" len=%d\n", p2, p1 - p2); + } + + i1 = p1 - p2; + if(i1 > 3) + i1 = 3; + + if(i1 > 0) + memcpy(ext, p2, i1); + + if(i1 < 3) + memset(ext + i1, ' ', 3 - i1); + + ext[3] = 0; + +// fprintf(stderr, " TEMPORARY: %s becomes \"%s.%s\"\n", +// szSourceFile, name, ext); + + // format file name into output buffer + + if(cbBuf > 6) + { + memcpy(pBuf, name, 6); + pBuf += 6; + cbBuf -= 6; + } + else if(cbBuf > 0) + { + memcpy(pBuf, name, cbBuf); + cbBuf = 0; + } + + if(cbBuf > 0) + { + *(pBuf++) = '.'; + cbBuf --; + } + + if(cbBuf > 3) + { + memcpy(pBuf, ext, 3); + pBuf += 3; + cbBuf -= 3; + } + else if(cbBuf > 0) + { + memcpy(pBuf, ext, cbBuf); + cbBuf = 0; + } + + if(cbBuf > 0) + *pBuf = 0; +} + + +// FILE UTILITIES +// Some of these were derived from 'ForkMe' - http://github.com/bombasticbob/ForkMe +// that utility is covered by the same type of license, and was written by the same author as 'dectape' + +#define WBAlloc(X) malloc(X) +#define WBReAlloc(X,Y) realloc(X,Y) +#define WBFree(X) free(X) + +int FileExists(const char *szFileName) +{ +struct stat sb; + + return !stat(szFileName, &sb); +} + +int IsDirectory(const char *szFileName) +{ +struct stat sb; + + // NOTE: this returns info about symlink targets if 'szFileName' is a symlink + + return !stat(szFileName, &sb) && + S_ISDIR(sb.st_mode); +} + +// NOTE: year is returned as YYYY not YY, so subtract 1900 - then 2000 becomes 100, etc. +int get_file_RT11_date_time(const char *szFileName, int *pnRTYear, int *pnRTDay) +{ +struct stat st; +struct tm *pTM; +time_t tmFile; + + + if(*pnRTYear) + *pnRTYear = 0; + if(*pnRTDay) + *pnRTDay = 0; + + if(stat(szFileName, &st)) + { + fprintf(stderr, "Unable to 'stat' \"%s\", errno=%d (%xH)\n", + szFileName, errno, errno); + return -1; + } + + tmFile = (time_t)st.st_mtim.tv_sec; // get the time_t value; next I'll want to conver to a date + + pTM = localtime(&tmFile); + + if(!pTM) + return -1; + + if(pnRTDay) + *pnRTDay = days_since_year_start(pTM->tm_year + 1900, pTM->tm_mon + 1, pTM->tm_mday); + + if(pnRTYear) + *pnRTYear = pTM->tm_year + 1900; + + return 0; +} + +int set_file_RT11_date_time(const char *szFileName, int nRTYear, int nRTDay) +{ +struct timeval tms[2]; +struct tm tm1; +int iMonth, iDay; + + + mdy_from_days_since_year_start(nRTYear, nRTDay, &iMonth, &iDay); + + memset(&tm1, 0, sizeof(tm1)); + tm1.tm_mday = iDay; + tm1.tm_mon = iMonth - 1; + tm1.tm_year = nRTYear - 1900; + + memset(tms, 0, sizeof(tms)); + tms[0].tv_sec = tms[1].tv_sec = mktime(&tm1); + + if(DEBUG_OUTPUT_CHATTY) + fprintf(stderr, "File: \"%s\" - set RT11 date %d.%d %04d-%02d-%02d %ld\n", + szFileName, nRTYear, nRTDay, nRTYear, iMonth, iDay, tms[0].tv_sec); + + return utimes(szFileName, &(tms[0])); +} + +typedef struct __DIRLIST__ +{ + const char *szPath, *szNameSpec; + DIR *hD; + struct stat sF; + union + { + char cde[sizeof(struct dirent) + NAME_MAX + 2]; + struct dirent de; + }; +// actual composite 'search name' follows +} DIRLIST; + +void *WBAllocDirectoryList(const char *szDirSpec) +{ +DIRLIST *pRval; +char *p1, *p2; +int iLen, nMaxLen; +char *pBuf; + + if(!szDirSpec || !*szDirSpec) + { + if(DEBUG_OUTPUT_WARN) + fprintf(stderr, "WARNING - %s - invalid directory (NULL or empty)\n", __FUNCTION__); + return NULL; + } + + iLen = strlen(szDirSpec); + nMaxLen = iLen + 32; + + pBuf = WBAlloc(nMaxLen); + if(!pBuf) + { + fprintf(stderr, "ERROR - %s - Unable to allocate memory for buffer size %d\n", __FUNCTION__, nMaxLen); + return NULL; + } + + if(szDirSpec[0] == '/') // path starts from the root + { + memcpy(pBuf, szDirSpec, iLen + 1); + } + else // for now, force a path of './' to be prepended to path spec + { + pBuf[0] = '.'; + pBuf[1] = '/'; + + memcpy(pBuf + 2, szDirSpec, iLen + 1); + iLen += 2; + } + + // do a reverse scan until I find a '/' + p1 = ((char *)pBuf) + iLen; + while(p1 > pBuf && *(p1 - 1) != '/') + { + p1--; + } + + if(DEBUG_OUTPUT_CHATTY) + { + fprintf(stderr, "Allocate Directory List: \"%s\" \"%s\" \"%s\"\n", pBuf, p1, szDirSpec); + } + + if(p1 > pBuf) + { + // found, and p1 points PAST the '/'. See if it ends in '/' or if there are wildcards present + if(!*p1) // name ends in '/' + { + if(p1 == (pBuf + 1) && *pBuf == '/') // root dir + { + p1++; + } + else + { + *(p1 - 1) = 0; // trim the final '/' + } + + p1[0] = '*'; + p1[1] = 0; + } + else if(strchr(p1, '*') || strchr(p1, '?')) + { + if(p1 == (pBuf + 1) && *pBuf == '/') // root dir + { + memmove(p1 + 1, p1, strlen(p1) + 1); + *(p1++) = 0; // after this, p1 points to the file spec + } + else + { + *(p1 - 1) = 0; // p1 points to the file spec + } + } + else if(IsDirectory(pBuf)) // entire name is a directory + { + // NOTE: root directory should NEVER end up here + + p1 += strlen(p1); + *(p1++) = 0; // end of path (would be '/') + p1[0] = '*'; + p1[1] = 0; + } + else if(DEBUG_OUTPUT_WARN) + { + fprintf(stderr, "allocating directory list, but I am confused, %s %s\n", pBuf, p1); + } + } + else + { + // this should never happen if I'm always prepending a './' + // TODO: make this more consistent, maybe absolute path? + + if(DEBUG_OUTPUT_WARN) + fprintf(stderr, "Allocating directory list: should not happen, %s %s\n", pBuf, p1); + + if(strchr(pBuf, '*') || strchr(pBuf, '?')) // wildcard spec + { + p1 = (char *)pBuf + 1; // make room for zero byte preceding dir spec + memmove(pBuf, p1, iLen + 1); + *pBuf = 0; // since it's the current working dir just make it a zero byte (empty string) + } + else if(IsDirectory(pBuf)) + { + p1 = (char *)pBuf + iLen; + *(p1++) = 0; // end of path (would be '/') + p1[0] = '*'; + p1[1] = 0; + } + } + + pRval = WBAlloc(sizeof(DIRLIST) + iLen + strlen(p1) + 2); + + if(pRval) + { + pRval->szPath = pBuf; + pRval->szNameSpec = p1; + + p2 = (char *)(pRval + 1); + strcpy(p2, pBuf); + p2 += strlen(p2); + *(p2++) = '/'; + strcpy(p2, p1); + p1 = (char *)(pRval + 1); + + pRval->hD = opendir(pBuf); + + if(DEBUG_OUTPUT_VERBOSE) + fprintf(stderr, "Allocating directory list - opendir for %s returns %p\n", pBuf, pRval->hD); + + if(pRval->hD == NULL) + { + if(DEBUG_OUTPUT_WARN) + fprintf(stderr, "WARNING - %s - Unable to open dir \"%s\", errno=%d\n", __FUNCTION__, pBuf, errno); + + WBFree(pBuf); + WBFree(pRval); + + pRval = NULL; + } + } + else + { + fprintf(stderr, "ERROR - %s - Unable to allocate memory for DIRLIST\n", __FUNCTION__); + WBFree(pBuf); // no need to keep this around + } + + return pRval; +} + +void WBDestroyDirectoryList(void *pDirectoryList) +{ + if(pDirectoryList) + { + DIRLIST *pD = (DIRLIST *)pDirectoryList; + + if(pD->hD) + { + closedir(pD->hD); + } + if(pD->szPath) + { + WBFree((void *)(pD->szPath)); + } + + WBFree(pDirectoryList); + } +} + +// returns < 0 on error, > 0 on EOF, 0 for "found something" + +int WBNextDirectoryEntry(void *pDirectoryList, char *szNameReturn, + int cbNameReturn, unsigned long *pdwModeAttrReturn) +{ +struct dirent *pD; +struct stat sF; +char *p1, *pBuf; +//static char *p2; // temporary +int iRval = 1; // default 'EOF' +DIRLIST *pDL = (DIRLIST *)pDirectoryList; + + + if(!pDirectoryList) + { + return -1; + } + + // TODO: improve this, maybe cache buffer or string length... + pBuf = WBAlloc(strlen(pDL->szPath) + 8 + NAME_MAX); + + if(!pBuf) + { + return -2; + } + + strcpy(pBuf, pDL->szPath); + p1 = pBuf + strlen(pBuf); + if(p1 > pBuf && *(p1 - 1) != '/') // it does not already end in / + { + *(p1++) = '/'; // for now assume this + *p1 = 0; // by convention + } + + if(pDL->hD) + { + while((pD = readdir(pDL->hD)) + != NULL) + { + // skip '.' and '..' + if(pD->d_name[0] == '.' && + (!pD->d_name[1] || + (pD->d_name[1] == '.' && !pD->d_name[2]))) + { + if(DEBUG_OUTPUT_CHATTY) + fprintf(stderr, "%s.%d: skipping %s\n", __FUNCTION__, __LINE__, pD->d_name); + continue; // no '.' or '..' + } + + strcpy(p1, pD->d_name); + + if(!lstat(pBuf, &sF)) // 'lstat' returns data about a file, and if it's a symlink, returns info about the link itself + { + if(!fnmatch(pDL->szNameSpec, p1, 0/*FNM_PERIOD*/)) // 'tbuf2' is my pattern + { + iRval = 0; + + if(pdwModeAttrReturn) + { + *pdwModeAttrReturn = sF.st_mode; + } + + if(szNameReturn && cbNameReturn > 0) + { + strncpy(szNameReturn, p1, cbNameReturn); + } + + break; + } + else if(DEBUG_OUTPUT_CHATTY) + { + fprintf(stderr, "DEBUG: \"%s\" does not match \"%s\"\n", p1, pDL->szNameSpec); + } + } + else + { + if(DEBUG_OUTPUT_WARN) + fprintf(stderr, "%s: can't 'stat' %s, errno=%d (%08xH)\n", __FUNCTION__, pBuf, errno, errno); + } + } + } + + if(pBuf) + { + WBFree(pBuf); + } + + return iRval; + +} + diff --git a/xen/tools/unix/init_rt11os_image.sh b/xen/tools/unix/init_rt11os_image.sh new file mode 100755 index 00000000..597d9e53 --- /dev/null +++ b/xen/tools/unix/init_rt11os_image.sh @@ -0,0 +1,13 @@ +#!/bin/bash -xe +cp -p Disks/rtv53_rl.dsk distribution.dsk +cp -p Disks/empty-rl02.dsk distribution-backup.dsk +cp -p Disks/empty-rl02.dsk rt11os.dsk +cp -p Disks/empty-rl02.dsk empty.dsk +touch lpt.txt +pdp11 initial.ini +mv empty.dsk Disks/empty-formatted.dsk +touch -t 9101011200 HALT.SAV STARTF.COM +rt11dsk a ./rt11os.dsk HALT.SAV >/dev/null +rt11dsk d ./rt11os.dsk STARTF.COM >/dev/null +rt11dsk a ./rt11os.dsk STARTF.COM >/dev/null +rt11dsk l ./rt11os.dsk |egrep -i "START|HALT" diff --git a/xen/tools/unix/obj2bin.pl b/xen/tools/unix/obj2bin.pl new file mode 100755 index 00000000..56d610fa --- /dev/null +++ b/xen/tools/unix/obj2bin.pl @@ -0,0 +1,1270 @@ +#!/usr/bin/perl -w +# https://forum.vcfed.org/index.php?threads/macro-11-cross-assembler.73895/post-896761 +# Copyright (c) 2005-2016 Don North +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# o Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# o Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# o Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +require 5.008; + +=head1 NAME + +obj2bin.pl - Convert a Macro-11 program image to PROM/load format + +=head1 SYNOPSIS + +obj2bin.pl +S<[--help]> +S<[--debug]> +S<[--verbose]> +S<[--boot]> +S<[--console]> +S<[--binary]> +S<[--ascii]> +S<[--rt11]> +S<[--rsx11]> +S<[--bytes=N]> +S<[--nocrc]> +S<[--logfile=LOGFILE]> +S<--outfile=BINFILE> +S + +=head1 DESCRIPTION + +Converts a Macro-11 object files to various output formats, +including M9312 boot and console PROM, straight binary records, +ASCII format for M9312 console load commands, and loadable absolute +binary program images (.BIN) files. + +Multiple .psect/.asect ops are supported, as well as all local +(non-global) relocation directory entries. + +Supports either RT-11 or RSX-11 format object files. + +=head1 OPTIONS + +The following options are available: + +=over + +=item B<--help> + +Output this manpage and exit the program. + +=item B<--debug> + +Enable debug mode; print input file records as parsed. + +=item B<--verbose> + +Verbose status; output status messages during processing. + +=item B<--boot> + +Generate a hex PROM file image suitable for programming into +an M9312 boot prom (512x4 geometry, only low half used). + +=item B<--console> + +Generate a hex PROM file image suitable for programming into +an M9312 console/diagnostic prom (1024x4 geometry). + +=item B<--ascii> + +Generate a a sequence of 'L addr' / 'D data' commands for downloading +a program via a terminal emulator thru the M9312 user command interface. +Suitable only for really small test programs. + +=item B<--binary> + +Generate binary format load records of the program image (paper +tape format) for loading into SIMH or compatible simulators. +These files can also be copied onto XXDP filesystems to generate +runnable program images (used to write custom diaqnostics). + +Binary format is the default if no other option is specified. +If more than one option is specified the last one takes effect. + +=item B<--rt11> + +Read input object files in RT-11 format. + +=item B<--rsx11> + +Read input object files in RSX-11 format. + +RSX-11 object file format is the default if no other option is specified. +If more than one option is specified the last one takes effect. + +=item B<--bytes=N> + +For hex format output files, output N bytes per line (default 16). + +=item B<--nocrc> + +For hex format output files, don't automatically stuff the computed +CRC-16 as the last word in the ROM. + +=item B<--logfile=FILENAME> + +Generate debug output into this file. + +=item B<--outfile=FILENAME> + +Output binary file in format selected by user option. + +=item B + +Input object file(s) in .obj format. + +=back + +=head1 ERRORS + +The following diagnostic error messages can be produced on STDERR. +The meaning should be fairly self explanatory. + +C -- bad option or missing file(s) + +C -- bad filename or unreadable file + +C -- valid RT-11 record must start with 0x01 + +C -- second RT-11 byte must be 0x00 + +C -- third byte is low byte of record length + +C -- fourth byte is high byte of record length + +C -- bytes five thru end-1 are data bytes + +C -- last RT-11 byte is checksum + +C -- compare rcv'ed checksum vs exp'ed checksum + +=head1 EXAMPLES + +Some examples of common usage: + + obj2bin.pl --help + + obj2bin.pl --verbose --boot --out 23-751A9.hex 23-751A9.obj + + obj2bin.pl --verbose --binary --rt11 --out memtest.bin memtest.obj + + obj2bin.pl --verbose --binary --rsx11 --out prftst.bin prftst.obj mac/printf.obj + +=head1 AUTHOR + +Don North - donorth + +=head1 HISTORY + +Modification history: + + 2005-05-05 v1.0 donorth - Initial version. + 2016-01-15 v1.1 donorth - Added RLD(IR) processing, moved sub's to end. + 2016-01-18 v1.2 donorth - Added GSD processing, improved debug output. + 2016-01-20 v1.3 donorth - Initial support for linking multiple PSECTs. + 2016-01-22 v1.4 donorth - Added objfile/outfile/logfile switches vs stdio. + 2016-01-28 v1.5 donorth - Added RLD processing, especially complex. + 2017-04-01 v1.9 donorth - Renamed from obj2hex.pl to obj2bin.pl + 2017-05-04 v1.95 donorth - Updated capability to read multiple input .obj files. + 2020-03-06 v2.0 donorth - Updated help documentation and README.md file. + 2020-03-10 v2.1 donorth - Broke down and added RSX-11 input format option. + +=cut + +# options +use strict; + +# external standard modules +use Getopt::Long; +use Pod::Text; +use FindBin; +use FileHandle; + +# external local modules search path +BEGIN { unshift(@INC, $FindBin::Bin); + unshift(@INC, $ENV{PERL5LIB}) if defined($ENV{PERL5LIB}); # cygwin bugfix + unshift(@INC, '.'); } + +# external local modules + +# generic defaults +my $VERSION = 'v2.1'; # version of code +my $HELP = 0; # set to 1 for man page output +my $DEBUG = 0; # set to 1 for debug messages +my $VERBOSE = 0; # set to 1 for verbose messages + +# specific defaults +my $crctype = 'CRC-16'; # type of crc calc to do +my $memsize; # number of instruction bytes allowed +my $memfill; # memory fill pattern +my %excaddr; # words to be skipped in rom crc calc +my $rombase; # base address of rom image +my $romsize; # number of rom addresses +my $romfill; # rom fill pattern +my $romtype = 'BINA'; # default rom type is BINARY +my $objtype = 'RSX11'; # default object file format is RSX11 +my $bytesper = -1; # bytes per block in output file +my $nocrc = 0; # output CRC16 as last word unless set +my $outfile = undef; # output filename +my $logfile = undef; # log filename + +# process command line arguments +my $NOERROR = GetOptions( "help" => \$HELP, + "debug" => \$DEBUG, + "verbose" => \$VERBOSE, + "boot" => sub { $romtype = 'BOOT'; }, + "console" => sub { $romtype = 'DIAG'; }, + "binary" => sub { $romtype = 'BINA'; }, + "ascii" => sub { $romtype = 'ASC9'; }, + "rt11" => sub { $objtype = 'RT11'; }, + "rsx11" => sub { $objtype = 'RSX11'; }, + "bytes=i" => \$bytesper, + "nocrc" => \$nocrc, + "outfile=s" => \$outfile, + "logfile=s" => \$logfile, + ); + +# init +$VERBOSE = 1 if $DEBUG; # debug implies verbose messages + +# output the documentation +if ($HELP) { + # output a man page if we can + if (ref(Pod::Text->can('new')) eq 'CODE') { + # try the new way if appears to exist + my $parser = Pod::Text->new(sentence=>0, width=>78); + printf STDOUT "\n"; $parser->parse_from_file($0); + } else { + # else must use the old way + printf STDOUT "\n"; Pod::Text::pod2text(-78, $0); + }; + exit(1); +} + +# check for correct arguments present, print usage if errors +unless ($NOERROR + && scalar(@ARGV) >= 1 + && defined($outfile) + && $romtype ne 'NONE' + ) { + printf STDERR "obj2bin.pl %s by Don North (perl %g)\n", $VERSION, $]; + print STDERR "Usage: $0 [options...] arguments\n"; + print STDERR <<"EOF"; + --help output manpage and exit + --debug enable debug mode + --verbose verbose status reporting + --boot M9312 boot prom .hex + --console M9312 console/diagnostic prom .hex + --binary binary program load image .bin [default] + --ascii ascii m9312 program load image .txt + --rt11 read .obj files in RT11 format + --rsx11 read .obj files in RSX11 format [default] + --bytes=N bytes per block on output hex format + --nocrc inhibit output of CRC-16 in hex format + --logfile=LOGFILE logging message file + --outfile=OUTFILE output .hex/.txt/.bin file + OBJFILE... macro11 object .obj file(s) +EOF + # exit if errors... + die "Aborted due to command line errors.\n"; +} + +# setup log file as a file, defaults to STDERR if not supplied +my $LOG = defined($logfile) ? FileHandle->new("> ".$logfile) : FileHandle->new_from_fd(fileno(STDERR),"w"); + +#---------------------------------------------------------------------------------------------------- + +# subroutine prototypes + +sub trim ($); +sub chksum (@); +sub rad2asc (@); +sub crc (%); +sub sym2psect ($$); +sub read_rec ($); +sub get_global ($); +sub parse_rec ($$$); + +#---------------------------------------------------------------------------------------------------- + +# fill in the parameters of the device + +if ($romtype eq 'BOOT') { + + # M9312 512x4 boot prom + %excaddr = ( 024=>1, 025=>1 ); # bytes to be skipped in rom crc calc + $memsize = 128; # number of instruction bytes allowed + $memfill = 0x00; # memory fill pattern + $romsize = 512; # number of rom addresses (must be a power of two) + $romfill = 0x00; # rom fill pattern + $rombase = 0173000; # base address of rom + +} elsif ($romtype eq 'DIAG') { + + # M9312 1024x4 diagnostic/console prom + %excaddr = ( ); # bytes to be skipped in rom crc calc + $memsize = 512; # number of instruction bytes allowed + $memfill = 0x00; # memory fill pattern + $romsize = 1024; # number of rom addresses (must be a power of two) + $romfill = 0x00; # rom fill pattern + $rombase = 0165000; # base address of rom + +} elsif ($romtype eq 'BINA' || $romtype eq 'ASC9') { + + # program load image ... 56KB address space maximum + %excaddr = ( ); # bytes to be skipped in rom crc calc + $memsize = 7*8192; # number of instruction bytes allowed + $memfill = 0x00; # memory fill pattern + $romsize = 8*8192; # number of rom addresses (must be a power of two) + $romfill = 0x00; # image fill pattern + $rombase = 0; # base address of binary image + +} else { + + # unknown ROM type code + die "ROM type '$romtype' is not supported!\n"; + +} + +if ($VERBOSE) { + printf $LOG "ROM type is '%s'\n", $romtype; + printf $LOG "ROM space is %d. bytes\n", $memsize; + printf $LOG "ROM length is %d. addresses\n", $romsize; + printf $LOG "ROM base address is 0%06o\n", $rombase; +} + +#---------------------------------------------------------------------------------------------------- + +# read/process the input object file records + +# real pdp11 memory data words in boot prom +my @mem = ((0) x $memsize); + +# min/max address limits in object file +my ($adrmin,$adrmax) = ('',''); + +# state variables in processing object records +my $rommsk = ($romsize-1)>>1; # address bit mask +my $adrmsk = 0xFFFF; # 16b addr mask +my $datmsk = 0xFFFF; # 16b data mask +my $memmsk = 0xFF; # 8b memory data mask + +# databases +my %gblsym = (); +my %psect = (); +my @psect = (); +my %program = (); +my $psectname = sprintf("%02d:%s",1,'. ABS.'); +my $psectaddr = 0; +my $psectnumb = -1; +my $textaddr = 0; + +# program defaults +$program{START}{ADDRESS} = 1; +$program{START}{VALUE} = 1; +$program{START}{PSECT} = $psectname; + +# two passes, first is headers, second is data records +foreach my $pass (1..2) { + foreach my $numb (0..$#ARGV) { + my $objfile = $ARGV[$numb]; + my $OBJ = FileHandle->new("< ".$objfile); + die "Error: can't open input object file '$objfile'\n" unless defined $OBJ; + printf $LOG "\n\nPROCESS PASS %d FILE %d '%s'\n\n", $pass, $numb+1, $objfile if $DEBUG; + while (my @rec = &read_rec($OBJ)) { &parse_rec($numb+1, $pass, \@rec); } + $OBJ->close; + } +} + +#---------------------------------------------------------------------------------------------------- + +# compute CRC if required, copy memory image to output buffer + +my @buf = ($romfill) x $romsize; # physical PROM data bytes, filled background pattern + +# only compute CRC on M9312 ROMs +if ($romtype eq 'BOOT' || $romtype eq 'DIAG') { + + # compute CRC-16 of the prom contents (except exception words) and store at last location + my $crctab = &crc(-name=>$crctype, -new=>1); + my $crc = &crc(-name=>$crctype, -init=>1); + for (my $adr = 0; $adr < $memsize-2; $adr += 1) { + next if exists($excaddr{$adr}); # skip these addresses + $mem[$rombase+$adr] = $memfill unless defined($mem[$rombase+$adr]); + $crc = &crc(-name=>$crctype, -table=>$crctab, -crc=>$crc, -byte=>$mem[$rombase+$adr]); + } + $crc = &crc(-name=>$crctype, -crc=>$crc, -last=>1); + unless ($nocrc) { + # output computed CRC-16 as last word in the ROM file + $mem[$rombase+$memsize-2] = ($crc>>0)&0xFF; + $mem[$rombase+$memsize-1] = ($crc>>8)&0xFF; + } + printf $LOG "ROM %s is %06o (0x%04X)\n", $crctype, ($crc) x 2 if $VERBOSE; + + # process data words to actual PROM byte data + # put 4bit nibble in low 4b of each 8b data byte, zero the upper 4b + # only copy the above instruction portion over + for (my $idx = 0; $idx < $memsize<<1; $idx += 4) { + my $dat = ($mem[$rombase+($idx>>1)+1]<<8) | ($mem[$rombase+($idx>>1)+0]<<0); + $buf[$idx+0] = ($dat&0xE)|(($dat>>8)&0x1); # bits 3 2 1 8 + $buf[$idx+1] = ($dat>>4)&0xF; # bits 7 6 5 4 + $buf[$idx+2] = ((($dat>>8)&0xE)|($dat&0x1))^0xC; # bits ~11 ~10 9 0 + $buf[$idx+3] = (($dat>>12)&0xF)^0x1; # bits 15 14 13 ~12 + } + +} elsif ($romtype eq 'BINA' || $romtype eq 'ASC9') { + + # only copy the above instruction portion over + for (my $adr = 0; $adr < $memsize; $adr += 1) { + $mem[$rombase+$adr] = $memfill unless defined($mem[$rombase+$adr]); + $buf[$adr] = $mem[$rombase+$adr]; + } + +} + +if ($VERBOSE) { + + # print checksum of entire device + my $chksum = 0; map($chksum += $_, @buf); + printf $LOG "ROM checksum is %06o (0x%04X)\n", $chksum, $chksum; + +} + +#---------------------------------------------------------------------------------------------------- + +# output the linked/processed binary file image in the desired format + +my $OUT = FileHandle->new("> ".$outfile); +die "Error: can't open output file '$outfile'\n" unless defined $OUT; + +if ($romtype eq 'BOOT' || $romtype eq 'DIAG') { + + # output the entire PROM buffer as an intel hex file + + $bytesper = 16 if $bytesper <= 0; + + for (my $idx = 0; $idx < $romsize; $idx += $bytesper) { + my $cnt = $idx+$bytesper <= $romsize ? $bytesper : $romsize-$idx; # N bytes or whatever is left + my @dat = @buf[$idx..($idx+$cnt-1)]; # get the data + my $dat = join('', map(sprintf("%02X",$_),@dat)); # map to ascii text + printf $OUT ":%02X%04X%02X%s%02X\n", $cnt, $idx, 0x00, $dat, &chksum($cnt, $idx>>0, $idx>>8, 0x00, @dat); + } + + printf $OUT ":%02X%04X%02X%s%02X\n", 0x00, 0x0000, 0x01, '', &chksum(0x0, 0x0000>>0, 0x0000>>8, 0x01); + +} elsif ($romtype eq 'BINA') { + + # Loader format consists of blocks, optionally preceded, separated, and + # followed by zeroes. Each block consists of: + # + # 001 --- + # 000 | + # lo(length) | + # hi(length) | + # lo(address) > 'length' bytes + # hi(address) | + # databyte1 | + # : | + # databyteN --- + # checksum + # + # If the byte length is exactly six, the block is the last on the tape, and + # there is no checksum. If the origin is not 000001, then the origin is + # the PC at which to start the program. + + $bytesper = 128 if $bytesper <= 0; + + my $start = $program{START}{ADDRESS}; + + sub m ($) { $_[0] & 0xFF; } + + # output the entire PROM buffer as a binary loader file + for (my $idx = $adrmin; $idx < $adrmax+1; $idx += $bytesper) { + my $cnt = $idx+$bytesper <= $adrmax+1 ? $bytesper : $adrmax+1-$idx; # N bytes or whatever is left + my @dat = @buf[$idx..($idx+$cnt-1)]; # get the data + my $len = $cnt+6; + my @rec = (0x01, 0x00, &m($len>>0), &m($len>>8), &m($idx>>0), &m($idx>>8), @dat); + print $OUT pack("C*", @rec, &chksum(@rec)); + } + my @end = (0x01, 0x00, 0x06, 0x00, &m($start>>0), &m($start>>8)); + print $OUT pack("C*", @end, &chksum(@end)); + +} elsif ($romtype eq 'ASC9') { + + # ascii interface to M9312 console emulator + + sub n ($) { $_[0] & 0xFF; } + + # start program load here + printf $OUT "L %o\r\n", $adrmin; + + # output the PROM buffer as an ascii load file + for (my $idx = $adrmin; $idx < $adrmax+1; $idx += 2) { + printf $OUT "D %06o\r\n", (&n($buf[$idx+1])<<8) | &n($buf[$idx+0]); + } + + # start program exec here + printf $OUT "L %o\r\nS\r\n", $adrmin; + +} + +# all done +$OUT->close; + +#---------------------------------------------------------------------------------------------------- + +# really done +$LOG->close; +exit; + +#---------------------------------------------------------------------------------------------------- +#---------------------------------------------------------------------------------------------------- + +# trim leading/trailing spaces on a string + +sub trim ($) { + + my ($str) = @_; + + $str =~ s/\s+$//; + $str =~ s/^\s+//; + + return $str; +} + +#---------------------------------------------------------------------------------------------------- + +# compute checksum (twos complement of the sum of bytes) + +sub chksum (@) { + + my $sum = 0; + + map($sum += $_, @_); + + return (-$sum) & 0xFF; +} + +#---------------------------------------------------------------------------------------------------- + +# RAD50 to ASCII decode + +sub rad2asc (@) { + + my @str = split(//, ' ABCDEFGHIJKLMNOPQRSTUVWXYZ$.%0123456789'); # RAD50 character subset + + my $ascii = ""; + foreach my $rad50 (@_) { + $ascii .= $str[int($rad50/1600)%40] . $str[int($rad50/40)%40] . $str[$rad50%40]; + } + + return $ascii; +} + +#---------------------------------------------------------------------------------------------------- + +# symbol to psect name converter + +sub sym2psect ($$) { + + return sprintf("%02d:%-6s", @_); +} + +#---------------------------------------------------------------------------------------------------- + +# crc computation routine + +sub crc (%) { + + # pass all args by name + my %args = @_; + + # all the crcs we know how to compute + my %crcdat = ( 'CRC-16' => [ 0xA001, 2, 0x0000, 0x0000 ], + 'CRC-32' => [ 0xEDB88320, 4, 0xFFFFFFFF, 0xFFFFFFFF ] ); + + # run next byte thru crc computation, return updated crc + return $args{-table}[($args{-crc}^$args{-byte}) & 0xFF]^($args{-crc}>>8) if exists($args{-byte}); + + # return initial crc value + return $crcdat{$args{-name}}->[2] if exists($args{-init}); + + # return final crc value xored with xorout + return $args{-crc} ^ $crcdat{$args{-name}}->[3] if exists($args{-last}); + + # compute the crc lookup table, return a pointer to it + if (exists($args{-new})) { + my $crctab = []; + my $poly = $crcdat{$args{-name}}->[0]; + foreach my $byte (0..255) { + my $data = $byte; + foreach (1..8) { $data = ($data>>1) ^ ($data&1 ? $poly : 0); } + $$crctab[$byte] = $data; + } + return $crctab; + } +} + +#---------------------------------------------------------------------------------------------------- + +# read a record from the object file + +sub read_rec ($) { + + my ($fh) = @_; + + my ($buf, $cnt, $len, $err) = (0,0,0,0); + my @pre = (); + my @dat = (); + my @suf = (); + + if ($objtype eq 'RT11') { + + # RT-11 object file format consists of blocks, optionally preceded, separated, and + # followed by zeroes. Each block consists of: + # + # 001 --- + # 000 | + # lo(length) | + # hi(length) > 'length' bytes + # databyte1 | + # : | + # databyteN --- + # checksum + # + + # skip over strings of 0x00; exit OK if hit EOF + do { return () unless $cnt = read($fh, $buf, 1); } while (ord($buf) == 0); + + # valid record starts with (1) + $err = 1 unless $cnt == 1 && ord($buf) == 1; + push(@pre, ord($buf)); + + # second byte must be (0) + $cnt = read($fh, $buf, 1); + $err = 2 unless $cnt == 1 && ord($buf) == 0; + push(@pre, ord($buf)); + + # third byte is low byte of record length + $cnt = read($fh, $buf, 1); + $err = 3 unless $cnt == 1; + $len = ord($buf); + push(@pre, ord($buf)); + + # fourth byte is high byte of record length + $cnt = read($fh, $buf, 1); + $err = 4 unless $cnt == 1; + $len += ord($buf)<<8; + push(@pre, ord($buf)); + + # bytes five thru end-1 are data bytes + $cnt = read($fh, $buf, $len-4); + $err = 5 unless $cnt == $len-4 && $len >= 4; + @dat = unpack("C*", $buf); + + # last byte is checksum + $cnt = read($fh, $buf, 1); + $err = 6 unless $cnt == 1; + my $rcv = ord($buf); + push(@suf, ord($buf)); + + # compare rcv'ed checksum vs exp'ed checksum + my $exp = &chksum(0x01, $len>>0, $len>>8, @dat); + warn sprintf("Warning: Bad checksum exp=0x%02X rcv=0x%02X", $exp, $rcv) unless $exp == $rcv; + + } elsif ($objtype eq 'RSX11') { + + # RSX-11 object file format consists of blocks of data in the following format. + # Each block consists of: + # + # lo(length) + # hi(length) + # databyte1 --- + # : | + # : > 'length' bytes + # : | + # databyteN --- + # zeroFill present if length is ODD; else not present + # + + # first byte is low byte of record length + $cnt = read($fh, $buf, 1); + # but exit OK if hit EOF + return () if $cnt == 0; + $err = 10 unless $cnt == 1; + $len = ord($buf); + push(@pre, ord($buf)); + + # second byte is high byte of record length + $cnt = read($fh, $buf, 1); + $err = 11 unless $cnt == 1; + $len += ord($buf)<<8; + push(@pre, ord($buf)); + + # bytes three thru end are data bytes + $cnt = read($fh, $buf, $len); + $err = 12 unless $cnt == $len && $len >= 0; + @dat = unpack("C*", $buf); + + # optional pad byte if length is odd + $cnt = ($len & 1) ? read($fh, $buf, 1) : 2; + $err = 13 unless $cnt == 1 && ord($buf) == 0 || $cnt == 2; + + } + + # output the record if debugging + if ($DEBUG >= 2) { + my $fmt = "%03o"; + my $n = 16; + my $pre = sprintf("RECORD: [%s] ",join(" ",map(sprintf($fmt,$_),@pre))); + printf $LOG "\n\n%s", $pre; + my $k = length($pre); + my @tmp = @dat; + while (@tmp > $n) { + printf $LOG "%s\n%*s", join(" ",map(sprintf($fmt,$_),splice(@tmp,0,$n))), $k, ''; + } + printf $LOG "%s", join(" ",map(sprintf($fmt,$_),@tmp)) if @tmp; + printf $LOG " [%s]\n\n", join(" ",map(sprintf($fmt,$_),@suf)); + } + + # check we have a well formatted record + warn sprintf("Warning: invalid %s object file record format (%d)", $objtype, $err) if $err; + + # all is well, return the record + return @dat; +} + +#---------------------------------------------------------------------------------------------------- + +# get a global symbol target value + +sub get_global ($) { + + my ($sym) = @_; + + # return target value if exists + return $gblsym{$sym}{DEF}{ADDRESS} if exists $gblsym{$sym}{DEF}{ADDRESS}; + + # issue a warning for multiple definition with a different address + warn sprintf("Warning: global symbol undefined: symbol=%s, assuming value of 000000\n", $sym); + + # and return nil + return 0; +} + +#---------------------------------------------------------------------------------------------------- + +# parse an input object file record, update data structures + +sub parse_rec ($$$) { + + my ($file,$pass,$rec) = (@_); + + # type is first byte of record + my $key = $rec->[0]; + + if ($key == 001 && $pass == 1) { # GSD + + # iterate over GSD subrecords + for (my $i = 2; $i < scalar(@$rec); ) { + # GSD records are fixed 8B length all in the same format + my $sym = &rad2asc(($rec->[$i+1]<<8)|($rec->[$i+0]<<0),($rec->[$i+3]<<8)|($rec->[$i+2]<<0)); + my $flg = $rec->[$i+4]; + my $ent = $rec->[$i+5]; + my $val = ($rec->[$i+7]<<8)|($rec->[$i+6]<<0); + my @ent = ('MODULE','CSECT','INTSYM','XFRADR','GBLSYM','PSECT','IDENT','VSECT'); + my $def = undef; + if ($ent == 3) { + # XFRADR + $program{START}{PSECT} = &sym2psect($file,$sym); + $program{START}{VALUE} = $val; + if ($DEBUG) { + printf $LOG "..GSD: type='%-6s'(%03o) name='%s' value=%06o\n", + $ent[$ent], $ent, $program{START}{PSECT}, $program{START}{VALUE}; + } + } elsif ($ent == 4) { + # GBLSYM flags + my $adr = $val + $psect{$psectname}{START}; + $def = $flg&(1<<3) ? "DEF" : "REF"; + if ($def eq "DEF" && exists $gblsym{$sym}{$def} && $adr != $gblsym{$sym}{$def}{ADDRESS}) { + # issue a warning for multiple definition with a different address + warn sprintf("Warning: global symbol redefinition: symbol=%s (address/psect) old=%06o/%s new=%06o/%s -- IGNORING\n", + &trim($sym), $gblsym{$sym}{$def}{ADDRESS}, &trim($gblsym{$sym}{$def}{PSECT}), $adr, &trim($psectname)); + } else { + # define first time only ... ignore any redefinition attempt + $gblsym{$sym}{$def}{FLG}{$flg&(1<<0) ? "WEA" : "STR"}++; + $gblsym{$sym}{$def}{FLG}{$flg&(1<<3) ? "DEF" : "REF"}++; + $gblsym{$sym}{$def}{FLG}{$flg&(1<<5) ? "REL" : "ABS"}++; + $gblsym{$sym}{$def}{PSECT} = $psectname; + $gblsym{$sym}{$def}{VALUE} = $val; + $gblsym{$sym}{$def}{ADDRESS} = $adr; + } + if ($DEBUG) { + printf $LOG "..GSD: type='%-6s'(%03o) name='%s' value=%06o", $ent[$ent], $ent, $sym, $val; + printf $LOG " psect='%s' value=%06o", $gblsym{$sym}{$def}{PSECT}, $gblsym{$sym}{$def}{VALUE}; + printf $LOG " flags=%s\n", join(",", sort(keys(%{$gblsym{$sym}{$def}{FLG}}))); + } + } elsif ($ent == 5) { + # PSECT flags + my $nam = &sym2psect($file,$sym); + $psect[++$psectnumb] = $nam; + $psect{$nam}{FILE} = $file; + $psect{$nam}{NUMBER} = $psectnumb; + $psect{$nam}{FLG}{$flg&(1<<0) ? "GBL" : $flg&(1<<6) ? "GBL" : "LCL"}++; + $psect{$nam}{FLG}{$flg&(1<<2) ? "OVR" : "CON"}++; + $psect{$nam}{FLG}{$flg&(1<<4) ? "R/O" : "R/W"}++; + $psect{$nam}{FLG}{$flg&(1<<5) ? "REL" : "ABS"}++; + $psect{$nam}{FLG}{$flg&(1<<7) ? "D" : "I/D"}++; + $psectname = $nam; + if ($psect{$nam}{FLG}{ABS}) { + # absolute + if ($psect{$nam}{FLG}{CON}) { + # concatenated + warn sprintf("Warning: psect ABS,CON is not supported, psect='%s'\n", $psectname); + } elsif ($psect{$nam}{FLG}{OVR}) { + # overlaid + $psect{$nam}{LENGTH} = $val; + $psect{$nam}{START} = 0; + } + } elsif ($psect{$nam}{FLG}{REL}) { + # relative + if ($psect{$nam}{FLG}{CON}) { + # concatenated + $psect{$nam}{LENGTH} = $val; + $psect{$nam}{START} = $psectaddr & 1 ? ++$psectaddr : $psectaddr; + $psectaddr += $val; + } elsif ($psect{$nam}{FLG}{OVR}) { + # overlaid + warn sprintf("Warning: psect REL,OVR is not supported, psect='%s'\n", $psectname); + } + } + if ($DEBUG) { + printf $LOG "..GSD: type='%-6s'(%03o) name='%s' value=%06o", $ent[$ent], $ent, $nam, $val; + printf $LOG " length=%06o start=%06o", $psect{$nam}{LENGTH}, $psect{$nam}{START}; + printf $LOG " flags=%s\n", join(",", sort(keys(%{$psect{$nam}{FLG}}))); + } + } + $i += 8; + } + + } elsif ($key == 002 && $pass == 1) { # ENDGSD + + # just say we saw it + printf $LOG "..ENDGSD\n\n" if $DEBUG; + + $program{END}{ADDRESS} = 0; + foreach my $nam (sort({$psect{$a}{START} == $psect{$b}{START} ? $psect{$a}{NUMBER} <=> $psect{$b}{NUMBER} : $psect{$a}{START} <=> $psect{$b}{START}} keys(%psect))) { + my $start = $psect{$nam}{START}; + my $length = $psect{$nam}{LENGTH}; + my $end = $length ? $start + $length - 1 : $start; + $program{END}{ADDRESS} = $end if $end > $program{END}{ADDRESS}; + printf $LOG "....PSECT[%02d](%s) START=%06o END=%06o LENGTH=%06o\n", + $psect{$nam}{NUMBER}, $nam, $start, $end, $length if $length && $DEBUG; + } + + printf $LOG "\n" if $DEBUG; + foreach my $nam (sort(keys(%gblsym))) { + if (exists $gblsym{$nam}{DEF}) { + printf $LOG "....GBLSYM(%s) PSECT='%s' VALUE=%06o : ADDRESS=%06o\n", + $nam, $gblsym{$nam}{DEF}{PSECT}, $gblsym{$nam}{DEF}{VALUE}, $gblsym{$nam}{DEF}{ADDRESS} if $DEBUG; + } + } + + if ($program{START}{ADDRESS} == 1) { + $program{START}{ADDRESS} = $program{START}{VALUE} + $psect{$program{START}{PSECT}}{START}; + } + printf $LOG "\n....PROG(ADDRESS) START=%06o END=%06o\n", + $program{START}{ADDRESS}, $program{END}{ADDRESS} if $DEBUG; + + } elsif ($key == 003 && $pass == 2) { # TXT + + # process text record + my $off = ($rec->[3]<<8)|($rec->[2]<<0); + my $len = scalar(@$rec)-4; + my $base = $psect{$psectname}{START}; + my $adr = ($base + $off) & $adrmsk; + foreach my $i (1..$len) { $mem[$adr+$i-1] = $rec->[4+$i-1]; } + if ($DEBUG) { + printf $LOG "..TXT OFFSET=%06o LENGTH=%o BASE=%06o PSECTNAME='%s'\n", $off, $len, $base, $psectname; + for (my $i = 0; $i < $len; $i += 2) { + printf $LOG " %06o: ", ($adr+$i)&~1 if $i%8 == 0; + printf $LOG " %03o...", $mem[$adr+$i++] if ($adr+$i)&1; + printf $LOG " %06o", ($mem[$adr+$i+1]<<8)|($mem[$adr+$i+0]<<0) if $i < $len-1; + printf $LOG " ...%03o", $mem[$adr+$i] if $i == $len-1; + printf $LOG "\n" if $i%8 >= 6 && $i < $len-2; + } + printf $LOG "\n"; + } + $adrmin = $adr if $adrmin eq '' || $adr < $adrmin; + $adrmax = $adr+$len-1 if $adrmax eq '' || $adr+$len-1 > $adrmax; + $textaddr = $adr; + + } elsif ($key == 004 && $pass == 2) { # RLD + + # iterate over RLD subrecords + for (my $i = 2; $i < scalar(@$rec); ) { + # first byte is entry type and flags + my $ent = $rec->[$i+0] & 0x7F; # entry type + my $flg = $rec->[$i+0] & 0x80; # modification flag (0=word, 1=byte) + # process an entry + if ($ent == 001) { + # internal relocation ... OK + my $dis = $rec->[$i+1]; + my $con = ($rec->[$i+3]<<8)|($rec->[$i+2]<<0); + # process + my $adr = $adrmsk & ($textaddr + $dis - 4); + my $val = $datmsk & ($psect{$psectname}{START} + $con); + # store + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + # print + printf $LOG "..RLD(IR): adr=%06o val=%06o ; dis=%06o con=%06o\n", + $adr, $val, $dis, $con if $DEBUG; + $i += 4; + } elsif ($ent == 003) { + # internal displaced relocation ... OK + my $dis = $rec->[$i+1]; + my $con = ($rec->[$i+3]<<8)|($rec->[$i+2]<<0); + # process + my $adr = $adrmsk & ($textaddr + $dis - 4); + my $val = $datmsk & ($con - ($adr+2)); + # store + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + # print + printf $LOG "..RLD(IDR): adr=%06o val=%06o ; dis=%06o con=%06o\n", + $adr, $val, $dis, $con if $DEBUG; + $i += 4; + } elsif ($ent == 012) { + # psect relocation ... OK + my $dis = $rec->[$i+1]; + my $nam = &sym2psect($file,&rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0))); + # process + my $adr = $adrmsk & ($textaddr + $dis - 4); + my $val = $datmsk & ($psect{$nam}{START}); + # store + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + # print + printf $LOG "..RLD(PR): adr=%06o val=%06o ; dis=%06o nam='%s'\n", + $adr, $val, $dis, $nam if $DEBUG; + $i += 6; + } elsif ($ent == 014) { + # psect displaced relocation ... OK + my $dis = $rec->[$i+1]; + my $nam = &sym2psect($file,&rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0))); + # process + my $adr = $adrmsk & ($textaddr + $dis - 4); + my $val = $datmsk & ($psect{$nam}{START} - ($adr+2)); + # store + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + # print + printf $LOG "..RLD(PDR): adr=%06o val=%06o ; dis=%06o nam='%s'\n", + $adr, $val, $dis, $nam if $DEBUG; + $i += 6; + } elsif ($ent == 015) { + # psect additive relocation ... OK + my $dis = $rec->[$i+1]; + my $nam = &sym2psect($file,&rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0))); + my $con = ($rec->[$i+7]<<8)|($rec->[$i+6]<<0); + # process + my $adr = $adrmsk & ($textaddr + $dis - 4); + my $val = $datmsk & ($psect{$nam}{START} + $con); + # store + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + # print + printf $LOG "..RLD(PAR): adr=%06o val=%06o ; dis=%06o con=%06o nam='%s'\n", + $adr, $val, $dis, $con, $nam if $DEBUG; + $i += 8; + } elsif ($ent == 016) { + # psect additive displaced relocation ... OK + my $dis = $rec->[$i+1]; + my $nam = &sym2psect($file,&rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0))); + my $con = ($rec->[$i+7]<<8)|($rec->[$i+6]<<0); + # process + my $adr = $adrmsk & ($textaddr + $dis - 4); + my $val = $datmsk & ($psect{$nam}{START} + $con - ($adr+2)); + # store + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + # print + printf $LOG "..RLD(PADR): adr=%06o val=%06o ; dis=%06o con=%06o nam='%s'\n", + $adr, $val, $dis, $con, $nam if $DEBUG; + $i += 8; + } elsif ($ent == 002) { + # global relocation ... OK + my $dis = $rec->[$i+1]; + my $sym = &rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0)); + # process + my $adr = $adrmsk & ($textaddr + $dis - 4); + my $val = $datmsk & (&get_global($sym)); + # store + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + # print + printf $LOG "..RLD(GR): adr=%06o val=%06o ; dis=%06o sym='%s'\n", + $adr, $val, $dis, $sym if $DEBUG; + $i += 6; + } elsif ($ent == 004) { + # global displaced relocation ... OK + my $dis = $rec->[$i+1]; + my $sym = &rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0)); + # process + my $adr = $adrmsk & ($textaddr + $dis - 4); + my $val = $datmsk & (&get_global($sym) - ($adr+2)); + # store + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + # print + printf $LOG "..RLD(GDR): adr=%06o val=%06o ; dis=%06o sym='%s'\n", + $adr, $val, $dis, $sym if $DEBUG; + $i += 6; + } elsif ($ent == 005) { + # global additive relocation ... OK + my $dis = $rec->[$i+1]; + my $sym = &rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0)); + my $con = ($rec->[$i+7]<<8)|($rec->[$i+6]<<0); + # process + my $adr = $adrmsk & ($textaddr + $dis - 4); + my $val = $datmsk & (&get_global($sym) + $con); + # store + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + # print + printf $LOG "..RLD(GAR): adr=%06o val=%06o ; dis=%06o con=%06o sym='%s'\n", + $adr, $val, $dis, $con, $sym if $DEBUG; + $i += 8; + } elsif ($ent == 006) { + # global additive displaced relocation ... OK + my $dis = $rec->[$i+1]; + my $sym = &rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0)); + my $con = ($rec->[$i+7]<<8)|($rec->[$i+6]<<0); + # process + my $adr = $adrmsk & ($textaddr + $dis - 4); + my $val = $datmsk & (&get_global($sym) + $con - ($adr+2)); + # store + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + # print + printf $LOG "..RLD(GADR): adr=%06o val=%06o ; dis=%06o con=%06o sym='%s'\n", + $adr, $val, $dis, $con, $sym if $DEBUG; + $i += 8; + } elsif ($ent == 007) { + # location counter definition ... OK + my $dis = $rec->[$i+1]; + my $nam = &sym2psect($file,&rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0))); + my $con = ($rec->[$i+7]<<8)|($rec->[$i+6]<<0); + # process + $psectname = $nam; + $textaddr = $datmsk & ($con); + # print + printf $LOG "..RLD(LCD): adr=%06o ; dis=%06o con=%06o nam='%s'\n", + $textaddr, $dis, $con, $nam if $DEBUG; + $i += 8; + } elsif ($ent == 010) { + # location counter modification ... OK + my $dis = $rec->[$i+1]; + my $con = ($rec->[$i+3]<<8)|($rec->[$i+2]<<0); + # process + $textaddr = $datmsk & ($con); + # print + printf $LOG "..RLD(LCM): adr=%06o ; dis=%06o con=%06o\n", + $textaddr, $dis, $con if $DEBUG; + $i += 4; + } elsif ($ent == 011) { + # program limits ... OK, mostly + my $dis = $rec->[$i+1]; + # process + my $adr = $adrmsk & ($textaddr + $dis - 4); + my $val = $datmsk & ( 01000 ); # make this up, no easy way to compute it + # store + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + # print + printf $LOG "..RLD(LIM1): adr=%06o val=%06o ; dis=%06o\n", + $adr, $val, $dis if $DEBUG; + # process + $dis += 2; + $adr += 2; + $val = $datmsk & ($program{END}{ADDRESS}); + # store + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + # print + printf $LOG "..RLD(LIM2): adr=%06o val=%06o ; dis=%06o\n", + $adr, $val, $dis if $DEBUG; + $i += 2; + } elsif ($ent == 017) { + # complex relocation ... OK + my $dis = $rec->[$i+1]; + my $nam = &sym2psect($file,'. ABS.'); + my $con = 0; + # process + my $adr = 0; + my $loc = 0; + my $val = 0; + my $opc = ""; + my @stk = (); + my $dun = 0; + for ($i += 2; !$dun; $i += 1) { + if ($rec->[$i] == 000) { + # NOP do nothing + $opc = "NOP"; + } elsif ($rec->[$i] == 001) { + # ADD : pop + pop => push + my @arg = splice(@stk,-2,2); + push(@stk, $arg[0] + $arg[1]); + $opc = "ADD"; + } elsif ($rec->[$i] == 002) { + # SUB : pop - pop => push + my @arg = splice(@stk,-2,2); + push(@stk, $arg[0] - $arg[1]); + $opc = "SUB"; + } elsif ($rec->[$i] == 003) { + # MUL : pop * pop => push + my @arg = splice(@stk,-2,2); + push(@stk, $arg[0] * $arg[1]); + $opc = "MUL"; + } elsif ($rec->[$i] == 004) { + # DIV : pop / pop => push + my @arg = splice(@stk,-2,2); + push(@stk, $arg[1] == 0 ? 0 : int($arg[0] / $arg[1])); + $opc = "DIV"; + } elsif ($rec->[$i] == 005) { + # AND : pop & pop => push + my @arg = splice(@stk,-2,2); + push(@stk, $arg[0] & $arg[1]); + $opc = "AND"; + } elsif ($rec->[$i] == 006) { + # IOR : pop | pop => push + my @arg = splice(@stk,-2,2); + push(@stk, $arg[0] | $arg[1]); + $opc = "IOR"; + } elsif ($rec->[$i] == 007) { + # XOR : pop ^ pop => push + my @arg = splice(@stk,-2,2); + push(@stk, $arg[0] ^ $arg[1]); + $opc = "XOR"; + } elsif ($rec->[$i] == 010) { + # NEG : pop - => push + my @arg = splice(@stk,-1,1); + push(@stk, -$arg[0]); + $opc = "NEG"; + } elsif ($rec->[$i] == 011) { + # COM : pop ~ => push + my @arg = splice(@stk,-1,1); + push(@stk, ~$arg[0]); + $opc = "COM"; + } elsif ($rec->[$i] == 012) { + # STO : pop => store @ address + my @arg = splice(@stk,-1,1); + $adr = $adrmsk & ($textaddr + $dis - 4); + $val = $datmsk & ($arg[0]); + $opc = "STO"; + $dun = 1; + } elsif ($rec->[$i] == 013) { + # STO : pop => store @ address + disp + my @arg = splice(@stk,-1,1); + $adr = $adrmsk & ($textaddr + $dis - 4); + $val = $datmsk & ($arg[0] - ($adr+2)); + $opc = "STO+DIS"; + $dun = 1; + } elsif ($rec->[$i] == 016) { + # FET : global => push + $nam = &rad2asc(($rec->[$i+2]<<8)|($rec->[$i+1]<<0),($rec->[$i+4]<<8)|($rec->[$i+3]<<0)); + $con = &get_global($nam); + push(@stk, $con); + $opc = sprintf("GLB[%s]=(%o)", &trim($nam), $con); + $i += 4; + } elsif ($rec->[$i] == 017) { + # FET : local => push + $nam = $psect[$rec->[$i+1]]; + $con = ($rec->[$i+3]<<8) | ($rec->[$i+2]<<0); + $loc = $psect{$nam}{START} + $con; + push(@stk, $loc); + $opc = sprintf("FET[%s+%o]=(%o)", &trim($nam), $con, $loc); + $i += 3; + } elsif ($rec->[$i] == 020) { + # CONstant : value => push + $con = ($rec->[$i+2]<<8) | ($rec->[$i+1]<<0); + push(@stk, $con); + $opc = "CON"; + $i += 2; + } + $stk[-1] = $datmsk & $stk[-1] if @stk; + printf $LOG "....OPC=%-20s STK=(%s)\n", $opc, join(",",map(sprintf("%o",$_),@stk)) if $DEBUG; + } + # print + printf $LOG "..RLD(CPXR): adr=%06o val=%06o ; dis=%06o\n", $adr, $val, $dis if $DEBUG; + # store + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + } else { + warn sprintf("Warning: Unknown RLD entry 0%o (%d)", $ent, $ent); + } + } + + } elsif ($key == 005) { # ISD + + # ignore + printf $LOG "..ISD: ignored\n" if $DEBUG; + + } elsif ($key == 006) { # ENDMOD + + # just say we saw it + printf $LOG "..ENDMOD\n\n\n" if $DEBUG; + + } elsif ($key == 007) { # LIBHDR + + # ignore + printf $LOG "..LIBHDR: ignored\n" if $DEBUG; + + } elsif ($key == 010) { # LIBEND + + # ignore + printf $LOG "..LIBEND: ignored\n" if $DEBUG; + + } elsif ($key == 000 || $key >= 011) { # unknown + + # invalid record type in the object file + warn sprintf("Warning: unknown record type 0%o (%d)", $key, $key); + + } + + return; +} + +#---------------------------------------------------------------------------------------------------- + +# the end