├── mfm ├── setup_mfm_write ├── inc │ ├── version.h │ ├── board.h │ ├── analyze.h │ ├── deltas_read.h │ ├── crc_ecc.h │ ├── msg.h │ ├── parse_cmdline.h │ ├── pru_setup.h │ ├── drive.h │ ├── cmd_write.h │ ├── cmd.h │ └── emu_tran_file.h ├── ext2emu_doc.odt ├── mfm_write_doc.odt ├── mfm_write_doc.pdf ├── mfm_read_util_doc.odt ├── mfm_read_util_doc.pdf ├── mfm_r_revc.bbio ├── mfm_w_revc.bbio ├── mfm_r_revab.bbio ├── mfm_w_revab.bbio ├── copy_emu.c ├── deltas_read_file.c ├── drive_file.c ├── msg.c ├── Makefile ├── README ├── mfm_read-00A0.dts ├── mfm_write-00A0.dts ├── mfm_read-00C0.dts ├── mfm_write-00C0.dts ├── mfm_write_doc.html ├── setup_mfm_read ├── prucode.hp ├── board.c ├── mfm_write.c ├── ext2emu_doc.html ├── mfm_read.c ├── crc_ecc.c ├── drive_write.c ├── drive_operations.p ├── parse_cmdline_write.c └── deltas_read.c ├── util ├── etc │ ├── sudoers.d │ │ └── debian │ ├── modprobe.d │ │ └── uio_pruss.conf │ ├── udev │ │ └── rules.d │ │ │ └── zz-mfm-emu.rules │ └── kernel │ │ └── postinst.d │ │ └── zzz-mfm-emu ├── bash_aliases ├── getdtb.sh ├── mfm_emu.service ├── mfm_emu.service.3.x ├── Makefile └── mfm_emu.conf ├── emu ├── mfm_emu_doc.odt ├── mfm_emu_doc.pdf ├── mfm_emu_revab.bbio ├── mfm_emu_revc.bbio ├── recover_deltas ├── inc │ ├── parse_cmdline.h │ └── cmd.h ├── start_mfm_emu ├── README ├── Makefile ├── setup_emu ├── emu-00A0.dts ├── emu-00C0.dts ├── prucode.hp └── mfm_emu_doc.html ├── contrib ├── Makefile └── RLL │ └── hack_rll.py ├── powerfail ├── powerfail_doc.odt ├── powerfail_doc.pdf ├── README ├── inc │ └── parse_cmdline.h ├── Makefile ├── powerfail_doc.html ├── parse_cmdline.c └── powerfail.c ├── README.md ├── .gitignore └── Makefile /mfm/setup_mfm_write: -------------------------------------------------------------------------------- 1 | setup_mfm_read -------------------------------------------------------------------------------- /mfm/inc/version.h: -------------------------------------------------------------------------------- 1 | #define VERSION "4.41" 2 | -------------------------------------------------------------------------------- /util/etc/sudoers.d/debian: -------------------------------------------------------------------------------- 1 | debian ALL=NOPASSWD: ALL 2 | -------------------------------------------------------------------------------- /util/bash_aliases: -------------------------------------------------------------------------------- 1 | PATH=$PATH:/opt/mfm/mfm:/opt/mfm/emu:/opt/mfm/powerfail 2 | -------------------------------------------------------------------------------- /emu/mfm_emu_doc.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgesswein/mfm/HEAD/emu/mfm_emu_doc.odt -------------------------------------------------------------------------------- /emu/mfm_emu_doc.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgesswein/mfm/HEAD/emu/mfm_emu_doc.pdf -------------------------------------------------------------------------------- /mfm/ext2emu_doc.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgesswein/mfm/HEAD/mfm/ext2emu_doc.odt -------------------------------------------------------------------------------- /mfm/mfm_write_doc.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgesswein/mfm/HEAD/mfm/mfm_write_doc.odt -------------------------------------------------------------------------------- /mfm/mfm_write_doc.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgesswein/mfm/HEAD/mfm/mfm_write_doc.pdf -------------------------------------------------------------------------------- /contrib/Makefile: -------------------------------------------------------------------------------- 1 | #Does nothing but make update not complain about contrib directory 2 | all: 3 | -------------------------------------------------------------------------------- /mfm/mfm_read_util_doc.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgesswein/mfm/HEAD/mfm/mfm_read_util_doc.odt -------------------------------------------------------------------------------- /mfm/mfm_read_util_doc.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgesswein/mfm/HEAD/mfm/mfm_read_util_doc.pdf -------------------------------------------------------------------------------- /powerfail/powerfail_doc.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgesswein/mfm/HEAD/powerfail/powerfail_doc.odt -------------------------------------------------------------------------------- /powerfail/powerfail_doc.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgesswein/mfm/HEAD/powerfail/powerfail_doc.pdf -------------------------------------------------------------------------------- /util/etc/modprobe.d/uio_pruss.conf: -------------------------------------------------------------------------------- 1 | softdep uio_pdrv_genirq pre: uio_pruss 2 | options uio_pruss extram_pool_sz=1048576 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mfm 2 | MFM emulator code 3 | 4 | This code is for a MFM hard drive reader and emulator. 5 | Project home page is at http://www.pdp8online.com/mfm/ 6 | -------------------------------------------------------------------------------- /util/etc/udev/rules.d/zz-mfm-emu.rules: -------------------------------------------------------------------------------- 1 | # /etc/udev/rules.d/zz-mfm-emu.rules 2 | # 3 | # Corrects UIO permissions on the BB so non-root users in the gpio group can mmap UIO memory 4 | # 5 | KERNEL=="uio*", MODE="0660", GROUP="gpio" 6 | -------------------------------------------------------------------------------- /mfm/inc/board.h: -------------------------------------------------------------------------------- 1 | /* 2 | * board.h 3 | * 4 | * Created on: August 1, 2015 5 | * Author: djg 6 | */ 7 | 8 | #ifndef BOARD_H_ 9 | #define BOARD_H_ 10 | 11 | void board_initialize(void); 12 | int board_get_revision(void); 13 | int board_set_restore_max_cpu_speed(int restore); 14 | #endif /* BOARD_H_ */ 15 | -------------------------------------------------------------------------------- /util/getdtb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | vernum=$(echo $1 | cut -f 1-2 -d.) 3 | case "$1" in 4 | "3."*) exit ;; 5 | *"-ti"*) dtbdir="dtb-${vernum}-ti" ;; 6 | *"-bone"*) dtbdir="dtb-${vernum}-bone" ;; 7 | *) dtbdir="dtb-${vernum}" 8 | esac 9 | echo $dtbdir 10 | -------------------------------------------------------------------------------- /mfm/inc/analyze.h: -------------------------------------------------------------------------------- 1 | /* 2 | * read_analyze.h 3 | * 4 | * Created on: Dec 23, 2013 5 | * Author: djg 6 | */ 7 | 8 | #ifndef READ_ANALYZE_H_ 9 | #define READ_ANALYZE_H_ 10 | 11 | void analyze_disk(DRIVE_PARAMS *drive_params, void *deltas, int max_deltas, 12 | int use_file); 13 | 14 | #endif /* READ_ANALYZE_H_ */ 15 | -------------------------------------------------------------------------------- /powerfail/README: -------------------------------------------------------------------------------- 1 | This code is intended to be used to cleanly shut down the MFM disk emulator 2 | when power is turned off. A makefile is provided to build powerfail. 3 | 4 | The site for this code is http://www.pdp8online.com/mfm/mfm.shtml 5 | 6 | All the code I created is released under GPLv3 or later. 7 | 8 | David Gesswein 9 | djg@pdp8online.com 10 | -------------------------------------------------------------------------------- /util/mfm_emu.service: -------------------------------------------------------------------------------- 1 | #execute systemctl --system daemon-reload after changing 2 | 3 | [Unit] 4 | Description=MFM Emulator 5 | After=syslog.target local-fs.target 6 | Before=sysinit.target shutdown.target 7 | DefaultDependencies=no 8 | Conflicts=shutdown.target 9 | 10 | [Service] 11 | ExecStart=/opt/mfm/emu/start_mfm_emu 12 | 13 | #StandardOutput=journal+console 14 | StandardOutput=null 15 | StandardError=journal+console 16 | 17 | [Install] 18 | WantedBy=sysinit.target 19 | -------------------------------------------------------------------------------- /util/mfm_emu.service.3.x: -------------------------------------------------------------------------------- 1 | #execute systemctl --system daemon-reload after changing 2 | 3 | [Unit] 4 | Description=MFM Emulator 5 | After=syslog.target fsck-root.service fsck@.service 6 | Before=sysinit.target shutdown.target 7 | DefaultDependencies=no 8 | Conflicts=shutdown.target 9 | 10 | [Service] 11 | ExecStart=/root/emu/start_mfm_emu 12 | 13 | #StandardOutput=journal+console 14 | StandardOutput=null 15 | StandardError=journal+console 16 | 17 | [Install] 18 | WantedBy=sysinit.target 19 | -------------------------------------------------------------------------------- /mfm/inc/deltas_read.h: -------------------------------------------------------------------------------- 1 | /* 2 | * read_deltas.h 3 | * 4 | * Created on: Dec 23, 2013 5 | * Author: djg 6 | */ 7 | 8 | #ifndef READ_DELTAS_H_ 9 | #define READ_DELTAS_H_ 10 | 11 | void deltas_start_read(int cyl, int head); 12 | void deltas_start_thread(DRIVE_PARAMS *drive_params); 13 | void deltas_stop_thread(); 14 | void *deltas_setup(int ddr_mem_size); 15 | int deltas_wait_read_finished(); 16 | int deltas_get_count(int cur_delta); 17 | void deltas_update_count(int num_deltas_in, int streaming_in); 18 | 19 | #endif /* READ_DELTAS_H_ */ 20 | -------------------------------------------------------------------------------- /mfm/mfm_r_revc.bbio: -------------------------------------------------------------------------------- 1 | P8_11 mfm 2 | P8_12 mfm_rw 3 | P8_13 mfm_rw 4 | P8_14 mfm_rw 5 | P8_15 mfm 6 | P8_16 mfm_rw 7 | P8_17 mfm_rw 8 | P8_19 mfm_rw 9 | P8_26 default 10 | P8_31 mfm_rw 11 | P8_32 mfm_rw 12 | P8_33 mfm_rw 13 | P8_35 mfm_rw 14 | P8_45 default 15 | P9_11 mfm 16 | P9_12 mfm 17 | P9_13 mfm 18 | P9_14 mfm 19 | P9_15 mfm_emu 20 | P9_16 mfm_rw 21 | P9_17 mfm_rw 22 | P9_18 mfm_rw 23 | P9_21 mfm_rw 24 | P9_22 mfm_rw 25 | P9_24 mfm_gpio 26 | P9_25 mfm_rw 27 | P9_26 default 28 | P9_27 mfm_rw 29 | P9_28 mfm_rw 30 | P9_29 mfm_rw 31 | P9_30 mfm_rw 32 | P9_31 mfm_r 33 | P9_91 mfm_rw 34 | P9_92 mfm_rw 35 | -------------------------------------------------------------------------------- /mfm/mfm_w_revc.bbio: -------------------------------------------------------------------------------- 1 | P8_11 mfm 2 | P8_12 mfm_rw 3 | P8_13 mfm_rw 4 | P8_14 mfm_rw 5 | P8_15 mfm 6 | P8_16 mfm_rw 7 | P8_17 mfm_rw 8 | P8_19 mfm_rw 9 | P8_26 default 10 | P8_31 mfm_rw 11 | P8_32 mfm_rw 12 | P8_33 mfm_rw 13 | P8_35 mfm_rw 14 | P8_45 default 15 | P9_11 mfm 16 | P9_12 mfm 17 | P9_13 mfm 18 | P9_14 mfm 19 | P9_15 mfm_emu 20 | P9_16 mfm_rw 21 | P9_17 mfm_rw 22 | P9_18 mfm_rw 23 | P9_21 mfm_rw 24 | P9_22 mfm_rw 25 | P9_24 mfm_gpio 26 | P9_25 mfm_rw 27 | P9_26 default 28 | P9_27 mfm_rw 29 | P9_28 mfm_rw 30 | P9_29 mfm_rw 31 | P9_30 mfm_rw 32 | P9_31 mfm_w 33 | P9_91 mfm_rw 34 | P9_92 mfm_rw 35 | -------------------------------------------------------------------------------- /mfm/mfm_r_revab.bbio: -------------------------------------------------------------------------------- 1 | P8_11 mfm 2 | P8_12 mfm_rw 3 | P8_13 mfm_rw 4 | P8_14 mfm_rw 5 | P8_15 mfm 6 | P8_16 mfm_ab 7 | P8_17 mfm_rw 8 | P8_19 mfm_rw 9 | P8_26 default 10 | P8_31 mfm_rw 11 | P8_32 mfm_rw 12 | P8_33 mfm_rw 13 | P8_35 mfm_rw 14 | P8_45 default 15 | P9_11 mfm_rw_ab 16 | P9_12 mfm 17 | P9_13 mfm 18 | P9_14 mfm 19 | P9_15 mfm_emu 20 | P9_16 gpio_pd 21 | P9_17 mfm_rw 22 | P9_18 mfm_rw 23 | P9_21 mfm_rw 24 | P9_22 mfm_rw 25 | P9_24 mfm_pru 26 | P9_25 mfm_rw 27 | P9_26 default 28 | P9_27 mfm_rw 29 | P9_28 mfm_rw_ab 30 | P9_29 mfm_rw 31 | P9_30 mfm_rw 32 | P9_31 mfm_r 33 | P9_91 mfm_rw 34 | P9_92 mfm_rw 35 | -------------------------------------------------------------------------------- /mfm/mfm_w_revab.bbio: -------------------------------------------------------------------------------- 1 | P8_11 mfm 2 | P8_12 mfm_rw 3 | P8_13 mfm_rw 4 | P8_14 mfm_rw 5 | P8_15 mfm 6 | P8_16 mfm_ab 7 | P8_17 mfm_rw 8 | P8_19 mfm_rw 9 | P8_26 default 10 | P8_31 mfm_rw 11 | P8_32 mfm_rw 12 | P8_33 mfm_rw 13 | P8_35 mfm_rw 14 | P8_45 default 15 | P9_11 mfm_rw_ab 16 | P9_12 mfm 17 | P9_13 mfm 18 | P9_14 mfm 19 | P9_15 mfm_emu 20 | P9_16 gpio_pd 21 | P9_17 mfm_rw 22 | P9_18 mfm_rw 23 | P9_21 mfm_rw 24 | P9_22 mfm_rw 25 | P9_24 mfm_pru 26 | P9_25 mfm_rw 27 | P9_26 default 28 | P9_27 mfm_rw 29 | P9_28 mfm_rw_ab 30 | P9_29 mfm_rw 31 | P9_30 mfm_rw 32 | P9_31 mfm_w 33 | P9_91 mfm_rw 34 | P9_92 mfm_rw 35 | -------------------------------------------------------------------------------- /emu/mfm_emu_revab.bbio: -------------------------------------------------------------------------------- 1 | P8_11 mfm 2 | P8_12 mfm_emu 3 | P8_13 mfm_emu 4 | P8_14 mfm_emu 5 | P8_15 mfm 6 | P8_16 mfm_ab 7 | P8_17 mfm_emu 8 | P8_19 mfm_emu 9 | P8_26 mfm_emu 10 | P8_31 mfm_emu 11 | P8_32 mfm_emu 12 | P8_33 mfm_emu 13 | P8_35 mfm_emu 14 | P8_45 mfm_emu 15 | P9_11 mfm 16 | P9_12 mfm 17 | P9_13 mfm 18 | P9_14 mfm 19 | P9_15 mfm_emu 20 | P9_16 default 21 | P9_17 mfm_emu 22 | P9_18 mfm_emu 23 | P9_21 mfm_emu 24 | P9_22 mfm_emu 25 | P9_24 mfm_gpio 26 | P9_25 mfm_emu 27 | P9_26 mfm_emu 28 | P9_27 mfm_emu 29 | P9_28 mfm_emu 30 | P9_29 mfm_emu 31 | P9_30 mfm_emu 32 | P9_31 mfm_emu 33 | P9_91 mfm_emu 34 | P9_92 mfm_emu 35 | -------------------------------------------------------------------------------- /emu/mfm_emu_revc.bbio: -------------------------------------------------------------------------------- 1 | P8_11 mfm 2 | P8_12 mfm_emu 3 | P8_13 mfm_emu 4 | P8_14 mfm_emu 5 | P8_15 mfm 6 | P8_16 mfm_emu 7 | P8_17 mfm_emu 8 | P8_19 mfm_emu 9 | P8_26 mfm_emu 10 | P8_31 mfm_emu 11 | P8_32 mfm_emu 12 | P8_33 mfm_emu 13 | P8_35 mfm_emu 14 | P8_45 mfm_emu 15 | P9_11 mfm 16 | P9_12 mfm 17 | P9_13 mfm 18 | P9_14 mfm 19 | P9_15 mfm_emu 20 | P9_16 mfm_emu 21 | P9_17 mfm_emu 22 | P9_18 mfm_emu 23 | P9_21 mfm_emu 24 | P9_22 mfm_emu 25 | P9_24 mfm_pru 26 | P9_25 mfm_emu 27 | P9_26 mfm_emu 28 | P9_27 mfm_emu 29 | P9_28 mfm_emu 30 | P9_29 mfm_emu 31 | P9_30 mfm_emu 32 | P9_31 mfm_emu 33 | P9_91 mfm_emu 34 | P9_92 mfm_emu 35 | -------------------------------------------------------------------------------- /powerfail/inc/parse_cmdline.h: -------------------------------------------------------------------------------- 1 | /* 2 | * parse_cmdline.h 3 | * 4 | * Created on: Dec 21, 2013 5 | * Author: djg 6 | */ 7 | 8 | #ifndef PARSE_CMDLINE_H_ 9 | #define PARSE_CMDLINE_H_ 10 | 11 | #define MAX_HEAD 16 12 | 13 | #define MAX_DRIVES 2 14 | // This is the main structure defining the drive characteristics 15 | typedef struct { 16 | char *command; 17 | double scale; 18 | double threshold; 19 | double wait; 20 | char *powercmd; 21 | int debug; 22 | } DRIVE_PARAMS; 23 | void parse_print_cmdline(DRIVE_PARAMS *drive_params); 24 | void parse_cmdline(int argc, char *argv[], DRIVE_PARAMS *drive_params); 25 | #endif /* PARSE_CMDLINE_H_ */ 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | *.bin 7 | 8 | # Precompiled Headers 9 | *.gch 10 | *.pch 11 | 12 | # Libraries 13 | *.lib 14 | *.a 15 | *.la 16 | *.lo 17 | 18 | # Shared objects (inc. Windows DLLs) 19 | *.dll 20 | *.so 21 | *.so.* 22 | *.dylib 23 | 24 | # Executables 25 | *.exe 26 | *.out 27 | *.app 28 | *.i*86 29 | *.x86_64 30 | *.hex 31 | 32 | # Debug files 33 | *.dSYM/ 34 | 35 | # Optional build dependencies 36 | am335x_pru_package-master 37 | 38 | # Lstings 39 | prucode*.txt 40 | logfile.txt 41 | mfm_write*.txt 42 | 43 | # Programs 44 | emu/mfm_emu 45 | mfm/mfm_read 46 | mfm/mfm_util 47 | mfm/mfm_write 48 | mfm/find_crc_info 49 | powerfail/powerfail 50 | mfm/ext2emu 51 | -------------------------------------------------------------------------------- /util/etc/kernel/postinst.d/zzz-mfm-emu: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | version="$1" 4 | 5 | # passing the kernel version is required 6 | if [ -z "${version}" ] ; then 7 | echo >&2 "W: zz-uenv_txt: ${DPKG_MAINTSCRIPT_PACKAGE:-kernel package} did not pass a version number" 8 | exit 2 9 | fi 10 | 11 | vernum=$(echo $version | cut -f 1-2 -d.) 12 | case "$version" in 13 | "3."*) exit ;; 14 | *"-ti"*) dtbdir="dtb-${vernum}-ti" ;; 15 | *"-bone"*) dtbdir="dtb-${vernum}-bone" ;; 16 | *) dtbdir="dtb-${vernum}" 17 | esac 18 | 19 | cd /opt/source/${dtbdir} 20 | git fetch --depth 1 || true 21 | git reset --hard origin/v${vernum}.x-ti-unified || true 22 | KERNEL_VERSION=${version} make clean all_arm install_arm 23 | # this will get us to userspace faster 24 | rm -rf "/boot/initrd.img-${version}" 25 | -------------------------------------------------------------------------------- /mfm/inc/crc_ecc.h: -------------------------------------------------------------------------------- 1 | 2 | // CRC/ECC module definitions 3 | #ifndef CRC_ECC_H_ 4 | #define CRC_ECC_H_ 5 | 6 | // This contains the polynomial, polynomial length, CRC initial value, 7 | // and maximum span for ECC correction. Use span 0 for no ECC correction. 8 | typedef struct { 9 | uint64_t init_value; 10 | uint64_t poly; 11 | uint32_t length; 12 | uint32_t ecc_max_span; 13 | } CRC_INFO; 14 | 15 | // Routine prototypes 16 | uint64_t crc_revbits(uint64_t v, int length); 17 | uint64_t crc64(uint8_t bytes[], int num_bytes, CRC_INFO *crc_info); 18 | int ecc64(uint8_t bytes[], int num_bytes, uint64_t syndrome, 19 | CRC_INFO *crc_info); 20 | uint64_t checksum64(uint8_t *bytes, int num_bytes, CRC_INFO *crc_info); 21 | uint64_t eparity64(uint8_t *bytes, int num_bytes, CRC_INFO *crc_info); 22 | #endif /* CRC_ECC_H_ */ 23 | -------------------------------------------------------------------------------- /util/Makefile: -------------------------------------------------------------------------------- 1 | SUDO = /usr/bin/sudo 2 | UNAME_R = $(shell uname -r) 3 | SYSTEMCTL = /bin/systemctl 4 | SYSTEMD_UNIT = /etc/systemd/system/mfm_emu.service 5 | SYSTEMD_UNIT_CONF = /etc/mfm_emu.conf 6 | BASH_ALIASES = /home/debian/.bash_aliases 7 | 8 | all clean: 9 | 10 | install: | $(SYSTEMD_UNIT) 11 | $(SUDO) cp -r etc/* /etc 12 | pwd 13 | $(SUDO) cp MFM-EMU-1.dts /opt/source/$(shell ./getdtb.sh $(UNAME_R))/src/arm/overlays 14 | $(SUDO) chmod +x /etc/kernel/postinst.d/zzz-mfm-emu 15 | $(SUDO) /etc/kernel/postinst.d/zzz-mfm-emu $(UNAME_R) 16 | 17 | $(SYSTEMD_UNIT): | $(SYSTEMD_UNIT_CONF) 18 | $(SUDO) cp mfm_emu.service /etc/systemd/system/mfm_emu.service 19 | $(SUDO) $(SYSTEMCTL) --system daemon-reload 20 | 21 | $(SYSTEMD_UNIT_CONF): 22 | $(SUDO) cp mfm_emu.conf $@ 23 | 24 | $(BASH_ALIASES): 25 | $(SUDO) cp bash_aliases $@ 26 | -------------------------------------------------------------------------------- /mfm/inc/msg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * msg.h 3 | * 4 | * Created on: Dec 20, 2013 5 | * Author: djg 6 | * 11/09/14 DJG Added new function 7 | * 09/06/14 DJG Added extra class of messages 8 | */ 9 | 10 | #ifndef MSG_H_ 11 | #define MSG_H_ 12 | 13 | 14 | #define MSG_DEBUG_DATA 0x001 15 | #define MSG_DEBUG 0x002 16 | #define MSG_INFO 0x004 17 | #define MSG_PROGRESS 0x008 18 | #define MSG_ERR 0x010 19 | #define MSG_INFO_SUMMARY 0x020 20 | #define MSG_ERR_SERIOUS 0x040 21 | #define MSG_ERR_SUMMARY 0x080 22 | #define MSG_FATAL 0x100 23 | #define MSG_STATS 0x200 24 | #define MSG_FORMAT 0x400 25 | 26 | void msg(uint32_t level, char *format, ...); 27 | uint32_t msg_set_err_mask(uint32_t mask); 28 | uint32_t msg_get_err_mask(void); 29 | void *msg_malloc(size_t size, char *msgstr); 30 | void msg_set_logfile(FILE *file, uint32_t mask); 31 | #endif /* MSG_H_ */ 32 | -------------------------------------------------------------------------------- /mfm/inc/parse_cmdline.h: -------------------------------------------------------------------------------- 1 | /* 2 | * parse_cmdline.h 3 | * 4 | * 11/09/14 DJG Changes for new command line options 5 | * Created on: Dec 21, 2013 6 | * Author: djg 7 | */ 8 | 9 | #ifndef PARSE_CMDLINE_H_ 10 | #define PARSE_CMDLINE_H_ 11 | 12 | char *parse_print_cmdline(DRIVE_PARAMS *drive_params, int print, 13 | int no_retries_drive_interleave); 14 | void parse_cmdline(int argc, char *argv[], DRIVE_PARAMS *drive_params, 15 | char *delete_options, int initialize, int only_deleted, 16 | int allow_invalid_options, int track_layout_format_only); 17 | void parse_validate_options(DRIVE_PARAMS *drive_params, int mfm_read); 18 | void parse_validate_options_listed(DRIVE_PARAMS *drive_params, char *opt); 19 | void parse_set_drive_params_from_controller(DRIVE_PARAMS *drive_params, 20 | int controller); 21 | #endif /* PARSE_CMDLINE_H_ */ 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # indent -kr -nut -i3 2 | # 3 | 4 | # Do not: 5 | # o use make's built-in rules and variables 6 | # (this increases performance and avoids hard-to-debug behaviour); 7 | # o print "Entering directory ..."; 8 | MAKEFLAGS += -rR --no-print-directory 9 | 10 | SUBDIRS := $(wildcard */.) 11 | TARGETS := all clean 12 | 13 | SUBDIRS_TARGETS := \ 14 | $(foreach t,$(TARGETS),$(addsuffix $t,$(SUBDIRS))) 15 | 16 | .PHONY : $(TARGETS) $(SUBDIRS_TARGETS) 17 | 18 | $(TARGETS) : % : $(addsuffix %,$(SUBDIRS)) 19 | 20 | update: 21 | cd mfm 22 | git pull --ff-only 23 | make 24 | 25 | $(SUBDIRS_TARGETS) : 26 | $(MAKE) -C $(@D) $(@F:.%=%) 27 | @/bin/echo -e "\n" 28 | 29 | help: 30 | @echo "Targets:" 31 | @echo " all: Build all MFM emulator binaries" 32 | @echo " clean: Remove all build artifacts" 33 | @echo " update: Updates code from github and rebuilds" 34 | @echo "" 35 | @echo "This top-level Makefile recurses over these subdirectories:" 36 | @echo " $(wildcard */)" 37 | 38 | -------------------------------------------------------------------------------- /powerfail/Makefile: -------------------------------------------------------------------------------- 1 | # indent -kr -nut -i3 2 | # 3 | # Execute 'make' to create powerfail 4 | # Other options: 5 | # make clean 6 | # make all 7 | # make project 8 | # make clean 9 | # 10 | 11 | project = powerfail 12 | 13 | OBJDIR=obj 14 | INCDIR=inc 15 | 16 | LIBRARIES = pthread m 17 | INCL_PATH = -I $(INCDIR)/ -I ../mfm/$(INCDIR) 18 | #-I ../mfm/$(INCDIR) 19 | 20 | SOURCES = powerfail.c parse_cmdline.c ../mfm/msg.c 21 | OBJECTS = $(addprefix $(OBJDIR)/, $(subst ../mfm/,,$(subst .c,.o,$(SOURCES)))) 22 | INCLUDES = $(addprefix $(INCDIR)/, parse_cmdline.h) 23 | 24 | CC = gcc 25 | EXTRA_DEFINE = 26 | CFLAGS = $(EXTRA_DEFINE) $(INCL_PATH) -O3 -g -Wall -D_FILE_OFFSET_BITS=64 -iquote . -iquote ../mfm 27 | 28 | all : $(project) 29 | 30 | $(project) : $(OBJECTS) 31 | $(CC) $(OBJECTS) -Wl,-rpath=$(LIB_PATH) $(LIB_PATH:%=-L %) $(LIBRARIES:%=-l%) -o $@ 32 | 33 | clean : 34 | echo $(OBJ) 35 | rm -rf $(OBJDIR)/*.o $(project) core *~ 36 | 37 | $(OBJDIR)/%.o: %.c $(INCLUDES) | obj 38 | $(CC) $(CFLAGS) -c -o $@ $< 39 | $(OBJDIR)/%.o: ../mfm/%.c $(INCLUDES) | obj 40 | $(CC) $(CFLAGS) -c -o $@ $< 41 | obj: 42 | mkdir $@ 43 | -------------------------------------------------------------------------------- /util/mfm_emu.conf: -------------------------------------------------------------------------------- 1 | # Executable directories 2 | EmuDir="/opt/mfm/emu" 3 | PowerFailDir="/opt/mfm/powerfail" 4 | 5 | # File name for first emulator file 6 | EmuFN1="/home/debian/emufile_a" 7 | # File name for second emulator file. Leave blank if none 8 | EmuFN2="" 9 | 10 | # Type of backup. Blank for none, copy for simple copy, 11 | # xdelta or rdiff to use those programs to reduce backup file size 12 | Backup="" 13 | # Number of backup files to keep 14 | NumBackup="4" 15 | 16 | # Command to use to power off the board when power lost 17 | PowerCmd="sudo poweroff -f" 18 | # Additional options to powerfail command 19 | PowerFailOptions="" 20 | 21 | # Additional optoins to mfm_emu command. 22 | # For revision a board if drive emulating isn't the first two specify 23 | # correct --drive here 24 | # If not using 10F capacitors on a rev B board remove the pool option. 25 | # Revision B boards have longer holdup time so can use more buffers. 26 | MfmEmuOptions="--pool 200,.6" 27 | 28 | # Set to yes to not buffer stdout/stderr for reptyr command to see mfm_emu 29 | # output. 30 | # reptyr -s `ps -C mfm_emu -o pid=` 31 | NoLineBuffer="" 32 | -------------------------------------------------------------------------------- /emu/recover_deltas: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ $# -lt 2 ]; then 3 | echo "Usage: rdiff|xdelta emufilename [directory]" 4 | exit 5 | fi 6 | new_fn="$2.recovered" 7 | if [ $# -eq 3 ]; then 8 | dir="$3" 9 | if [ "${dir: -1}" != "/" ]; then 10 | dir="$3/" 11 | fi 12 | new_fn=`basename $2` 13 | new_fn="$dir$new_fn.recovered" 14 | fi 15 | last_fn="$2.0" 16 | if [ "$1" = "xdelta" ]; then 17 | delta_fn="$2.xdelta" 18 | num_files=`ls $delta_fn.* | wc -l` 19 | for ((i=0; i < $num_files; i++ )); do 20 | if [ -e "$delta_fn.$i" ]; then 21 | xdelta patch "$delta_fn.$i" "$last_fn" "$new_fn.$i" 22 | echo "recovered $new_fn.$i" 23 | last_fn="$new_fn.$i" 24 | fi 25 | done 26 | elif [ "$1" = "rdiff" ]; then 27 | delta_fn="$2.rdiff" 28 | num_files=`ls $delta_fn.* | wc -l` 29 | for ((i=0; i < $num_files; i++ )); do 30 | if [ -e "$delta_fn.$i" ]; then 31 | rdiff patch "$last_fn" "$delta_fn.$i" "$new_fn.$i" 32 | echo "recovered $new_fn.$i" 33 | last_fn="$new_fn.$i" 34 | fi 35 | done 36 | else 37 | echo "Unknow delta type $1" 38 | fi 39 | -------------------------------------------------------------------------------- /mfm/inc/pru_setup.h: -------------------------------------------------------------------------------- 1 | /* 2 | * pru_setup.h 3 | * 4 | * Created on: Dec 20, 2013 5 | * Author: djg 6 | */ 7 | 8 | #ifndef PRU_SETUP_H_ 9 | #define PRU_SETUP_H_ 10 | 11 | typedef enum { 12 | MEM_PRU0_DATA, 13 | MEM_PRU1_DATA, 14 | MEM_PRU_SHARED, 15 | MEM_DDR 16 | } MEM_TYPE; 17 | 18 | int pru_setup(int num_pru_in); 19 | int pru_exec_program(int pru, char *filename); 20 | void pru_shutdown(void); 21 | int pru_exec_cmd(uint32_t cmd, uint32_t data); 22 | uint32_t pru_get_cmd_status(void); 23 | uint32_t pru_get_cmd_data(void); 24 | void pru_restart(int pru_num); 25 | uint32_t pru_get_pc(int pru_num); 26 | uint32_t pru_get_halt(int pru_num); 27 | void pru_print_registers(int pru_num); 28 | void pru_print_memory(MEM_TYPE mem_type, int start, int len); 29 | int pru_write_mem(MEM_TYPE mem_type, void *data, int len, int offset); 30 | int pru_read_mem(MEM_TYPE mem_type, void *data, int len, int offset); 31 | uint32_t pru_read_word(MEM_TYPE mem_type, int loc); 32 | void pru_write_word(MEM_TYPE mem_type, int loc, uint32_t value); 33 | #define PRU_SET_CLOCK_HALT 1 34 | #define PRU_SET_CLOCK_NO_HALT 0 35 | uint32_t pru_set_clock(uint32_t tgt_bitrate_hz, int halt); 36 | #endif /* PRU_SETUP_H_ */ 37 | -------------------------------------------------------------------------------- /mfm/inc/drive.h: -------------------------------------------------------------------------------- 1 | /* 2 | * drive.h 3 | * 4 | * Created on: Dec 23, 2013 5 | * Author: djg 6 | */ 7 | 8 | #ifndef DRIVE_H_ 9 | #define DRIVE_H_ 10 | 11 | void drive_select(int drive); 12 | void drive_set_head(int head); 13 | void drive_seek_track0(void); 14 | void drive_setup(DRIVE_PARAMS *drive_params); 15 | void drive_read_disk(DRIVE_PARAMS *drive_params, void *deltas, int max_deltas); 16 | int drive_at_track0(void); 17 | uint32_t drive_get_drive_status(void); 18 | void drive_print_drive_status(int level, uint32_t status); 19 | double drive_rpm(void); 20 | // Step rates for drive_params->step_speed and drive.c drive_step parameter 21 | #define DRIVE_STEP_SLOW 0 22 | #define DRIVE_STEP_FAST 1 23 | 24 | #define step_speed_text(x) (x == DRIVE_STEP_SLOW ? "slow ST506" : "fast ST412") 25 | 26 | // Parameters to drive.c drive_step 27 | #define DRIVE_STEP_FATAL_ERR 1 28 | #define DRIVE_STEP_RET_ERR 0 29 | #define DRIVE_STEP_NO_UPDATE_CYL 0 30 | #define DRIVE_STEP_UPDATE_CYL 1 31 | 32 | // Return values from drive.c drive_step. 0 no error 33 | #define DRIVE_STEP_TIMEOUT 1 34 | #define DRIVE_STEP_RECAL 2 35 | 36 | int drive_step(int seek_speed, int steps, int update_cyl, int err_fatal); 37 | int drive_current_cyl(void); 38 | int drive_read_track(DRIVE_PARAMS *drive_params, int cyl, int head, 39 | void *deltas, int max_deltas, int return_write_fault); 40 | void drive_initialize(void); 41 | int drive_get_board_revision(void); 42 | void drive_write_disk(DRIVE_PARAMS *drive_params); 43 | void drive_enable_recovery(int enable); 44 | int drive_has_write_fault(void); 45 | 46 | 47 | 48 | #endif /* DRIVE_H_ */ 49 | -------------------------------------------------------------------------------- /mfm/copy_emu.c: -------------------------------------------------------------------------------- 1 | // copy track xfer_cyl, xfer_head from good.emu to bad.emu 2 | // build with 3 | // gcc -o copy_emu copy_emu.c emu_tran_file.c msg.c crc_ecc.c -Iinc -lm 4 | 5 | #include 6 | #include 7 | 8 | #include "inc/emu_tran_file.h" 9 | #include "inc/crc_ecc.h" 10 | #include "inc/mfm_decoder.h" 11 | 12 | int main() { 13 | int in_fd, out_fd; 14 | EMU_FILE_INFO emu_in_file_info, emu_out_file_info; 15 | int cyl,head; 16 | unsigned int words[MAX_TRACK_WORDS]; 17 | unsigned int words2[MAX_TRACK_WORDS]; 18 | int xfer_cyl = 0; 19 | int xfer_head = 0; 20 | FILE *out; 21 | int i; 22 | 23 | in_fd = emu_file_read_header("good.emu", &emu_in_file_info, 0, 0); 24 | out_fd = emu_file_read_header("bad.emu", &emu_out_file_info, 1, 0); 25 | 26 | 27 | emu_file_seek_track(in_fd, xfer_cyl, xfer_head, &emu_in_file_info); 28 | emu_file_read_track_bits(in_fd, &emu_in_file_info, words, ARRAYSIZE(words), 29 | &cyl, &head); 30 | 31 | #if 0 32 | emu_file_seek_track(out_fd, xfer_cyl, xfer_head, &emu_out_file_info); 33 | emu_file_read_track_bits(out_fd, &emu_out_file_info, words2, ARRAYSIZE(words2), 34 | &cyl, &head); 35 | for (i = 0; i < 4000; i++) { 36 | if (words[i] != words2[i]) { 37 | printf("Diff %d %x %x\n", i, words[i], words2[i]); 38 | } 39 | } 40 | words[1038] = words2[1038]; 41 | #endif 42 | emu_file_seek_track(out_fd, xfer_cyl, xfer_head, &emu_out_file_info); 43 | emu_file_write_track_bits(out_fd, words, ARRAYSIZE(words), xfer_cyl, 44 | xfer_head, emu_out_file_info.track_data_size_bytes); 45 | 46 | emu_file_close(in_fd, 0); 47 | emu_file_close(out_fd, 0); 48 | 49 | #if 0 50 | out = fopen("/tmp/good_track", "w"); 51 | for (i = 0; i < emu_out_file_info.track_data_size_bytes / 4; i++) { 52 | fprintf(out, "%08x\n",words2[i]); 53 | } 54 | fclose(out); 55 | out = fopen("/tmp/bad_track", "w"); 56 | for (i = 0; i < emu_out_file_info.track_data_size_bytes / 4; i++) { 57 | fprintf(out, "%08x\n",words[i]); 58 | } 59 | fclose(out); 60 | #endif 61 | } 62 | -------------------------------------------------------------------------------- /mfm/deltas_read_file.c: -------------------------------------------------------------------------------- 1 | // This is a replacement for deltas_read.c for use when reading data from a file 2 | // instead of a real drive. See deltas_read for more information. 3 | // 4 | // Copyright 2015 David Gesswein. 5 | // This file is part of MFM disk utilities. 6 | // 7 | // MFM disk utilities is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // MFM disk utilities is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with MFM disk utilities. If not, see . 19 | #include 20 | #include 21 | #include 22 | #include "msg.h" 23 | 24 | // The number of deltas we have to process 25 | uint32_t num_deltas; 26 | 27 | // Update the count of deltas. Always not streaming for reading files. 28 | // 29 | // num_deltas_in: Number of deltas read 30 | void deltas_update_count(int num_deltas_in, int streaming_in) 31 | { 32 | num_deltas = num_deltas_in; 33 | } 34 | 35 | // Return deltas available or -1 if caller has processed all the deltas. 36 | // 37 | // cur_delta: Number of deltas processed so far 38 | // return: Number of deltas available or -1 if all processed. 39 | int deltas_get_count(int deltas_processed) 40 | { 41 | if (deltas_processed >= num_deltas) { 42 | return -1; 43 | } else { 44 | return num_deltas; 45 | } 46 | } 47 | 48 | void deltas_start_read(void) { 49 | msg(MSG_FATAL, "deltas_start_read called\n"); 50 | exit(1); 51 | } 52 | void deltas_start_thread(void) { 53 | msg(MSG_FATAL, "deltas_start_thread called\n"); 54 | exit(1); 55 | } 56 | void deltas_stop_thread(void) { 57 | msg(MSG_FATAL, "deltas_stop_thread called\n"); 58 | exit(1); 59 | } 60 | 61 | void deltas_wait_read_finished(void) { 62 | } 63 | -------------------------------------------------------------------------------- /emu/inc/parse_cmdline.h: -------------------------------------------------------------------------------- 1 | /* 2 | * parse_cmdline.h 3 | * 4 | * 09/12/23 JST Changes to support 5.10 kernel and --sync option 5 | * 05/17/21 DJG Added option to initialize 6 | * 11/09/14 DJG Added new command line options 7 | * 10/24/14 DJG Changed needed to support mfm_emu write buffer 8 | 9 | * Created on: Dec 21, 2013 10 | * Author: djg 11 | */ 12 | 13 | #ifndef PARSE_CMDLINE_H_ 14 | #define PARSE_CMDLINE_H_ 15 | 16 | #ifndef DEF_DATA 17 | #define DEF_EXTERN extern 18 | #else 19 | #define DEF_EXTERN 20 | #endif 21 | 22 | #define MAX_HEAD 16 23 | 24 | #define MAX_DRIVES 2 25 | typedef struct { 26 | char *name; 27 | int value; 28 | } CONTROLLER; 29 | 30 | #define CONTROLLER_DEFAULT 1 31 | #define CONTROLLER_CROMEMCO 2 32 | DEF_EXTERN CONTROLLER mfm_controller_info[] 33 | #ifdef DEF_DATA 34 | = { 35 | {"Default", CONTROLLER_DEFAULT}, 36 | {"Cromemco", CONTROLLER_CROMEMCO}, 37 | {NULL, 0} 38 | } 39 | #endif 40 | ; 41 | 42 | // This is the main structure defining the drive characteristics 43 | typedef struct { 44 | // The number of cylinders and heads 45 | int num_cyl; 46 | int num_head; 47 | char *filename[MAX_DRIVES]; 48 | int fd[MAX_DRIVES]; 49 | int drive[MAX_DRIVES]; // Drive select number 50 | EMU_FILE_INFO emu_file_info[MAX_DRIVES]; 51 | int num_drives; 52 | int initialize; 53 | int buffer_count; // Number of track buffers 54 | float buffer_max_time; // Max time to delay when last buffer used 55 | float buffer_time; // time parameter for delay calculation 56 | char *cmdline; // Decode parameters from command line 57 | char *options; // Extra options specified for saving in file 58 | char *note; // File information string 59 | uint32_t sample_rate_hz; // MFM clock and data bit rate 60 | uint32_t rpm; // Drive RPM. 0 if not set. 61 | uint32_t start_time_ns; // Time to shift start of reading from index 62 | int sync; // Open emu file with O_DSYNC 63 | } DRIVE_PARAMS; 64 | char *parse_print_cmdline(DRIVE_PARAMS *drive_params, int print); 65 | void parse_cmdline(int argc, char *argv[], DRIVE_PARAMS *drive_params); 66 | #endif /* PARSE_CMDLINE_H_ */ 67 | -------------------------------------------------------------------------------- /emu/start_mfm_emu: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #look at rdiff, xdelta 3 | # bsdiff takes too much memory 4 | 5 | source /etc/mfm_emu.conf 6 | 7 | cd $EmuDir 8 | $EmuDir/setup_emu 9 | 10 | if [ "$EmuFN2" == "" ]; then 11 | cmd="--file $EmuFN1 --drive 1 $MfmEmuOptions" 12 | files="1" 13 | else 14 | cmd="--file $EmuFN1,$EmuFN2 --drive 1,2 $MfmEmuOptions" 15 | files="1 2" 16 | fi 17 | for fnum in $files ; do 18 | name="EmuFN$fnum" 19 | base=${!name} 20 | if [ "$Backup" == "rdiff" ]; then 21 | delta_fn="$base.rdiff" 22 | for ((i=NumBackup-1; i >= 1; i-- )); do 23 | last=$(($i-1)) 24 | if [ -e "$delta_fn.$last" ]; then 25 | mv "$delta_fn.$last" "$delta_fn.$i" 26 | fi 27 | done 28 | if [ -e "$base.0" ]; then 29 | rdiff signature "$base" "$base.sig" 30 | rdiff delta "$base.sig" "$base.0" "$delta_fn.0" 31 | fi 32 | cp "$base" "$base.0" 33 | elif [ "$Backup" == "xdelta" ]; then 34 | delta_fn="$base.xdelta" 35 | for ((i=NumBackup-1; i >= 1; i-- )); do 36 | last=$(($i-1)) 37 | if [ -e "$delta_fn.$last" ]; then 38 | mv "$delta_fn.$last" "$delta_fn.$i" 39 | fi 40 | done 41 | if [ -e "$base.0" ]; then 42 | xdelta delta "$base" "$base.0" "$delta_fn.0" 43 | fi 44 | cp "$base" "$base.0" 45 | elif [ "$Backup" == "copy" ]; then 46 | for ((i=NumBackup-1; i >= 1; i-- )); do 47 | last=$(($i-1)) 48 | if [ -e "$base.$last" ]; then 49 | mv "$base.$last" "$base.$i" 50 | fi 51 | done 52 | cp "$base" "$base.0" 53 | fi 54 | done 55 | KERNEL_VER=$(uname -r | cut -f1 -d.) 56 | if [ "$KERNEL_VER" -ge "4" ]; then 57 | # The sytem start of these is 7 seconds after this script starts so 58 | # delays emulator starting. Didn't figure out where they are loaded 59 | # to try to start sooner so forcing them to load here 60 | modprobe uio 61 | modprobe uio_pruss 62 | modprobe uio_pdrv_genirq 63 | fi 64 | for (( i=i ; i < 100; i++ )); do 65 | if [ -e /dev/uio1 ]; then 66 | break 67 | fi 68 | sleep .1 69 | done 70 | echo Loops $i 71 | if [ "$NoLineBuffer" != "" ]; then 72 | /usr/bin/stdbuf -o L -e L \ 73 | $PowerFailDir/powerfail --powercmd "$PowerCmd" $PowerFailOptions --command "$EmuDir/mfm_emu $cmd" 74 | else 75 | $PowerFailDir/powerfail --powercmd "$PowerCmd" $PowerFailOptions --command "$EmuDir/mfm_emu $cmd" 76 | fi 77 | -------------------------------------------------------------------------------- /emu/README: -------------------------------------------------------------------------------- 1 | This code is intended to be used to emulate MFM disks using a beaglebone 2 | board with a disk interface daughter card. A makefile is provided to 3 | build mfm_emu. It requires files from the mfm_read/mfm_util programs. 4 | 5 | The site for this code is http://www.pdp8online.com/mfm/mfm.shtml 6 | 7 | The Makefile assumes am335x_pru_package-master is in the parent directory 8 | of the source. This provides libprussdrv.a for accessing the PRUs, the 9 | PRU assembler, and necessary include files. The version I used to build it 10 | is at https://github.com/beagleboard/am335x_pru_package. The Debian 11 | distribution includes a version of this package with bugs so for now you 12 | still need to download the current version. 13 | 14 | See the pdp8online site above for more information on building. 15 | 16 | For emulating disks the code running on PRU 0 is controlled by commands 17 | from the ARM which are documented in cmd.h and prucode0.p. The commands 18 | tell PRU 0 to start processing track data or exit. The emulation uses 19 | track data as MFM clock and data bits. This data is converted to pulse 20 | timing to control a pulse width modulator (PWM) for generating the MFM signal 21 | to the controller. The write data from the controller pulse timing is decoded 22 | and converted back to clock and data bits. This is written in the proper 23 | spot in the emulation data file. The MFM clock and data bits are transferred 24 | in the PRU DDR memory buffer to PRU 1. PRU 1 handles converting the bits 25 | to PWM data and converting the capture time delta data back to MFM bits. 26 | PRU 0 handles actual generation/reading of the MFM signal line and handling 27 | select, head line changes, and seeks. The ARM handles reading and writing 28 | the file and transferring to the DDR memory buffer. It also sends commands 29 | and gets interrupts from PRU 0 to perform the needed operations. 30 | More information is in the headers of the various routines. 31 | 32 | See mfm_emu_doc.* for how to run the program. 33 | 34 | All the code I created is released under GPLv3 or later. 35 | 36 | Source file organization 37 | mfm_emu.c Top file for reading MFM disks 38 | prucode0.p The assembly program running in PRU 0 39 | prucode1.p The assembly program running in PRU 1 40 | cmd.h The PRU code commands, return status, and various memory 41 | location definitions 42 | parse_cmdline.c Routines for parsing and printing the command line options 43 | Makefile Makefile for building the two executables and PRU code 44 | .h Various header files which define function prototypes 45 | 46 | Other files 47 | setup_emu Script to configure the beaglebone pins 48 | emu-00A0.dts Device tree file to configure pins 49 | 50 | David Gesswein 51 | djg@pdp8online.com 52 | -------------------------------------------------------------------------------- /emu/Makefile: -------------------------------------------------------------------------------- 1 | # indent -kr -nut -i3 2 | # 3 | # Execute 'make' to create prucode.bin and mfm_emu 4 | # Other options: 5 | # make clean 6 | # make all 7 | # make pru 8 | # make clean 9 | # 10 | 11 | PRU = prucode0_reva.bin prucode0_revb.bin prucode0_revc.bin prucode1_reva.bin prucode1_revb.bin prucode1_revc.bin 12 | PROJECT = mfm_emu 13 | 14 | OBJDIR=obj 15 | INCDIR=inc 16 | 17 | LIBRARIES = pthread prussdrv m rt 18 | 19 | SOURCES = mfm_emu.c ../mfm/pru_setup.c ../mfm/msg.c parse_cmdline.c \ 20 | ../mfm/emu_tran_file.c ../mfm/crc_ecc.c ../mfm/board.c 21 | OBJECTS = $(addprefix $(OBJDIR)/, $(subst ../mfm/,,$(subst .c,.o,$(SOURCES)))) 22 | INCLUDES = $(addprefix $(INCDIR)/, cmd.h parse_cmdline.h) ../mfm/$(INCDIR)/msg.h \ 23 | ../mfm/$(INCDIR)/emu_tran_file.h ../mfm/$(INCDIR)/crc_ecc.h \ 24 | ../mfm/$(INCDIR)/pru_setup.h ../mfm/$(INCDIR)/version.h 25 | 26 | CC = gcc 27 | SUDO = /usr/bin/sudo 28 | SETCAP = /sbin/setcap 29 | 30 | FP_ABI = 31 | #Rev B angstrom require softfp 32 | ifneq ($(wildcard /usr/lib/arm-angstrom-linux-gnueabi),) 33 | FP_ABI = -mfloat-abi=softfp 34 | endif 35 | 36 | # If local copy of PRU package exists use it. Currently the 37 | # package isn't provided for Angstrom and doesn't work for Debian 38 | PRU_PACKAGE_PATH = ../am335x_pru_package 39 | PASM = pasm 40 | LIB_PATH = 41 | INCL_PATH = -I $(INCDIR)/ -I ../mfm/$(INCDIR) 42 | ifneq ($(wildcard $(PRU_PACKAGE_PATH)),) 43 | $(info Using local copy of am335x_pru_package) 44 | PASM = $(PRU_PACKAGE_PATH)/pru_sw/utils/pasm 45 | LIB_PATH = $(PRU_PACKAGE_PATH)/pru_sw/app_loader/lib/ 46 | INCL_PATH += -I $(PRU_PACKAGE_PATH)/pru_sw/app_loader/include/ 47 | endif 48 | 49 | UNAME_S := $(shell uname -m) 50 | EXTRA_DEFINE = 51 | ifeq ($(UNAME_S), armv7l) 52 | EXTRA_DEFINE = -mcpu=cortex-a8 -mfpu=vfpv3 $(FP_ABI) \ 53 | -ffast-math -Ofast 54 | 55 | endif 56 | PASM += -V2 57 | 58 | CFLAGS = $(EXTRA_DEFINE) $(INCL_PATH) -O3 -g -Wall -D_FILE_OFFSET_BITS=64 59 | 60 | all : $(PRU) $(PROJECT) 61 | pru : $(PRU) 62 | 63 | $(PROJECT) : $(OBJECTS) 64 | $(CC) $(OBJECTS) -Wl,-rpath=$(LIB_PATH) $(LIB_PATH:%=-L %) $(LIBRARIES:%=-l%) -o $@ 65 | if [ -f $(SETCAP) ]; then $(SUDO) $(SETCAP) 'cap_sys_nice=eip' $(PROJECT); fi 66 | 67 | clean : 68 | echo $(OBJ) 69 | rm -rf $(OBJDIR)/*.o *.bin $(PROJECT) core *~ prucode*_rev*.txt 70 | 71 | distclean : clean 72 | rm -rf logfile.txt *.dto 73 | 74 | %_reva.bin: %.p prucode.hp $(INCDIR)/cmd.h 75 | $(PASM) -b -L $< $*_reva 76 | 77 | %_revb.bin: %.p prucode.hp $(INCDIR)/cmd.h 78 | $(PASM) -b -L -DREVB $< $*_revb 79 | 80 | %_revc.bin: %.p prucode.hp $(INCDIR)/cmd.h 81 | $(PASM) -b -L -DREVC $< $*_revc 82 | 83 | $(OBJDIR)/%.o: %.c $(INCLUDES) | obj 84 | $(CC) $(CFLAGS) -c -o $@ $< 85 | $(OBJDIR)/%.o: ../mfm/%.c $(INCLUDES) | obj 86 | $(CC) $(CFLAGS) -c -o $@ $< 87 | obj: 88 | mkdir $@ 89 | -------------------------------------------------------------------------------- /mfm/inc/cmd_write.h: -------------------------------------------------------------------------------- 1 | // These defines are used in both the PRU assembly language and in the C 2 | // code so they can only be simple defines. 3 | 4 | 5 | // Registers used locally and XFER bank 10 to communicate with PRU0 6 | #define PRU1_BUF_OFFSET r8.w0 7 | #define PRU1_STATE r8.b3 8 | #define PRU1_BUF_STATE r8 9 | #define PRU0_BUF_OFFSET r9.w0 10 | #define PRU0_STATE r9.b3 11 | #define PRU0_BUF_STATE r9 12 | #define TRACK_BIT r18 13 | 14 | #define STATE_IDLE 1 15 | #define STATE_READ 2 16 | #define STATE_READ_FILLED 4 17 | #define STATE_READ_DONE 5 18 | #define STATE_RESTART_READ 9 19 | #define STATE_EXIT 10 20 | 21 | #define PRU_TEST0 0x24 22 | #define PRU_TEST1 0x28 23 | #define PRU_TEST2 0x2c 24 | #define PRU_TEST3 0x30 25 | #define PRU_TEST4 0x34 26 | 27 | 28 | // Save pin setting to restore on exit 29 | #define PRU0_MCASP0_ACLKX 0x50 30 | // Number of bytes in each drive# value. 31 | #define DRIVE_DATA_BYTES 4 32 | // PRU 0-1 queue underrun count 33 | #define PRU0_RQUEUE_UNDERRUN 0x6c 34 | #define PRU0_WQUEUE_OVERRUN 0x70 35 | 36 | // Used to convert bit-count to cycles at current rate (4 bytes) 37 | #define PRU0_BIT_PRU_CLOCKS 0x8c 38 | 39 | // Cycle count for PWM logic 1 at current data rate (4 bytes) 40 | #define PRU0_DEFAULT_PULSE_WIDTH 0x90 41 | 42 | // Start and end time for generating index pulse in PRU clocks 43 | #define PRU0_START_INDEX_TIME 0x94 44 | #define PRU0_END_INDEX_TIME 0x98 45 | 46 | // Time for a disk rotation in PRU clocks 47 | #define PRU0_ROTATION_TIME 0x9c 48 | 49 | // Non zero indicates error in converting bits to PWM words 50 | #define PRU1_BAD_PATTERN_COUNT 0x40 51 | #define PRU1_CUR_HEAD 0x44 52 | #define PRU1_DRIVE0_TRACK_HEADER_BYTES 0x50 53 | #define PRU1_DRIVE0_TRACK_DATA_BYTES 0x58 54 | 55 | // inverse bit period scaled 2^32, 2^32/bit_period 56 | #define PRU1_INV_BIT_PERIOD_S32 0x60 57 | 58 | // Nominal bit cell cycles 59 | #define PRU1_BIT_PRU_CLOCKS 0x68 60 | // DMA channel to use 61 | #define PRU1_DMA_CHANNEL 0x6c 62 | // Last DMA RAM and DDR offset, internal PRU use 63 | #define PRU1_NEXT_DMA_RAM_OFFSET 0x70 64 | #define PRU1_NEXT_DMA_DDR_OFFSET 0x74 65 | 66 | #define PRU_WORD_SIZE_BYTES 4 67 | 68 | // Location bit lookup table stored 69 | #define PRU1_BIT_TABLE 0x80 70 | 71 | // Maximum size of cylinder buffer. 72 | #define DDR_DRIVE_BUFFER_MAX_SIZE (16*32768) 73 | // PRU queues are in shared memory locations 0 to mask 74 | #define SHARED_PWM_READ_MASK 0x1f 75 | #define SHARED_DELTAS_WRITE_MASK 0x7f 76 | 77 | // Bits in registers for the control lines 78 | #define R30_WRITE_GATE 0 79 | 80 | 81 | #define GPIO1_TEST 29 82 | -------------------------------------------------------------------------------- /powerfail/powerfail_doc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 |

powerfail monitors the input 12V to the 17 | MFM reader/emulator board and shuts down the BeagleBone when power is 18 | lost.

19 |


20 | 21 |

22 |

--powercmd -p 'command and arguments'

23 |

The command to 24 | execute when power is lost.

25 |

--command -c 'command and arguments'

26 |

The command to 27 | execute when powerfail starts. This program is sent a SIGINT when 28 | power is lost. The powercmd is sent after this program exits.

29 |

--threshold -t #

30 |

When the input 31 | power drops below this voltage the power down actions will start. 32 | Default is 11.5 volts.

33 |

--wait -w #

34 |

The input power 35 | must be below the threshold for the specified number of seconds 36 | before the power down actions will start. Default is .1 seconds.

37 |

--scale -s #

38 |

The resistor 39 | divider scale value used to convert the A/D reading to voltage. 40 | Should be r16 / (r16 + r15). Default is 0.1253.

41 |

--debug -d

42 |

Print debugging and 43 | status information.

44 |

--version -v

45 |

Print program 46 | version number.

47 |


48 | 49 |

50 |

Long options can be abbreviated to the 51 | shortest unique name. Long option values can't have spaces.

52 |


53 | 54 |

55 |

# is a floating point number. 56 |

57 |

58 |

59 |

Example if running from emu directory:

60 |

../powerfail/powerfail --powercmd 61 | 'halt -f' --command './mfm_emu --drive 1 --file disk_file'

62 |


63 | 64 |

65 |

If you have the 66 | extra chip installed to activate the power button when power returns 67 | the recommended powercmd is poweroff otherwise it is halt. For Debian 68 | it is recommended to use the -f flag to to poweroff or halt.

69 | 70 | -------------------------------------------------------------------------------- /mfm/inc/cmd.h: -------------------------------------------------------------------------------- 1 | // These defines are used in both the PRU assembly language and in the C 2 | // code so they can only be simple defines. 3 | 4 | // The commands to the PRU 5 | #define CMD_NONE 0 6 | #define CMD_EXIT 1 7 | // The commands to the PRU for mfm_read 8 | #define CMD_READ_TRACK 2 9 | #define CMD_RPM 3 10 | #define CMD_SEEK_FAST 4 11 | #define CMD_SEEK_SLOW 5 12 | #define CMD_SEEK_SLOW_TRACK0 6 13 | #define CMD_CHECK_READY 7 14 | // The commands to the PRU for mfm_write 15 | #define CMD_WRITE_TRACK 8 16 | 17 | // The command status. All commands other than CMD_READ_TRACK 18 | // return their status when done. CMD_READ_TRACK returns 19 | // CMD_STATUS_READ_STARTED when the read has started and CMD_STATUS_OK 20 | // or an error status when the read is finished. 21 | #define CMD_STATUS_WAIT_READY 0x100 22 | #define CMD_STATUS_OK 0x200 23 | #define CMD_STATUS_READY_ERR 0x300 24 | #define CMD_STATUS_INDEX_TIMEOUT 0x400 25 | #define CMD_STATUS_SEEK_COMPLETE_ERR 0x500 26 | #define CMD_STATUS_READ_OVERFLOW 0x600 27 | #define CMD_STATUS_READ_OVERRUN 0x700 28 | #define CMD_STATUS_READ_STARTED 0x800 29 | #define CMD_STATUS_DELTA_OVERFLOW 0x900 30 | 31 | // These are the control parameters to communicate with the PRU code 32 | #define PRU_DDR_ADDR 0x00 // Physical address of shared DDR memory 33 | #define PRU_DDR_SIZE 0x04 // Size of shared DDR memory 34 | #define PRU0_WRITE_PTR 0x08 // Delta write pointer 35 | #define PRU0_CMD 0x0c // Command location 36 | #define PRU0_CMD_DATA 0x10 // Data for command 37 | #define PRU0_STATUS 0x14 // Drive status, only valid when idle 38 | #define PRU0_START_TIME_CLOCKS 0x18 // Delay from index until we start 39 | // capturing transitions 40 | #define PRU_DATARAM_ADDR 0x1c // Physical address of the PRU memory 41 | #define PRU0_BOARD_REVISION 0x20 // 0 = A etc 42 | 43 | #define REVB_DETECT_PIN 46 // GPIO 1_14 44 | #define REVC_DETECT_PIN 61 // GPIO 1_29 45 | // Rev A,B 46 | #define GPIO0_TRACK_0_BIT 30 47 | // Rev C 48 | #define GPIO3_TRACK_0_BIT_REVC 17 49 | #define GPIO3_START_PIN (32*3) 50 | // Where the physical lines show up in the PRU registers 51 | #define R30_SEEK_DIR_BIT 6 52 | #define R30_STEP_BIT 7 53 | #define R30_MFM0_IN_ENABLE 15 54 | 55 | #define R31_DRIVE_SEL 1 56 | #define R31_SEEK_COMPLETE_BIT 2 57 | // Rev A,B 58 | #define R31_WRITE_FAULT_BIT 3 59 | // Rev C 60 | #define GPIO1_WRITE_FAULT_BIT 19 61 | #define R31_READY_BIT 4 62 | #define R31_INDEX_BIT 5 63 | 64 | // Address in PRU code for stopping current operation 65 | #define RESTART_ADDR 0x400 66 | // Offset from start of PRU0_DATARAM to the PRU control register 67 | #define PRU_CONTROL_REG 0x22000 68 | #define PRU_STATUS_REG 0x22004 69 | #define PRU_DEBUG 0x22400 70 | 71 | // Registers for configuring the PRU clock 72 | #define CLOCK_BASE 0x44e00000 73 | #define CM_AUTOIDEL_DPLL_DISP 0x448 74 | #define CM_IDLEST_DPLL_DISP 0x448 75 | #define CM_SSC_DELTAMSTEP_DPLL_DISP 0x44c 76 | #define CM_SSC_MODFREQDIV_DPLL_DISP 0x450 77 | #define CM_CLKSEL_DPLL_DISP 0x454 78 | #define CM_CLKMODE_DPLL_DISP 0x498 79 | #define CM_DIV_M2_DPLL_DISP 0x4a4 80 | #define CLKSEL_PRU_ICSS_OCP_CLK 0x530 81 | -------------------------------------------------------------------------------- /mfm/drive_file.c: -------------------------------------------------------------------------------- 1 | // This is a replacement for drive.c that reads from a transition file or 2 | // a emulation file instead of a actual drive. See drive.c for function 3 | // definitions 4 | // 5 | // Copyright 2023 David Gesswein. 6 | // This file is part of MFM disk utilities. 7 | // 8 | // MFM disk utilities is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // MFM disk utilities is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with MFM disk utilities. If not, see . 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "msg.h" 26 | #include "crc_ecc.h" 27 | #include "emu_tran_file.h" 28 | #include "mfm_decoder.h" 29 | #include "drive.h" 30 | #include "deltas_read.h" 31 | 32 | 33 | 34 | uint32_t drive_get_drive_status(void) { 35 | msg(MSG_FATAL, "drive_get_drive_status called\n"); 36 | exit(1); 37 | } 38 | 39 | void drive_print_drive_status(int level, uint32_t status) { 40 | msg(MSG_FATAL, "drive_print_drive_status called\n"); 41 | exit(1); 42 | } 43 | 44 | void drive_set_head(int head) { 45 | } 46 | 47 | int drive_step(int seek_speed, int steps, int update_cyl, int err_fatal) { 48 | return 0; 49 | } 50 | 51 | int drive_at_track0(void) { 52 | return 0; 53 | } 54 | 55 | void drive_seek_track0(void) { 56 | } 57 | 58 | void drive_select(int drive) { 59 | msg(MSG_FATAL, "drive_select called\n"); 60 | exit(1); 61 | } 62 | 63 | void drive_setup(DRIVE_PARAMS *drive_params) { 64 | msg(MSG_FATAL, "drive_setup called\n"); 65 | exit(1); 66 | } 67 | 68 | double drive_rpm() { 69 | return 0; 70 | } 71 | 72 | // This reads the proper track from the file and puts it in the delta array 73 | // for decoding. Its a little wasteful to convert decoded bits from an 74 | // emulation file back to deltas so we can convert back to bits but it 75 | // allowed for minimum code changes. 76 | // 77 | int drive_read_track(DRIVE_PARAMS *drive_params, int cyl, int head, 78 | void *deltas, int max_deltas, int return_write_fault) { 79 | int num_deltas; 80 | 81 | if (drive_params->tran_fd != -1) { 82 | if (tran_file_seek_track(drive_params->tran_fd, cyl, head, 83 | drive_params->tran_file_info)) { 84 | num_deltas = 0; 85 | } else { 86 | num_deltas = tran_file_read_track_deltas(drive_params->tran_fd, 87 | deltas, max_deltas, &cyl, &head); 88 | } 89 | } else { 90 | if (emu_file_seek_track(drive_params->emu_fd, cyl, head, 91 | drive_params->emu_file_info)) { 92 | num_deltas = 0; 93 | } else { 94 | num_deltas = emu_file_read_track_deltas(drive_params->emu_fd, 95 | drive_params->emu_file_info, 96 | deltas, max_deltas, &cyl, &head); 97 | } 98 | } 99 | deltas_update_count(num_deltas, 0); 100 | 101 | return 0; 102 | } 103 | -------------------------------------------------------------------------------- /mfm/inc/emu_tran_file.h: -------------------------------------------------------------------------------- 1 | /* 2 | * emu_tran_file.h 3 | * 4 | * 09/12/23 JST Changes to support 5.10 kernel and --sync option 5 | * 11/09/14 DJG Added new function prototypes for emulator file 6 | * buffering and structure changes for buffering and other new 7 | * command line options 8 | * Created on: Jan 25, 2014 9 | * Author: djg 10 | */ 11 | 12 | #ifndef EMU_TRAN_FILE_H_ 13 | #define EMU_TRAN_FILE_H_ 14 | 15 | // Information on disk image file for emulator 16 | typedef struct emu_file_info { 17 | // File version information 18 | uint32_t version; 19 | // The size of this data in the file header 20 | int file_header_size_bytes; 21 | // Header and data size of track. All tracks are the same size 22 | int track_header_size_bytes; 23 | int track_data_size_bytes; 24 | // File contains num_cyl*num_head tracks 25 | int num_cyl; 26 | int num_head; 27 | // Rate in Hz 28 | int sample_rate_hz; 29 | // Delay from index to first data in nanoseconds 30 | uint32_t start_time_ns; 31 | // The command line used to generate the file 32 | char *decode_cmdline; 33 | // And description of file 34 | char *note; 35 | } EMU_FILE_INFO; 36 | 37 | // Information on transition data file 38 | typedef struct tran_file_info { 39 | // File version information 40 | uint32_t version; 41 | int file_header_size_bytes; 42 | int track_header_size_bytes; 43 | // File contains num_cyl*num_head tracks 44 | int num_cyl; 45 | int num_head; 46 | // Rate in Hz 47 | int sample_rate_hz; 48 | // Delay from index to first data in nanoseconds 49 | uint32_t start_time_ns; 50 | // The command line used to generate the file 51 | char *decode_cmdline; 52 | // And description of file 53 | char *note; 54 | } TRAN_FILE_INFO; 55 | 56 | int emu_file_write_header(char *fn, int num_cyl, int num_head, char *cmdline, 57 | char *note, uint32_t sample_rate, uint32_t start_time_ns, 58 | uint32_t track_bytes); 59 | int emu_file_read_header(char *fn, EMU_FILE_INFO *emu_file_info_out, 60 | int rewrite, int direct); 61 | void emu_file_write_track_bits(int fd, uint32_t *words, int num_words, int cyl, 62 | int head, uint32_t track_bytes); 63 | int emu_file_read_track_bits(int fd, EMU_FILE_INFO *emu_file_info, 64 | uint32_t *words, int num_bytes, int *cyl, int *head); 65 | void emu_file_close(int fd, int write_eof); 66 | int emu_file_seek_track(int fd, int seek_cyl, int seek_head, EMU_FILE_INFO *emu_file_info); 67 | int emu_file_read_track_deltas(int fd, EMU_FILE_INFO *emu_file_info, 68 | uint16_t deltas[], int max_deltas, int *cyl, int *head); 69 | void emu_file_read_cyl(int fd, EMU_FILE_INFO *emu_file_info, int cyl, 70 | void *buf, int buf_size); 71 | void emu_file_write_cyl(int fd, EMU_FILE_INFO *emu_file_info, int cyl, 72 | void *buf, int buf_size); 73 | void emu_file_rewrite_track(int fd, EMU_FILE_INFO *emu_file_info, 74 | int cyl, int head, void *buf, int buf_size); 75 | 76 | int tran_file_write_header(char *fn, int num_cyl, int num_head, char *cmdline, 77 | char *note, uint32_t start_time_ns); 78 | int tran_file_read_header(char *fn, TRAN_FILE_INFO *tran_file_info); 79 | int tran_file_seek_track(int fd, int seek_cyl, int seek_head, TRAN_FILE_INFO *tran_file_info); 80 | int tran_file_read_track_deltas(int fd,uint16_t deltas[], int max_deltas, int *cyl, 81 | int *head); 82 | void tran_file_write_track_deltas(int fd,uint16_t *deltas, int num_words, int cyl, int head); 83 | void tran_file_close(int fd, int write_eof); 84 | 85 | float emu_rps(int sample_rate_hz); 86 | 87 | #endif /* EMU_TRAN_FILE_H_ */ 88 | -------------------------------------------------------------------------------- /mfm/msg.c: -------------------------------------------------------------------------------- 1 | // Message handling routines 2 | // 3 | // Call msg to print an error message 4 | // Call msg_set_err_mask to set which errors should be printed 5 | // Call msg_get_err_mask to get current error mask 6 | // Call msg_malloc to malloc with error message if fail 7 | // Call msg_set_logfile to log fatal errors to the log file 8 | // 9 | // 05/17/15 DJG Added ability to log errors to a file 10 | // 11/09/14 DJG added new msg_malloc so I don't have to keep checking return 11 | // 12 | // Copyright 2014 David Gesswein. 13 | // This file is part of MFM disk utilities. 14 | // 15 | // MFM disk utilities is free software: you can redistribute it and/or modify 16 | // it under the terms of the GNU General Public License as published by 17 | // the Free Software Foundation, either version 3 of the License, or 18 | // (at your option) any later version. 19 | // 20 | // MFM disk utilities is distributed in the hope that it will be useful, 21 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | // GNU General Public License for more details. 24 | // 25 | // You should have received a copy of the GNU General Public License 26 | // along with MFM disk utilities. If not, see . 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #ifdef CLOCK_MONOTONIC_RAW 34 | #define CLOCK CLOCK_MONOTONIC_RAW 35 | #else 36 | #define CLOCK CLOCK_MONOTONIC 37 | #endif 38 | 39 | #include "msg.h" 40 | 41 | // Current error mask. Not thread safe for changes 42 | static uint64_t err_mask = 0xffffffff; 43 | 44 | // If not null log fatal errors to file 45 | static FILE *logfile = NULL; 46 | uint32_t logfile_err_mask; 47 | 48 | // Print an error message. 49 | // 50 | // level: Error level used to determine if message should be printed 51 | // format: Format string for message 52 | // 53 | void msg(uint32_t level, char *format, ...) { 54 | static int last_progress = 0; 55 | va_list va; 56 | 57 | 58 | va_start(va, format); 59 | if (err_mask & level) { 60 | // Overwrite progress message with spaces in case new message is shorter 61 | if (last_progress && !(level & MSG_PROGRESS)) { 62 | printf("%79s\r",""); 63 | } 64 | vprintf(format, va); 65 | if (level & MSG_PROGRESS) { 66 | fflush(stdout); 67 | } 68 | last_progress = level & MSG_PROGRESS; 69 | } 70 | if (logfile != NULL && (level & logfile_err_mask)) { 71 | vfprintf(logfile, format, va); 72 | } 73 | va_end(va); 74 | } 75 | 76 | // Set the error mask and get previous value 77 | // 78 | // mask: New error mask 79 | // return: Previous error mask 80 | uint32_t msg_set_err_mask(uint32_t mask) { 81 | uint32_t last_mask; 82 | 83 | last_mask = err_mask; 84 | err_mask = mask; 85 | return last_mask; 86 | } 87 | 88 | // Get the error mask 89 | // return: Current error mask 90 | uint32_t msg_get_err_mask(void) { 91 | return err_mask; 92 | } 93 | 94 | // Malloc and print error if fail 95 | // 96 | void *msg_malloc(size_t size, char *msgstr) { 97 | void *ptr; 98 | 99 | ptr = malloc(size); 100 | if (ptr == NULL) { 101 | msg(MSG_FATAL,"Malloc failed %s size %u\n", msgstr, size); 102 | exit(1); 103 | } 104 | return ptr; 105 | } 106 | 107 | // Set logfile to log selected errors to 108 | // file: File descriptor to write messages to 109 | // mask: Error mask for errors to log 110 | void msg_set_logfile(FILE *file, uint32_t mask) { 111 | logfile = file; 112 | logfile_err_mask = mask; 113 | } 114 | -------------------------------------------------------------------------------- /mfm/Makefile: -------------------------------------------------------------------------------- 1 | # indent -kr -nut -i3 2 | # 3 | # Execute 'make' to create prucode.bin and mfm_read 4 | # Other options: 5 | # make all 6 | # make pru 7 | # make mfm_read 8 | # make mfm_util 9 | # make clean 10 | # 11 | 12 | PRU = prucode0.bin 13 | PRU2 = mfm_write0.bin mfm_write1.bin 14 | 15 | OBJDIR=obj 16 | INCDIR=inc 17 | 18 | LIBRARIES = pthread prussdrv m rt 19 | 20 | SOURCES = mfm_read.c mfm_decoder.c wd_mfm_decoder.c xebec_mfm_decoder.c \ 21 | crc_ecc.c pru_setup.c msg.c parse_cmdline.c analyze.c \ 22 | deltas_read.c drive.c emu_tran_file.c corvus_mfm_decoder.c \ 23 | northstar_mfm_decoder.c board.c drive_read.c tagged_mfm_decoder.c \ 24 | perq_mfm_decoder.c 25 | OBJECTS = $(addprefix $(OBJDIR)/, $(SOURCES:.c=.o)) 26 | SOURCES2 = mfm_util.c mfm_decoder.c wd_mfm_decoder.c xebec_mfm_decoder.c \ 27 | crc_ecc.c msg.c parse_cmdline.c emu_tran_file.c corvus_mfm_decoder.c \ 28 | northstar_mfm_decoder.c analyze.c deltas_read_file.c drive_file.c \ 29 | tagged_mfm_decoder.c perq_mfm_decoder.c 30 | OBJECTS2 = $(addprefix $(OBJDIR)/, $(SOURCES2:.c=.o)) 31 | SOURCES3 = mfm_write.c msg.c parse_cmdline_write.c emu_tran_file.c \ 32 | drive.c pru_setup.c crc_ecc.c board.c drive_write.c 33 | OBJECTS3 = $(addprefix $(OBJDIR)/, $(SOURCES3:.c=.o)) 34 | INCLUDES = $(addprefix $(INCDIR)/, analyze.h cmd.h crc_ecc.h deltas_read.h \ 35 | drive.h emu_tran_file.h mfm_decoder.h msg.h parse_cmdline.h \ 36 | pru_setup.h version.h) 37 | 38 | CC = c99 39 | 40 | CPP = c++ 41 | GOT_CPP := $(shell $(CPP) --version 2>/dev/null) 42 | ifndef GOT_CPP 43 | $(info no c++ compiler found, CRC support program not built) 44 | CPP = echo c++ 45 | endif 46 | 47 | FP_ABI = 48 | #Rev B angstrom require softfp 49 | ifneq ($(wildcard /usr/lib/arm-angstrom-linux-gnueabi),) 50 | FP_ABI = -mfloat-abi=softfp 51 | endif 52 | 53 | # If local copy of PRU package exists use it. Currently the 54 | # package isn't provided for Angstrom and doesn't work for Debian 55 | # Used copy from https://github.com/beagleboard/am335x_pru_package.git 56 | # run pru_sw/utils/pasm_source/linuxbuild 57 | PRU_PACKAGE_PATH = ../am335x_pru_package 58 | LIB_PATH = 59 | INCL_PATH = -I $(INCDIR)/ -I /usr/include/libiberty/ 60 | PASM = pasm 61 | ifneq ($(wildcard $(PRU_PACKAGE_PATH)),) 62 | $(info Using local copy of am335x_pru_package) 63 | PASM = $(PRU_PACKAGE_PATH)/pru_sw/utils/pasm 64 | LIB_PATH = $(PRU_PACKAGE_PATH)/pru_sw/app_loader/lib/ 65 | INCL_PATH += -I $(PRU_PACKAGE_PATH)/pru_sw/app_loader/include/ 66 | endif 67 | PASM += -V2 -L 68 | 69 | UNAME_S := $(shell uname -m) 70 | EXTRA_DEFINE = 71 | ifeq ($(UNAME_S), armv7l) 72 | EXTRA_DEFINE = -mcpu=cortex-a8 -mfpu=vfpv3 $(FP_ABI) \ 73 | -ffast-math -Ofast 74 | else 75 | GOT_PASM := $(shell $(PASM) --version 2>/dev/null) 76 | ifndef GOT_PASM 77 | $(info no pasm found, PRU programs not built) 78 | PASM = echo pasm 79 | endif 80 | endif 81 | 82 | CFLAGS = $(EXTRA_DEFINE) $(INCL_PATH) -O3 -g -Wall -D_FILE_OFFSET_BITS=64 -D_XOPEN_SOURCE=600 83 | 84 | all : $(PRU) $(PRU2) mfm_util ext2emu find_crc_info mfm_write mfm_read 85 | pru : $(PRU) $(PRU2) 86 | 87 | mfm_read : $(OBJECTS) 88 | $(CC) $(OBJECTS) -Wl,-rpath=$(LIB_PATH) $(LIB_PATH:%=-L %) $(LIBRARIES:%=-l%) -o $@ 89 | mfm_util : $(OBJECTS2) 90 | $(CC) $(OBJECTS2) $(LIB_PATH:%=-L %) -lm -lrt -liberty -o $@ 91 | ext2emu : mfm_util 92 | ln -s mfm_util ext2emu 93 | mfm_write : $(OBJECTS3) 94 | $(CC) $(OBJECTS3) -Wl,-rpath=$(LIB_PATH) $(LIB_PATH:%=-L %) $(LIBRARIES:%=-l%) -o $@ 95 | 96 | find_crc_info : find_crc_info.cpp 97 | $(CPP) -O3 -std=c++0x -Wall $< -o $@ 98 | 99 | clean : 100 | rm -rf $(OBJDIR)/*.o *.bin mfm_read mfm_util core *~ find_crc_info 101 | 102 | %.bin: %.p prucode.hp $(INCDIR)/cmd.h drive_operations.p 103 | $(PASM) -b $< 104 | 105 | $(OBJDIR)/%.o: %.c $(INCLUDES) | obj 106 | $(CC) $(CFLAGS) -c -o $@ $< 107 | obj: 108 | mkdir $@ 109 | -------------------------------------------------------------------------------- /mfm/README: -------------------------------------------------------------------------------- 1 | This code is intended to be used to read MFM disks using a beaglebone 2 | board with a disk interface daughter card. A makefile is provided to 3 | build mfm_read and mfm_util on the beaglebone. 4 | 5 | The site for this code is http://www.pdp8online.com/mfm/mfm.shtml 6 | 7 | The Makefile assumes am335x_pru_package-master is in the parent directory 8 | of the source. This provides libprussdrv.a for accessing the PRUs, the 9 | PRU assembler, and necessary include files. The version I used to build it 10 | is at https://github.com/beagleboard/am335x_pru_package. The Debian 11 | distribution includes a version of this package with bugs so for now you 12 | still need to download the current version. 13 | 14 | The mfm_util program can also be built on a little endian Linux system. 15 | See the pdp8online site above for more information on building. 16 | 17 | For reading disks the code running on the PRU is controlled by commands 18 | from the ARM which are documented in cmd.h and prucode0.p. The commands 19 | do disk control such as seeking, checking status, and reading a track. 20 | The track is read as the timing of rising edges of the MFM data signal. The 21 | time is represented as delta time between the edges. These delta transitions 22 | are put in a shared DDR memory buffer as 16 bit words with a count stored in 23 | the PRU local memory. The ARM reads the count and deltas for processing. 24 | The raw transitions can be stored for later processing and/or decoded back 25 | into the disk contents. The mfm_util program is used to decode the 26 | saved transitions into the decoded disk contents. 27 | 28 | See mfm_read_util_doc.* for how to run the programs. 29 | 30 | All the code I created is released under GPLv3 or later. 31 | 32 | Source file organization 33 | mfm_read.c Top file for reading MFM disks 34 | mfm_util.c Top file for processing existing MFM delta transitions file 35 | 36 | For mfm_read 37 | analyze.c Try to determine the track format of a disk drive 38 | deltas_read.c Routines for transferring deltas from PRU 39 | drive.c Routines for controlling the disk drive 40 | pru_setup.c Routines for initializing the PRU 41 | prucode0.p The assembly program running in the PRU 42 | cmd.h The PRU code commands, return status, and various memory 43 | location definitions 44 | 45 | Used by both 46 | crc_ecc.c Routines for performing CRC and ECC operations 47 | mfm_decoder.c Routines for performing the MFM decoding 48 | msg.c Routines for printing messages 49 | parse_cmdline.c Routines for parsing and printing the command line options 50 | wd_mfm_decoder.c Routines for processing "Western Digital" format 51 | xebec_mfm_decoder.c Routines for processing Xebec format tracks 52 | emu_tran_file.c Routines for reading and writing emulation and transition files 53 | mfm_decoder.h Defines for data structures used by the code 54 | Makefile Makefile for building the two executables and PRU code 55 | .h Various header files which define function prototypes 56 | 57 | Other files 58 | crc_reverse.c Routines to be manually used for determining CRC data for a disk 59 | setup_mfm_read Script to configure the beaglebone pins 60 | mfm_read-00A0.dts Device tree file to configure pins 61 | 62 | The DRIVE_PARAMS structure in mfm_decoder.h defines most of the information 63 | for decoding the track. It can either be set from the command line or by 64 | analyze_disk routine. Some decoding information which is hardcoded in the 65 | routines might be better moved into the structure. 66 | 67 | See emu_tran_file.c header for data file formats. 68 | 69 | The decoded disk contents is the concatenated sector data. It is in the 70 | order specified in the sector headers, not the physical order on disk. 71 | This will be different if the drive was formatted with interleave. 72 | If the sector header was bad the sector data will be all zeros. If the 73 | data area had a CRC error the data read will be written to the file. 74 | 75 | David Gesswein 76 | djg@pdp8online.com 77 | -------------------------------------------------------------------------------- /emu/setup_emu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | wait_writeable() { 4 | while [ ! -w "/sys/class/gpio/gpio$1/direction" ]; do 5 | sleep .01 6 | done 7 | } 8 | 9 | KERNEL_VER=$(uname -r | cut -f1 -d.) 10 | 11 | #Files are in same directory as script 12 | dir="$(dirname "$BASH_SOURCE")" 13 | if [ "$KERNEL_VER" -lt "4" ]; then 14 | # Use legacy cape manager loading functionality 15 | if [ -e /sys/devices/platform/bone_capemgr/slots ] 16 | then 17 | CAPE=/sys/devices/platform/bone_capemgr/slots 18 | else 19 | CAPE=/sys/devices/bone_capemgr.*/slots 20 | # Enable A/D 21 | echo cape-bone-iio > $CAPE 22 | fi 23 | fi 24 | 25 | pin='61' 26 | if [ ! -d /sys/class/gpio/gpio$pin ]; then 27 | # Depending on default pullup since we haven't loaded the overlay 28 | echo $pin > /sys/class/gpio/export 29 | wait_writeable "$pin" 30 | fi 31 | echo in > /sys/class/gpio/gpio$pin/direction 32 | REV=$(cat /sys/class/gpio/gpio$pin/value) 33 | 34 | if [ "$REV" == "0" ]; then 35 | echo Rev C or D Board 36 | if [ "$KERNEL_VER" -lt "4" ]; then 37 | /usr/bin/dtc -O dtb -o $dir/emu-00C0.dtbo -b 0 -@ $dir/emu-00C0.dts 38 | cp $dir/emu-00C0.dtbo /lib/firmware 39 | echo emu:00C0 > $CAPE 40 | else 41 | # Batch configure all pins for mfm_emu 42 | # Shouldn't need sudo but sometimes get 43 | # ERROR: open() for /sys/devices/platform/ocp/ocp:P9_13_pinmux/state failed, Permission denied 44 | sudo config-pin -c $dir/mfm_emu_revc.bbio 45 | fi 46 | # 31 drive1 select/recovery, 51 write fault(C) 47 | # 60 emulation xcvr en 48 | # 48 testing(C) 49 | pins_low="31 48 51 60" 50 | # 14,30(C) drive selected LEDS 51 | # 50 mfm read xcvr en 52 | pins_high="14 30 50" 53 | else 54 | if [ "$KERNEL_VER" -lt "4" ]; then 55 | /usr/bin/dtc -O dtb -o $dir/emu-00A0.dtbo -b 0 -@ $dir/emu-00A0.dts 56 | cp $dir/emu-00A0.dtbo /lib/firmware 57 | echo emu:00A0 > $CAPE 58 | else 59 | # Shouldn't need sudo but sometimes get 60 | # ERROR: open() for /sys/devices/platform/ocp/ocp:P9_13_pinmux/state failed, Permission denied 61 | sudo config-pin -c $dir/mfm_emu_revab.bbio 62 | fi 63 | # Detect A/B PIN with P8_16 64 | pin='46' 65 | if [ ! -d /sys/class/gpio/gpio$pin ]; then 66 | echo $pin > /sys/class/gpio/export 67 | wait_writeable "$pin" 68 | fi 69 | echo in > /sys/class/gpio/gpio$pin/direction 70 | REV=$(cat /sys/class/gpio/gpio$pin/value) 71 | if [ "$REV" == "0" ]; then 72 | echo Rev B Board 73 | else 74 | echo Rev A Board 75 | fi 76 | # 30 track0(B), 31 drive1 select/recovery, 51 write fault(C) 77 | # 60 emulation xcvr en 78 | # 61 testing(B) 79 | pins_low="30 31 51 60" 80 | # 14,15(B) drive selected LEDS 81 | # 50 mfm read xcvr en 82 | pins_high="14 15 50" 83 | fi 84 | 85 | 86 | # With univeresal cape header, all pins are exported and set to in by default. 87 | # However, we now support reboot-less operation, so set them all anyway. 88 | if [ "$KERNEL_VER" -lt "4" ]; then 89 | # 2-5, 22,23,26,27 Drive select and head select 90 | pins_in="2 3 4 5 22 23 26 27 8 9 10 11" 91 | else 92 | pins_in="22 23 26 27" 93 | fi 94 | for pin in $pins_in $pins_low $pins_high; do 95 | if [ ! -d /sys/class/gpio/gpio$pin ]; then 96 | echo $pin > /sys/class/gpio/export 97 | fi 98 | wait_writeable "$pin" 99 | echo in > /sys/class/gpio/gpio$pin/direction 100 | done 101 | 102 | for pin in $pins_low; do 103 | echo out > /sys/class/gpio/gpio$pin/direction 104 | echo 0 > /sys/class/gpio/gpio$pin/value 105 | done 106 | 107 | for pin in $pins_high; do 108 | echo out > /sys/class/gpio/gpio$pin/direction 109 | echo 1 > /sys/class/gpio/gpio$pin/value 110 | done 111 | 112 | # hide dmesg warnings about unhandled IRQs 113 | # (IRQs are being handled by the PRUs directly, not Linux) 114 | if [ "$KERNEL_VER" -ge "4" ]; then 115 | gpiomon gpiochip0 8 9 10 11 12 13 > /dev/null 2>&1 & 116 | sleep 0.5 117 | kill $! 118 | fi 119 | -------------------------------------------------------------------------------- /mfm/mfm_read-00A0.dts: -------------------------------------------------------------------------------- 1 | /dts-v1/; 2 | /plugin/; 3 | 4 | / { 5 | compatible = "ti,beaglebone-black", "ti,beaglebone-green"; 6 | 7 | /* identification */ 8 | part-number = "mfm_read"; 9 | 10 | /* version */ 11 | version = "00A0"; 12 | 13 | /* state the resources this cape uses or prepare to get winged! */ 14 | exclusive-use = 15 | /* the pin header P9 uses */ 16 | "P8.11", 17 | "P8.12", 18 | "P8.13", 19 | "P8.14", 20 | "P8.15", 21 | "P8.17", 22 | "P8.19", 23 | "P9.11", 24 | "P9.12", 25 | "P9.13", 26 | "P9.14", 27 | "P9.17", 28 | "P9.18", 29 | "P9.21", 30 | "P9.22", 31 | "P9.25", 32 | "P9.27", 33 | "P9.28", 34 | "P9.29", 35 | "P9.30", 36 | "P9.31", 37 | "P9.41", 38 | "P9.42", 39 | /* Rev B Pins */ 40 | "P8.16", 41 | "P8.31", 42 | "P8,32", 43 | "P8.33", 44 | /* Hardware IP cores in use */ 45 | "pruss"; 46 | 47 | fragment@0 { 48 | target = <&am33xx_pinmux>; 49 | __overlay__ { 50 | pruicss_stepper_pins: pinmux_pruicss_stepper_pins{ 51 | pinctrl-single,pins = < 52 | // All inputs pullup 53 | // All outputs fast pullup disabled 54 | // These go to PRU0 55 | 0x034 0x06 // OUT P8_11 = pr1_pru0_pru_30_15 56 | 0x03c 0x35 // IN/OUT P8_15 = pr1_ecap0 57 | 0x184 0x36 // IN P9_24 = pr1_pru0_pru_31_16 58 | 0x1ac 0x2d // OUT P9_25 = pr1_pru0_pru_30_7 59 | 0x1a4 0x36 // IN P9_27 = pr1_pru0_pru_31_5 60 | 0x19c 0x36 // IN P9_28 = pr1_pru0_pru_31_3 61 | 0x194 0x36 // IN P9_29 = pr1_pru0_pru_31_1 62 | 0x198 0x36 // IN P9_30 = pr1_pru0_pru_31_2 63 | //Until we support write make pin GPIO 64 | 0x190 0x07 // OUT P9_31 = gpio3_14 65 | //0x190 0x2d // OUT P9_31 = pr1_pru0_pru_30_0 66 | 0x1a8 0x2d // OUT P9_41 = pr1_pru0_pru_30_6 67 | 0x1a0 0x36 // IN P9_42.1 = pr1_pru0_pru_31_4 68 | 69 | // These are GPIO 70 | 0x15C 0x0f // OUT P9_17 = gpio0_5 71 | 0x158 0x0f // OUT P9_18 = gpio0_4 72 | 0x150 0x0f // OUT P9_22 = gpio0_2 73 | 0x154 0x0f // OUT P9_21 = gpio0_3 74 | 0x020 0x0f // OUT P8_19 = gpio0_22 75 | 0x030 0x0f // OUT P8_12 = gpio1_12 76 | 0x024 0x0f // OUT P8_13 = gpio0_23 77 | 0x028 0x0f // OUT P8_14 = gpio0_26 78 | 0x02c 0x0f // OUT P8_17 = gpio0_27 79 | 0x070 0x3f // IN P9_11 = gpio0_30 80 | 0x078 0x0f // OUT P9_12 = gpio1_28 81 | 0x074 0x0f // OUT P9_13 = gpio0_31 82 | 0x040 0x0f // OUT P9_14 = gpio1_18 83 | // Rev B GPIO 84 | 0x038 0x37 // IN P8_16 = gpio1_14, pullup 85 | 0x0d8 0x0f // OUT P8_31 = gpio0_10 86 | 0x0dc 0x0f // OUT P8_32 = gpio0_11 87 | 0x0d4 0x0f // OUT P8_33 = gpio0_09 88 | 0x0d0 0x0f // OUT P8_35 = gpio0_08 89 | 90 | >; 91 | }; 92 | 93 | }; 94 | }; 95 | 96 | fragment@1{ 97 | target = <&pruss>; 98 | __overlay__{ 99 | status = "okay"; 100 | pinctrl-names = "default"; 101 | pinctrl-0 = <&pruicss_stepper_pins>; 102 | 103 | }; 104 | }; 105 | fragment@2 { 106 | target = <&ocp>; 107 | __overlay__ { 108 | test_helper: helper { 109 | compatible = "bone-pinmux-helper"; 110 | pinctrl-names = "default"; 111 | pinctrl-0 = <&pruicss_stepper_pins>; 112 | status = "okay"; 113 | }; 114 | }; 115 | }; 116 | }; 117 | -------------------------------------------------------------------------------- /mfm/mfm_write-00A0.dts: -------------------------------------------------------------------------------- 1 | /dts-v1/; 2 | /plugin/; 3 | 4 | / { 5 | compatible = "ti,beaglebone-black", "ti,beaglebone-green"; 6 | 7 | /* identification */ 8 | part-number = "mfm_read"; 9 | 10 | /* version */ 11 | version = "00A0"; 12 | 13 | /* state the resources this cape uses or prepare to get winged! */ 14 | exclusive-use = 15 | /* the pin header P9 uses */ 16 | "P8.11", 17 | "P8.12", 18 | "P8.13", 19 | "P8.14", 20 | "P8.15", 21 | "P8.17", 22 | "P8.19", 23 | "P9.11", 24 | "P9.12", 25 | "P9.13", 26 | "P9.14", 27 | "P9.17", 28 | "P9.18", 29 | "P9.21", 30 | "P9.22", 31 | "P9.25", 32 | "P9.27", 33 | "P9.28", 34 | "P9.29", 35 | "P9.30", 36 | "P9.31", 37 | "P9.41", 38 | "P9.42", 39 | /* Rev B Pins */ 40 | "P8.16", 41 | "P8.31", 42 | "P8,32", 43 | "P8.33", 44 | /* Hardware IP cores in use */ 45 | "pruss"; 46 | 47 | fragment@0 { 48 | target = <&am33xx_pinmux>; 49 | __overlay__ { 50 | pruicss_stepper_pins: pinmux_pruicss_stepper_pins{ 51 | pinctrl-single,pins = < 52 | // All inputs pullup 53 | // All outputs fast pullup disabled 54 | // These go to PRU0 55 | 0x034 0x06 // OUT P8_11 = pr1_pru0_pru_30_15 56 | 0x03c 0x35 // IN/OUT P8_15 = pr1_ecap0 57 | 0x184 0x36 // IN P9_24 = pr1_pru0_pru_31_16 58 | 0x1ac 0x2d // OUT P9_25 = pr1_pru0_pru_30_7 59 | 0x1a4 0x36 // IN P9_27 = pr1_pru0_pru_31_5 60 | 0x19c 0x36 // IN P9_28 = pr1_pru0_pru_31_3 61 | 0x194 0x36 // IN P9_29 = pr1_pru0_pru_31_1 62 | 0x198 0x36 // IN P9_30 = pr1_pru0_pru_31_2 63 | //Until we support write make pin GPIO 64 | //0x190 0x07 // OUT P9_31 = gpio3_14 65 | 0x190 0x2d // OUT P9_31 = pr1_pru0_pru_30_0 66 | 0x1a8 0x2d // OUT P9_41 = pr1_pru0_pru_30_6 67 | 0x1a0 0x36 // IN P9_42.1 = pr1_pru0_pru_31_4 68 | 69 | // These are GPIO 70 | 0x15C 0x0f // OUT P9_17 = gpio0_5 71 | 0x158 0x0f // OUT P9_18 = gpio0_4 72 | 0x150 0x0f // OUT P9_22 = gpio0_2 73 | 0x154 0x0f // OUT P9_21 = gpio0_3 74 | 0x020 0x0f // OUT P8_19 = gpio0_22 75 | 0x030 0x0f // OUT P8_12 = gpio1_12 76 | 0x024 0x0f // OUT P8_13 = gpio0_23 77 | 0x028 0x0f // OUT P8_14 = gpio0_26 78 | 0x02c 0x0f // OUT P8_17 = gpio0_27 79 | 0x070 0x3f // IN P9_11 = gpio0_30 80 | 0x078 0x0f // OUT P9_12 = gpio1_28 81 | 0x074 0x0f // OUT P9_13 = gpio0_31 82 | 0x040 0x0f // OUT P9_14 = gpio1_18 83 | // Rev B GPIO 84 | 0x038 0x37 // IN P8_16 = gpio1_14, pullup 85 | 0x0d8 0x0f // OUT P8_31 = gpio0_10 86 | 0x0dc 0x0f // OUT P8_32 = gpio0_11 87 | 0x0d4 0x0f // OUT P8_33 = gpio0_09 88 | 0x0d0 0x0f // OUT P8_35 = gpio0_08 89 | 90 | >; 91 | }; 92 | 93 | }; 94 | }; 95 | 96 | fragment@1{ 97 | target = <&pruss>; 98 | __overlay__{ 99 | status = "okay"; 100 | pinctrl-names = "default"; 101 | pinctrl-0 = <&pruicss_stepper_pins>; 102 | 103 | }; 104 | }; 105 | fragment@2 { 106 | target = <&ocp>; 107 | __overlay__ { 108 | test_helper: helper { 109 | compatible = "bone-pinmux-helper"; 110 | pinctrl-names = "default"; 111 | pinctrl-0 = <&pruicss_stepper_pins>; 112 | status = "okay"; 113 | }; 114 | }; 115 | }; 116 | }; 117 | -------------------------------------------------------------------------------- /mfm/mfm_read-00C0.dts: -------------------------------------------------------------------------------- 1 | /dts-v1/; 2 | /plugin/; 3 | 4 | / { 5 | compatible = "ti,beaglebone-black", "ti,beaglebone-green"; 6 | 7 | /* identification */ 8 | part-number = "mfm_read"; 9 | 10 | /* version */ 11 | version = "00A0"; 12 | 13 | /* state the resources this cape uses or prepare to get winged! */ 14 | exclusive-use = 15 | /* the pin header P9 uses */ 16 | "P8.11", 17 | "P8.12", 18 | "P8.13", 19 | "P8.14", 20 | "P8.15", 21 | "P8.17", 22 | "P8.19", 23 | "P9.11", 24 | "P9.12", 25 | "P9.13", 26 | "P9.14", 27 | "P9.16", 28 | "P9.17", 29 | "P9.18", 30 | "P9.21", 31 | "P9.22", 32 | "P9.24", 33 | "P9.25", 34 | "P9.27", 35 | "P9.28", 36 | "P9.29", 37 | "P9.30", 38 | "P9.31", 39 | "P9.41", 40 | "P9.42", 41 | "P8.16", 42 | "P8.31", 43 | "P8,32", 44 | "P8.33", 45 | /* Hardware IP cores in use */ 46 | "pruss"; 47 | 48 | fragment@0 { 49 | target = <&am33xx_pinmux>; 50 | __overlay__ { 51 | pruicss_stepper_pins: pinmux_pruicss_stepper_pins{ 52 | pinctrl-single,pins = < 53 | // All inputs pullup 54 | // All outputs fast pullup disabled 55 | // These go to PRU0 56 | 0x034 0x06 // OUT P8_11 = pr1_pru0_pru_30_15 57 | 0x03c 0x35 // IN/OUT P8_15 = pr1_ecap0 58 | 0x1ac 0x2d // OUT P9_25 = pr1_pru0_pru_30_7 59 | 0x1a4 0x36 // IN P9_27 = pr1_pru0_pru_31_5 60 | 0x194 0x36 // IN P9_29 = pr1_pru0_pru_31_1 61 | 0x198 0x36 // IN P9_30 = pr1_pru0_pru_31_2 62 | //Until we support write make pin GPIO 63 | 0x190 0x07 // OUT P9_31 = gpio3_14 64 | //0x190 0x2d // OUT P9_31 = pr1_pru0_pru_30_0 65 | 0x1a8 0x2d // OUT P9_41 = pr1_pru0_pru_30_6 66 | 0x1a0 0x36 // IN P9_42.1 = pr1_pru0_pru_31_4 67 | 68 | // These are GPIO 69 | 0x15C 0x0f // OUT P9_17 = gpio0_5 70 | 0x158 0x0f // OUT P9_18 = gpio0_4 71 | 0x150 0x0f // OUT P9_22 = gpio0_2 72 | 0x154 0x0f // OUT P9_21 = gpio0_3 73 | 0x020 0x0f // OUT P8_19 = gpio0_22 74 | 0x030 0x0f // OUT P8_12 = gpio1_12 75 | 0x024 0x0f // OUT P8_13 = gpio0_23 76 | 0x028 0x0f // OUT P8_14 = gpio0_26 77 | 0x02c 0x0f // OUT P8_17 = gpio0_27 78 | 0x070 0x0f // OUT P9_11 = gpio0_30 79 | 0x078 0x0f // OUT P9_12 = gpio1_28 80 | 0x074 0x0f // OUT P9_13 = gpio0_31 81 | 0x048 0x0f // OUT P9_14 = gpio1_18 82 | // Rev C GPIO 83 | 0x038 0x0f // OUT P8_16 = gpio1_14 84 | 0x0d8 0x0f // OUT P8_31 = gpio0_10 85 | 0x0dc 0x0f // OUT P8_32 = gpio0_11 86 | 0x0d4 0x0f // OUT P8_33 = gpio0_09 87 | 0x0d0 0x0f // OUT P8_35 = gpio0_08 88 | 0x184 0x0f // OUT P9_24 = gpio0_15 89 | 0x19c 0x3f // IN P9_28 = gpio3_17 90 | 0x04c 0x3f // IN P9_16 = gpio0_05 91 | 92 | >; 93 | }; 94 | 95 | }; 96 | }; 97 | 98 | fragment@1{ 99 | target = <&pruss>; 100 | __overlay__{ 101 | status = "okay"; 102 | pinctrl-names = "default"; 103 | pinctrl-0 = <&pruicss_stepper_pins>; 104 | 105 | }; 106 | }; 107 | fragment@2 { 108 | target = <&ocp>; 109 | __overlay__ { 110 | test_helper: helper { 111 | compatible = "bone-pinmux-helper"; 112 | pinctrl-names = "default"; 113 | pinctrl-0 = <&pruicss_stepper_pins>; 114 | status = "okay"; 115 | }; 116 | }; 117 | }; 118 | }; 119 | -------------------------------------------------------------------------------- /mfm/mfm_write-00C0.dts: -------------------------------------------------------------------------------- 1 | /dts-v1/; 2 | /plugin/; 3 | 4 | / { 5 | compatible = "ti,beaglebone-black", "ti,beaglebone-green"; 6 | 7 | /* identification */ 8 | part-number = "mfm_read"; 9 | 10 | /* version */ 11 | version = "00A0"; 12 | 13 | /* state the resources this cape uses or prepare to get winged! */ 14 | exclusive-use = 15 | /* the pin header P9 uses */ 16 | "P8.11", 17 | "P8.12", 18 | "P8.13", 19 | "P8.14", 20 | "P8.15", 21 | "P8.17", 22 | "P8.19", 23 | "P9.11", 24 | "P9.12", 25 | "P9.13", 26 | "P9.14", 27 | "P9.16", 28 | "P9.17", 29 | "P9.18", 30 | "P9.21", 31 | "P9.22", 32 | "P9.24", 33 | "P9.25", 34 | "P9.27", 35 | "P9.28", 36 | "P9.29", 37 | "P9.30", 38 | "P9.31", 39 | "P9.41", 40 | "P9.42", 41 | "P8.16", 42 | "P8.31", 43 | "P8,32", 44 | "P8.33", 45 | /* Hardware IP cores in use */ 46 | "pruss"; 47 | 48 | fragment@0 { 49 | target = <&am33xx_pinmux>; 50 | __overlay__ { 51 | pruicss_stepper_pins: pinmux_pruicss_stepper_pins{ 52 | pinctrl-single,pins = < 53 | // All inputs pullup 54 | // All outputs fast pullup disabled 55 | // These go to PRU0 56 | 0x034 0x06 // OUT P8_11 = pr1_pru0_pru_30_15 57 | 0x03c 0x35 // IN/OUT P8_15 = pr1_ecap0 58 | 0x1ac 0x2d // OUT P9_25 = pr1_pru0_pru_30_7 59 | 0x1a4 0x36 // IN P9_27 = pr1_pru0_pru_31_5 60 | 0x194 0x36 // IN P9_29 = pr1_pru0_pru_31_1 61 | 0x198 0x36 // IN P9_30 = pr1_pru0_pru_31_2 62 | //Until we support write make pin GPIO 63 | //0x190 0x07 // OUT P9_31 = gpio3_14 64 | 0x190 0x2d // OUT P9_31 = pr1_pru0_pru_30_0 65 | 0x1a8 0x2d // OUT P9_41 = pr1_pru0_pru_30_6 66 | 0x1a0 0x36 // IN P9_42.1 = pr1_pru0_pru_31_4 67 | 68 | // These are GPIO 69 | 0x15C 0x0f // OUT P9_17 = gpio0_5 70 | 0x158 0x0f // OUT P9_18 = gpio0_4 71 | 0x150 0x0f // OUT P9_22 = gpio0_2 72 | 0x154 0x0f // OUT P9_21 = gpio0_3 73 | 0x020 0x0f // OUT P8_19 = gpio0_22 74 | 0x030 0x0f // OUT P8_12 = gpio1_12 75 | 0x024 0x0f // OUT P8_13 = gpio0_23 76 | 0x028 0x0f // OUT P8_14 = gpio0_26 77 | 0x02c 0x0f // OUT P8_17 = gpio0_27 78 | 0x070 0x0f // OUT P9_11 = gpio0_30 79 | 0x078 0x0f // OUT P9_12 = gpio1_28 80 | 0x074 0x0f // OUT P9_13 = gpio0_31 81 | 0x048 0x0f // OUT P9_14 = gpio1_18 82 | // Rev C GPIO 83 | 0x038 0x0f // OUT P8_16 = gpio1_14 84 | 0x0d8 0x0f // OUT P8_31 = gpio0_10 85 | 0x0dc 0x0f // OUT P8_32 = gpio0_11 86 | 0x0d4 0x0f // OUT P8_33 = gpio0_09 87 | 0x0d0 0x0f // OUT P8_35 = gpio0_08 88 | 0x184 0x0f // OUT P9_24 = gpio0_15 89 | 0x19c 0x3f // IN P9_28 = gpio3_17 90 | 0x04c 0x3f // IN P9_16 = gpio0_05 91 | 92 | >; 93 | }; 94 | 95 | }; 96 | }; 97 | 98 | fragment@1{ 99 | target = <&pruss>; 100 | __overlay__{ 101 | status = "okay"; 102 | pinctrl-names = "default"; 103 | pinctrl-0 = <&pruicss_stepper_pins>; 104 | 105 | }; 106 | }; 107 | fragment@2 { 108 | target = <&ocp>; 109 | __overlay__ { 110 | test_helper: helper { 111 | compatible = "bone-pinmux-helper"; 112 | pinctrl-names = "default"; 113 | pinctrl-0 = <&pruicss_stepper_pins>; 114 | status = "okay"; 115 | }; 116 | }; 117 | }; 118 | }; 119 | -------------------------------------------------------------------------------- /mfm/mfm_write_doc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 |

19 | mfm_write writes an emulator file to a disk drive. Currently it can 20 | only write an entire disk at once. It has limited testing. A couple 21 | people have successfully used it. It will pick up number of 22 | cylinders and heads to write from emulator file. This 23 | program does not do anything to avoid using bad locations/sectors 24 | on the disk so 25 | unless the drive is error free you will likely end up with some files 26 | with read errors.

27 |

28 | 29 |

30 |

Emulation_file and drive must be 31 | specified.

32 |


33 | 34 |

35 |

--drive -d #

36 |

Drive number to 37 | select for reading. Drives are number 1 to 4. 38 |

39 |

--emulation_file -m filename

40 |

File to write to 41 | disk drive.

42 |

--quiet -q #h

43 |

Bit mask to select 44 | which messages don't print. 0 is print all messages. Default is 1 (no 45 | debug messages). Higher bits are more important messages in general.

46 |

47 | --precomp_cyl #

48 |

49 | First cylinder to apply precompensation on. Cylinder number starts 50 | with 0. Default is to not apply precompensation.

51 |

52 | --precomp_ns #[,#]

53 |

54 | If single number is specified its used for both early and late 55 | precompensation. If two numbers are specified the first is early and 56 | the second late precompensation. Value is in nanoseconds and must be 57 | 0 to 30. Default is 0.

58 |

--unbuffered_seek -u

59 |

Use 60 | unbuffered/ST506 seeks. Default is buffered/ST412.

61 |

--version -v

62 |

Print program 63 | version number.

64 |


65 | 66 |

67 |

68 | Execute setup_mfm_write once before using mfm_write. With the version 69 | 11.# operating system image you can use setup_mfm_read after 70 | setup_mfm_write and vice versa without needing to reboot.

71 |

72 |
73 | 74 |

75 |

76 | Use mfm_read to verify the disk is properly written after writing the 77 | image. 78 |

79 |

80 |
81 | 82 |

83 | 84 | -------------------------------------------------------------------------------- /emu/emu-00A0.dts: -------------------------------------------------------------------------------- 1 | /dts-v1/; 2 | /plugin/; 3 | 4 | / { 5 | compatible = "ti,beaglebone-black", "ti,beaglebone-green"; 6 | 7 | /* identification */ 8 | part-number = "emu"; 9 | 10 | /* version */ 11 | version = "00A0"; 12 | 13 | /* state the resources this cape uses or prepare to get winged! */ 14 | exclusive-use = 15 | /* the pin header P9 uses */ 16 | "P8.11", 17 | "P8.12", 18 | "P8.13", 19 | "P8.14", 20 | "P8.15", 21 | "P8.17", 22 | "P8.19", 23 | "P8.26", 24 | "P8.45", 25 | "P9.11", 26 | "P9.12", 27 | "P9.13", 28 | "P9.14", 29 | "P9.17", 30 | "P9.18", 31 | "P9.21", 32 | "P9.22", 33 | "P9.24", 34 | "P9.25", 35 | "P9.26", 36 | "P9.27", 37 | "P9.28", 38 | "P9.29", 39 | "P9.30", 40 | "P9.31", 41 | "P9.41", 42 | "P9.42", 43 | /* Rev B Pins */ 44 | "P8.16", 45 | "P8.31", 46 | "P8,32", 47 | "P8.33", 48 | "P8.35", 49 | /* Hardware IP cores in use */ 50 | "pruss"; 51 | 52 | fragment@0 { 53 | target = <&am33xx_pinmux>; 54 | __overlay__ { 55 | pruicss_stepper_pins: pinmux_pruicss_stepper_pins{ 56 | pinctrl-single,pins = < 57 | // All inputs pullup 58 | // All outputs fast pullup disabled 59 | // These go to PRU0 60 | 0x034 0x06 // OUT P8_11 = pr1_pru0_pru_30_15 61 | 0x030 0x06 // OUT P8_12 = pr1_pru0_pru_30_14 62 | 0x03c 0x35 // IN/OUT P8_15 = pr1_ecap0 pullup input fast 63 | 0x1ac 0x3e // IN P9_25 = pr1_pru0_pru_30_7 64 | 0x1a4 0x05 // OUT P9_27 = pr1_pru0_pru_31_5 65 | 0x19c 0x05 // OUT P9_28 = pr1_pru0_pru_31_3 66 | 0x194 0x05 // OUT P9_29 = pr1_pru0_pru_31_1 67 | 0x198 0x05 // OUT P9_30 = pr1_pru0_pru_31_2 68 | 0x190 0x3e // IN P9_31 = pr1_pru0_pru_30_0 69 | 0x1a8 0x3e // IN P9_41 = pr1_pru0_pru_30_6 70 | 0x1a0 0x05 // OUT P9_42.1 = pr1_pru0_pru_31_4 71 | // Timing testing from PRU1 72 | 0xa0 0x05 // OUT P8_45 = pr1_pru1_pru_30_0 73 | 74 | 75 | // These are GPIO 76 | 0x15C 0x3f // IN P9_17 = gpio0_5 77 | 0x158 0x3f // IN P9_18 = gpio0_4 78 | 0x150 0x3f // IN P9_22 = gpio0_2 79 | 0x154 0x3f // IN P9_21 = gpio0_3 80 | 0x020 0x3f // IN P8_19 = gpio0_22 81 | 0x024 0x37 // IN P8_13 = gpio0_23, pullup 82 | 0x028 0x3f // IN P8_14 = gpio0_26 83 | 0x02c 0x3f // IN P8_17 = gpio0_27 84 | 0x070 0x0f // OUT P9_11 = gpio0_30 85 | 0x078 0x0f // OUT P9_12 = gpio1_28 86 | 0x074 0x0f // OUT P9_13 = gpio0_31 87 | 0x040 0x0f // OUT P9_14 = gpio1_18 88 | 0x184 0x0f // OUT P9_24 = gpio0_15 89 | 0x180 0x0f // OUT P9_26 = gpio0_14 90 | 0x07c 0x37 // IN P8_26 = gpio1_29, pullup, REV C detect 91 | // Rev B GPIO 92 | 0x038 0x37 // IN P8_16 = gpio1_14, pullup 93 | 0x0d8 0x3f // IN P8_31 = gpio0_10 94 | 0x0dc 0x3f // IN P8_32 = gpio0_11 95 | 0x0d4 0x3f // IN P8_33 = gpio0_09 96 | 0x0d0 0x3f // IN P8_35 = gpio0_08 97 | >; 98 | }; 99 | 100 | }; 101 | }; 102 | 103 | fragment@1{ 104 | target = <&pruss>; 105 | __overlay__{ 106 | status = "okay"; 107 | pinctrl-names = "default"; 108 | pinctrl-0 = <&pruicss_stepper_pins>; 109 | 110 | }; 111 | }; 112 | fragment@2 { 113 | target = <&ocp>; 114 | __overlay__ { 115 | test_helper: helper { 116 | compatible = "bone-pinmux-helper"; 117 | pinctrl-names = "default"; 118 | pinctrl-0 = <&pruicss_stepper_pins>; 119 | status = "okay"; 120 | }; 121 | }; 122 | }; 123 | }; 124 | -------------------------------------------------------------------------------- /mfm/setup_mfm_read: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | wait_writeable() { 4 | while [ ! -w "/sys/class/gpio/gpio$1/direction" ]; do 5 | sleep .01 6 | done 7 | } 8 | 9 | KERNEL_VER=$(uname -r | cut -f1 -d.) 10 | 11 | #Files are in same directory as script 12 | dir="$(dirname "$BASH_SOURCE")" 13 | if [ "$(basename "$BASH_SOURCE")" == "setup_mfm_write" ]; then 14 | DTBOAB="mfm_write-00A0" 15 | BBIOAB="mfm_w_revab.bbio" 16 | DTBOC="mfm_write-00C0" 17 | BBIOC="mfm_w_revc.bbio" 18 | else 19 | DTBOAB="mfm_read-00A0" 20 | BBIOAB="mfm_r_revab.bbio" 21 | DTBOC="mfm_read-00C0" 22 | BBIOC="mfm_r_revc.bbio" 23 | fi 24 | 25 | if [ "$KERNEL_VER" -lt "4" ]; then 26 | # Use legacy cape manager loading functionality 27 | if [ -e /sys/devices/platform/bone_capemgr/slots ] 28 | then 29 | CAPE=/sys/devices/platform/bone_capemgr/slots 30 | else 31 | CAPE=/sys/devices/bone_capemgr.*/slots 32 | # Enable A/D 33 | echo cape-bone-iio > $CAPE 34 | fi 35 | fi 36 | 37 | pin='61' 38 | if [ ! -d /sys/class/gpio/gpio$pin ]; then 39 | # Depending on default pullup since we haven't loaded the overlay 40 | echo $pin > /sys/class/gpio/export 41 | wait_writeable "$pin" 42 | fi 43 | echo in > /sys/class/gpio/gpio$pin/direction 44 | REV=$(cat /sys/class/gpio/gpio$pin/value) 45 | 46 | if [ "$REV" == "0" ]; then 47 | echo Rev C or D Board 48 | if [ "$KERNEL_VER" -lt "4" ]; then 49 | /usr/bin/dtc -O dtb -o $dir/$DTBOC.dtbo -b 0 -@ $dir/$DTBOC.dts 50 | cp $dir/$DTBOC.dtbo /lib/firmware 51 | echo ${DTBOC/-/:} > $CAPE 52 | else 53 | # Batch configure all pins for mfm_rw 54 | # Shouldn't need sudo but sometimes get 55 | # ERROR: open() for /sys/devices/platform/ocp/ocp:P9_13_pinmux/state failed, Permission denied 56 | sudo config-pin -c $dir/$BBIOC 57 | fi 58 | # Set inactive 31 recovery 46,15,26,27 Drive select, 110 write 59 | # 50 read transceiver enable, low enabled 60 | # 8-11 rev B head, all low is head 0 after inversion 61 | pins_low="8 9 10 11 31 46 15 26 27 110 50" 62 | # 113 TRACK 0 63 | pins_in="113" 64 | # 44 emu second driver emulation, disable 65 | # 60 MFM emulation tranceiver enable, high is disabled 66 | # 14,30(C) drive selected LEDS 67 | # 60 emulation xcvr en 48 testing(C) 68 | pins_high="14 30 44 60" 69 | else 70 | if [ "$KERNEL_VER" -lt "4" ]; then 71 | /usr/bin/dtc -O dtb -o $dir/$DTBOAB.dtbo -b 0 -@ $dir/$DTBOAB.dts 72 | cp $dir/$DTBOAB.dtbo /lib/firmware 73 | echo ${DTBOAB/-/:} > $CAPE 74 | else 75 | # Shouldn't need sudo but sometimes get 76 | # ERROR: open() for /sys/devices/platform/ocp/ocp:P9_13_pinmux/state failed, Permission denied 77 | sudo config-pin -c $dir/$BBIOAB 78 | fi 79 | # Detect A/B PIN with P8_16 80 | pin='46' 81 | if [ ! -d /sys/class/gpio/gpio$pin ]; then 82 | # Depending on default pullup since we haven't loaded the overlay 83 | echo $pin > /sys/class/gpio/export 84 | wait_writeable "$pin" 85 | fi 86 | echo in > /sys/class/gpio/gpio$pin/direction 87 | REV=$(cat /sys/class/gpio/gpio$pin/value) 88 | if [ "$REV" == "0" ]; then 89 | echo Rev B Board 90 | else 91 | echo Rev A Board 92 | fi 93 | # Set inactive 22,23,26,27 Drive select, 31 recovery, 110 write 94 | # 50 read transceiver enable, low enabled 95 | # 2-5 rev A head, 8-11 rev B head, all low is head 0 after inversion 96 | pins_low="2 3 4 5 8 9 10 11 22 23 26 27 31 110 50" 97 | # 14,15 drive selected LEDS 98 | # 44 emu second driver emulation, disable 99 | # 60 MFM emulation tranceiver enable, high is disabled 100 | pins_high="14 15 44 60" 101 | # 30 TRACK 0 102 | pins_in="30" 103 | fi 104 | 105 | # With universal cape header, all pins are exported and set to in by default. 106 | # However, we now support reboot-less operation, so set them all anyway. 107 | for pin in $pins_in $pins_low $pins_high; do 108 | if [ ! -d /sys/class/gpio/gpio$pin ]; then 109 | echo $pin > /sys/class/gpio/export 110 | fi 111 | wait_writeable "$pin" 112 | echo in > /sys/class/gpio/gpio$pin/direction 113 | done 114 | 115 | for pin in $pins_low; do 116 | echo out > /sys/class/gpio/gpio$pin/direction 117 | echo 0 > /sys/class/gpio/gpio$pin/value 118 | done 119 | 120 | for pin in $pins_high; do 121 | echo out > /sys/class/gpio/gpio$pin/direction 122 | echo 1 > /sys/class/gpio/gpio$pin/value 123 | done 124 | 125 | -------------------------------------------------------------------------------- /emu/emu-00C0.dts: -------------------------------------------------------------------------------- 1 | /dts-v1/; 2 | /plugin/; 3 | 4 | / { 5 | compatible = "ti,beaglebone-black", "ti,beaglebone-green"; 6 | 7 | /* identification */ 8 | part-number = "emu"; 9 | 10 | /* version */ 11 | version = "00A0"; 12 | 13 | /* state the resources this cape uses or prepare to get winged! */ 14 | exclusive-use = 15 | /* the pin header P9 uses */ 16 | "P8.11", 17 | "P8.12", 18 | "P8.13", 19 | "P8.14", 20 | "P8.15", 21 | "P8.17", 22 | "P8.19", 23 | "P8.26", 24 | "P8.45", 25 | "P9.11", 26 | "P9.12", 27 | "P9.13", 28 | "P9.14", 29 | "P9.15", 30 | "P9.16", 31 | "P9.17", 32 | "P9.18", 33 | "P9.21", 34 | "P9.22", 35 | "P9.24", 36 | "P9.25", 37 | "P9.26", 38 | "P9.27", 39 | "P9.28", 40 | "P9.29", 41 | "P9.30", 42 | "P9.31", 43 | "P9.41", 44 | "P9.42", 45 | /* Rev B Pins */ 46 | "P8.16", 47 | "P8.31", 48 | "P8,32", 49 | "P8.33", 50 | "P8.35", 51 | /* Hardware IP cores in use */ 52 | "pruss"; 53 | 54 | fragment@0 { 55 | target = <&am33xx_pinmux>; 56 | __overlay__ { 57 | pruicss_stepper_pins: pinmux_pruicss_stepper_pins{ 58 | pinctrl-single,pins = < 59 | // All inputs pullup 60 | // All outputs fast pullup disabled 61 | // These go to PRU0 62 | 0x034 0x06 // OUT P8_11 = pr1_pru0_pru_30_15 63 | 0x030 0x06 // OUT P8_12 = pr1_pru0_pru_30_14 64 | 0x03c 0x35 // IN/OUT P8_15 = pr1_ecap0 pullup input fast 65 | 0x1ac 0x3e // IN P9_25 = pr1_pru0_pru_30_7 66 | 0x1a4 0x05 // OUT P9_27 = pr1_pru0_pru_31_5 67 | 0x19c 0x05 // OUT P9_28 = pr1_pru0_pru_31_3 68 | 0x194 0x05 // OUT P9_29 = pr1_pru0_pru_31_1 69 | 0x198 0x05 // OUT P9_30 = pr1_pru0_pru_31_2 70 | 0x190 0x3e // IN P9_31 = pr1_pru0_pru_30_0 71 | 0x1a8 0x3e // IN P9_41 = pr1_pru0_pru_30_6 72 | 0x1a0 0x05 // OUT P9_42.1 = pr1_pru0_pru_31_4 73 | 0x038 0x3e // IN P8_16 = pru1_pru0_pru_r31_14 74 | 0x184 0x36 // IN P9_24 = pru1_pru0_pru_r31_16, pullup 75 | // Timing testing from PRU1 76 | 0xa0 0x05 // OUT P8_45 = pr1_pru1_pru_30_0 77 | 78 | 79 | // These are GPIO 80 | 0x15C 0x3f // IN P9_17 = gpio0_5 81 | 0x158 0x3f // IN P9_18 = gpio0_4 82 | 0x150 0x3f // IN P9_22 = gpio0_2 83 | 0x154 0x3f // IN P9_21 = gpio0_3 84 | 0x020 0x3f // IN P8_19 = gpio0_22 85 | 0x024 0x37 // IN P8_13 = gpio0_23, pullup 86 | 0x028 0x3f // IN P8_14 = gpio0_26 87 | 0x02c 0x3f // IN P8_17 = gpio0_27 88 | 0x070 0x0f // OUT P9_11 = gpio0_30 89 | 0x078 0x0f // OUT P9_12 = gpio1_28 90 | 0x074 0x0f // OUT P9_13 = gpio0_31 91 | 0x048 0x0f // OUT P9_14 = gpio1_18 92 | 0x040 0x0f // OUT P9_15 = gpio1_16 Testing 93 | 0x180 0x0f // OUT P9_26 = gpio0_14 94 | 0x07c 0x37 // IN P8_26 = gpio1_29, pullup, REV C detect 95 | // Rev B GPIO 96 | 0x0d8 0x3f // IN P8_31 = gpio0_10 97 | 0x0dc 0x3f // IN P8_32 = gpio0_11 98 | 0x0d4 0x3f // IN P8_33 = gpio0_09 99 | 0x0d0 0x3f // IN P8_35 = gpio0_08 100 | // Rev C GPIO 101 | 0x04c 0x0f // OUT P9_16 = gpio1_19 102 | >; 103 | }; 104 | 105 | }; 106 | }; 107 | 108 | fragment@1{ 109 | target = <&pruss>; 110 | __overlay__{ 111 | status = "okay"; 112 | pinctrl-names = "default"; 113 | pinctrl-0 = <&pruicss_stepper_pins>; 114 | 115 | }; 116 | }; 117 | fragment@2 { 118 | target = <&ocp>; 119 | __overlay__ { 120 | test_helper: helper { 121 | compatible = "bone-pinmux-helper"; 122 | pinctrl-names = "default"; 123 | pinctrl-0 = <&pruicss_stepper_pins>; 124 | status = "okay"; 125 | }; 126 | }; 127 | }; 128 | }; 129 | -------------------------------------------------------------------------------- /powerfail/parse_cmdline.c: -------------------------------------------------------------------------------- 1 | #define VERSION "0.3" 2 | // Parse the command line. 3 | // 4 | // Call parse_cmdline to parse the command line 5 | // Call parse_print_cmdline to print drive parameter information in command 6 | // line format 7 | // 8 | // Copyright 2014 David Gesswein. 9 | // This file is part of MFM disk utilities. 10 | // 11 | // MFM disk utilities is free software: you can redistribute it and/or modify 12 | // it under the terms of the GNU General Public License as published by 13 | // the Free Software Foundation, either version 3 of the License, or 14 | // (at your option) any later version. 15 | // 16 | // MFM disk utilities is distributed in the hope that it will be useful, 17 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | // GNU General Public License for more details. 20 | // 21 | // You should have received a copy of the GNU General Public License 22 | // along with MFM disk utilities. If not, see . 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | 31 | #include "msg.h" 32 | 33 | #include "parse_cmdline.h" 34 | 35 | #define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0])) 36 | 37 | // argc, argv: Main argc, argv 38 | // drive_params: Drive parameters where most of the parsed values are stored 39 | void parse_cmdline(int argc, char *argv[], DRIVE_PARAMS *drive_params) 40 | { 41 | #define MIN_OPTS (0x01) 42 | // If you change this fix parse_print_cmdline 43 | struct option long_options[] = { 44 | {"powercmd", 1, NULL, 'p'}, 45 | {"command", 1, NULL, 'c'}, 46 | {"debug", 0, NULL, 'd'}, 47 | {"scale", 1, NULL, 's'}, 48 | {"threshold", 1, NULL, 't'}, 49 | {"wait", 1, NULL, 'w'}, 50 | {"version", 0, NULL, 'v'}, 51 | {NULL, 0, NULL, 0} 52 | }; 53 | char short_options[] = "p:c:ds:t:w:v"; 54 | int rc; 55 | // Loop counters 56 | int i; 57 | int options_index; 58 | // Bit vector of which options were specified 59 | uint32_t opt_mask = 0; 60 | 61 | // Enable all errors other than debug 62 | msg_set_err_mask(~0 ^ MSG_DEBUG); 63 | 64 | // Set defaults 65 | memset(drive_params, 0, sizeof(*drive_params)); 66 | drive_params->scale = .125; 67 | drive_params->threshold = 11.5; 68 | drive_params->wait = .1; 69 | 70 | // Handle the options. The long options are converted to the short 71 | // option name for the switch by getopt_long. 72 | options_index = -1; 73 | while ((rc = getopt_long(argc, argv, short_options, long_options, 74 | &options_index)) != -1 ) { 75 | // Short options don't set options_index so look it up 76 | if (options_index == -1) { 77 | for (i = 0; i < ARRAYSIZE(long_options); i++) { 78 | if (rc == long_options[i].val) { 79 | options_index = i; 80 | break; 81 | } 82 | } 83 | if (options_index == -1) { 84 | //msg(MSG_FATAL, "Error parsing option %c\n",rc); 85 | msg(MSG_FATAL,"Valid options:\n"); 86 | for (i = 0; long_options[i].name != NULL; i++) { 87 | msg(MSG_FATAL, "%c %s\n", long_options[i].val, long_options[i].name); 88 | } 89 | exit(1); 90 | } 91 | } 92 | opt_mask |= 1 << options_index; 93 | switch(rc) { 94 | case 's': 95 | drive_params->scale = atof(optarg); 96 | break; 97 | case 't': 98 | drive_params->threshold = atof(optarg); 99 | break; 100 | case 'w': 101 | drive_params->wait = atof(optarg); 102 | break; 103 | case 'p': 104 | drive_params->powercmd = optarg; 105 | break; 106 | case 'd': 107 | drive_params->debug = 1; 108 | break; 109 | case 'c': 110 | drive_params->command = optarg; 111 | break; 112 | case 'v': 113 | msg(MSG_INFO_SUMMARY,"Version %s\n",VERSION); 114 | break; 115 | case '?': 116 | msg(MSG_FATAL, "Error parsing option\n"); 117 | exit(1); 118 | break; 119 | default: 120 | msg(MSG_FATAL, "Didn't process argument %c\n", rc); 121 | exit(1); 122 | } 123 | options_index = -1; 124 | } 125 | if ((opt_mask & MIN_OPTS) != MIN_OPTS) { 126 | msg(MSG_FATAL, "Program requires options:"); 127 | for (i = 0; i < 32; i++) { 128 | int bit = (1 << i); 129 | if (!(opt_mask & bit) && (MIN_OPTS & bit)) { 130 | msg(MSG_FATAL, " %s", long_options[i].name); 131 | } 132 | } 133 | msg(MSG_FATAL, "\n"); 134 | exit(1); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /emu/prucode.hp: -------------------------------------------------------------------------------- 1 | // prucode.hp 2 | 3 | #ifndef __PRUCODE_HP__ 4 | #define __PRUCODE_HP__ 5 | 6 | // Definitions 7 | 8 | // Refer to this mapping in the file - pruss_intc_mapping.h 9 | #define PRU0_PRU1_INTERRUPT 17 10 | #define PRU1_PRU0_INTERRUPT 18 11 | #define PRU0_ARM_INTERRUPT 19 12 | #define PRU1_ARM_INTERRUPT 20 13 | #define ARM_PRU0_INTERRUPT 21 14 | #define ARM_PRU1_INTERRUPT 22 15 | 16 | #define CONST_PRUSSINTC C0 17 | #define CONST_DMTIMER2 C1 18 | #define CONST_ECAP C3 19 | #define CONST_PRUCFG C4 20 | #define CONST_PRURAM C24 21 | #define CONST_PRURAM_OTHER C25 22 | #define CONST_IEP C26 23 | #define CONST_PRUSHAREDRAM C28 24 | #define CONST_DDR C31 25 | 26 | // Base addresses for the following control registers 27 | #define PRU0_CONTROL 0x00022000 28 | #define PRU1_CONTROL 0x00024000 29 | // Address for the control register (CONTROL) 30 | #define CONTROL 0x0 31 | // Address for the cycle counter register (CYCLE) 32 | #define CYCLE 0x0c 33 | // Address for the Constant table Block Index Register (CTBIR) 34 | #define CTBIR 0x20 35 | // Address for the Constant table Block Index Register (CTBIR1) 36 | #define CTBIR1 0x24 37 | // Address for the Constant table Programmable Pointer Register 0(CTPPR_0) 38 | #define CTPPR_0 0x28 39 | // Address for the Constant table Programmable Pointer Register 1(CTPPR_1) 40 | #define CTPPR_1 0x2C 41 | 42 | 43 | // Interrupt controller registers 44 | #define GER_OFFSET 0x10 45 | #define HIESR_OFFSET 0x34 46 | #define SICR_OFFSET 0x24 47 | #define SISR_OFFSET 0x20 48 | #define EISR_OFFSET 0x28 49 | #define GPIR_OFFSET 0x80 50 | #define HOST_NUM 0 51 | #define CHN_NUM 0 52 | 53 | #define ECAP0_EVT 31 54 | #define GPIO0_EVT 57 55 | // Enhanced capture registers 56 | #define TSCTR 0x00 57 | #define CAP1 0x08 58 | #define CAP2 0x0c 59 | #define CAP3 0x10 60 | #define CAP4 0x14 61 | #define ECCTL1 0x28 62 | #define ECCTL2 0x2a 63 | #define ECFLG 0x2e 64 | #define ECCLR 0x30 65 | 66 | // IEP 67 | #define IEP_COUNT 0x0c 68 | #define INTC_REGS_BASE 0x00004000 69 | #define INTC_CHNMAP_REGS_OFFSET 0x0400 70 | #define INTC_HOSTMAP_REGS_OFFSET 0x0800 71 | #define INTC_HOSTINTPRIO_REGS_OFFSET 0x0900 72 | #define INTC_SYS_INT_REGS_OFFSET 0x0D00 73 | #define INTC_HOSTNEST_REGS_OFFSET 0x1100 74 | 75 | #define GPIO0 0x44e07000 76 | #define GPIO1 0x4804c000 77 | #define GPIO2 0x481Ac000 78 | #define GPIO_IRQSTATUS_0 0x02c 79 | #define GPIO_IRQSTATUS_1 0x030 80 | #define GPIO_IRQSTATUS_RAW_0 0x024 81 | #define GPIO_IRQSTATUS_RAW_1 0x028 82 | #define GPIO_IRQSTATUS_SET_0 0x034 83 | #define GPIO_IRQSTATUS_SET_1 0x038 84 | #define GPIO_DATIN 0x138 85 | #define GPIO_LEVELDECTECT0 0x140 86 | #define GPIO_LEVELDECTECT1 0x144 87 | #define GPIO_RISINGDETECT 0x148 88 | #define GPIO_FALLINGDETECT 0x14c 89 | #define GPIO_CLEARDATAOUT 0x190 90 | #define GPIO_SETDATAOUT 0x194 91 | 92 | 93 | //EDMA registers 94 | #define EDMA0_CC_BASE 0x49000000 95 | #define DMAQNUM0 0x0240 96 | #define QUEPRI 0x0284 97 | #define EMR 0x0300 98 | #define EMCR 0x0308 99 | #define EMCRH 0x030C 100 | #define QEMCR 0x0314 101 | #define CCERRCLR 0x031C 102 | #define DRAE0 0x0340 103 | #define DRAE1 0x0348 104 | #define DRAE2 0x0350 105 | #define DRAE3 0x0358 106 | #define QWMTHRA 0x0620 107 | #define GLOBAL_ESR 0x1010 108 | #define GLOBAL_CER 0x1018 109 | #define GLOBAL_EECR 0x1028 110 | #define GLOBAL_SECR 0x1040 111 | #define GLOBAL_IER 0x1050 112 | #define GLOBAL_IECR 0x1058 113 | #define GLOBAL_IESR 0x1060 114 | #define GLOBAL_ICR 0x1070 115 | // 0x1000 + 0x200 * region, we use region 1 116 | #define DRAE_OFFSET 0x1200 117 | //#define DRAE_OFFSET 0x0000 118 | #define ESR (0x1010+DRAE_OFFSET) 119 | #define EECR (0x1028+DRAE_OFFSET) 120 | #define SECR (0x1040+DRAE_OFFSET) 121 | #define IER (0x1050+DRAE_OFFSET) 122 | #define IPR (0x1068+DRAE_OFFSET) 123 | #define ICR (0x1070+DRAE_OFFSET) 124 | #define IESRL (0x1060+DRAE_OFFSET) 125 | #define IEVAL (0x1078+DRAE_OFFSET) 126 | #define IECR (0x1058+DRAE_OFFSET) 127 | 128 | 129 | //EDMA PARAM registers 130 | #define PARAM_OFFSET 0x4000 131 | #define OPT 0x00 132 | #define SRC 0x04 133 | #define A_B_CNT 0x08 134 | #define DST 0x0C 135 | #define SRC_DST_BIDX 0x10 136 | #define LINK_BCNTRLD 0x14 137 | #define SRC_DST_CIDX 0x18 138 | #define CCNT 0x1C 139 | #define DCHMAP_0 0x100 140 | 141 | #endif // __PRUCODE_HP__ 142 | -------------------------------------------------------------------------------- /mfm/prucode.hp: -------------------------------------------------------------------------------- 1 | // prucode.hp 2 | 3 | #ifndef __PRUCODE_HP__ 4 | #define __PRUCODE_HP__ 5 | 6 | // Definitions 7 | 8 | // Refer to this mapping in the file - pruss_intc_mapping.h 9 | #define PRU0_PRU1_INTERRUPT 17 10 | #define PRU1_PRU0_INTERRUPT 18 11 | #define PRU0_ARM_INTERRUPT 19 12 | #define PRU1_ARM_INTERRUPT 20 13 | #define ARM_PRU0_INTERRUPT 21 14 | #define ARM_PRU1_INTERRUPT 22 15 | 16 | #define CONST_PRUSSINTC C0 17 | #define CONST_DMTIMER2 C1 18 | #define CONST_ECAP C3 19 | #define CONST_PRUCFG C4 20 | #define CONST_PRURAM C24 21 | #define CONST_PRURAM_OTHER C25 22 | #define CONST_IEP C26 23 | #define CONST_PRUSHAREDRAM C28 24 | #define CONST_DDR C31 25 | 26 | // Base addresses for the following control registers 27 | #define PRU0_CONTROL 0x00022000 28 | #define PRU1_CONTROL 0x00024000 29 | // Address for the control register (CONTROL) 30 | #define CONTROL 0x0 31 | // Address for the cycle counter register (CYCLE) 32 | #define CYCLE 0x0c 33 | // Address for the Constant table Block Index Register (CTBIR) 34 | #define CTBIR 0x20 35 | // Address for the Constant table Block Index Register (CTBIR1) 36 | #define CTBIR1 0x24 37 | // Address for the Constant table Programmable Pointer Register 0(CTPPR_0) 38 | #define CTPPR_0 0x28 39 | // Address for the Constant table Programmable Pointer Register 1(CTPPR_1) 40 | #define CTPPR_1 0x2C 41 | 42 | 43 | // Interrupt controller registers 44 | #define GER_OFFSET 0x10 45 | #define HIESR_OFFSET 0x34 46 | #define SICR_OFFSET 0x24 47 | #define SISR_OFFSET 0x20 48 | #define EISR_OFFSET 0x28 49 | #define GPIR_OFFSET 0x80 50 | #define HOST_NUM 0 51 | #define CHN_NUM 0 52 | 53 | #define ECAP0_EVT 31 54 | #define GPIO0_EVT 57 55 | // Enhanced capture registers 56 | #define TSCTR 0x00 57 | #define CAP1 0x08 58 | #define CAP2 0x0c 59 | #define CAP3 0x10 60 | #define CAP4 0x14 61 | #define ECCTL1 0x28 62 | #define ECCTL2 0x2a 63 | #define ECFLG 0x2e 64 | #define ECCLR 0x30 65 | 66 | // IEP 67 | #define IEP_COUNT 0x0c 68 | #define INTC_REGS_BASE 0x00004000 69 | #define INTC_CHNMAP_REGS_OFFSET 0x0400 70 | #define INTC_HOSTMAP_REGS_OFFSET 0x0800 71 | #define INTC_HOSTINTPRIO_REGS_OFFSET 0x0900 72 | #define INTC_SYS_INT_REGS_OFFSET 0x0D00 73 | #define INTC_HOSTNEST_REGS_OFFSET 0x1100 74 | 75 | #define GPIO0 0x44e07000 76 | #define GPIO1 0x4804c000 77 | #define GPIO2 0x481Ac000 78 | #define GPIO3 0x481AE000 79 | #define GPIO_IRQSTATUS_0 0x02c 80 | #define GPIO_IRQSTATUS_1 0x030 81 | #define GPIO_IRQSTATUS_RAW_0 0x024 82 | #define GPIO_IRQSTATUS_RAW_1 0x028 83 | #define GPIO_IRQSTATUS_SET_0 0x034 84 | #define GPIO_IRQSTATUS_SET_1 0x038 85 | #define GPIO_DATIN 0x138 86 | #define GPIO_LEVELDECTECT0 0x140 87 | #define GPIO_LEVELDECTECT1 0x144 88 | #define GPIO_RISINGDETECT 0x148 89 | #define GPIO_FALLINGDETECT 0x14c 90 | #define GPIO_CLEARDATAOUT 0x190 91 | #define GPIO_SETDATAOUT 0x194 92 | 93 | 94 | //EDMA registers 95 | #define EDMA0_CC_BASE 0x49000000 96 | #define DMAQNUM0 0x0240 97 | #define QUEPRI 0x0284 98 | #define EMR 0x0300 99 | #define EMCR 0x0308 100 | #define EMCRH 0x030C 101 | #define QEMCR 0x0314 102 | #define CCERRCLR 0x031C 103 | #define DRAE0 0x0340 104 | #define DRAE1 0x0348 105 | #define DRAE2 0x0350 106 | #define DRAE3 0x0358 107 | #define QWMTHRA 0x0620 108 | #define GLOBAL_ESR 0x1010 109 | #define GLOBAL_CER 0x1018 110 | #define GLOBAL_EECR 0x1028 111 | #define GLOBAL_SECR 0x1040 112 | #define GLOBAL_IER 0x1050 113 | #define GLOBAL_IECR 0x1058 114 | #define GLOBAL_IESR 0x1060 115 | #define GLOBAL_ICR 0x1070 116 | // 0x1000 + 0x200 * region, we use region 1 117 | #define DRAE_OFFSET 0x1200 118 | //#define DRAE_OFFSET 0x0000 119 | #define ESR (0x1010+DRAE_OFFSET) 120 | #define EECR (0x1028+DRAE_OFFSET) 121 | #define SECR (0x1040+DRAE_OFFSET) 122 | #define IER (0x1050+DRAE_OFFSET) 123 | #define IPR (0x1068+DRAE_OFFSET) 124 | #define ICR (0x1070+DRAE_OFFSET) 125 | #define IESRL (0x1060+DRAE_OFFSET) 126 | #define IEVAL (0x1078+DRAE_OFFSET) 127 | #define IECR (0x1058+DRAE_OFFSET) 128 | 129 | 130 | //EDMA PARAM registers 131 | #define PARAM_OFFSET 0x4000 132 | #define OPT 0x00 133 | #define SRC 0x04 134 | #define A_B_CNT 0x08 135 | #define DST 0x0C 136 | #define SRC_DST_BIDX 0x10 137 | #define LINK_BCNTRLD 0x14 138 | #define SRC_DST_CIDX 0x18 139 | #define CCNT 0x1C 140 | #define DCHMAP_0 0x100 141 | 142 | #define AM335X_CTRL_BASE 0x44e10000 143 | #define CONF_MCASP0_ACLKX 0x990 144 | 145 | #endif // __PRUCODE_HP__ 146 | -------------------------------------------------------------------------------- /mfm/board.c: -------------------------------------------------------------------------------- 1 | // This module is routines for determing which version of the MFM board 2 | // is present. Also sets CPU speed for beaglebone 3 | // board_initialize sets up this module. 4 | // board_get_revision returns the revision of the MFM emulator board 5 | // 6 | // 09/17/2023 DJG Moved set_restore_max_cpu_speed to this file as board_ 7 | // to only have one copy 8 | // 03/22/2019 DJG Added REV C support 9 | // 02/08/2017 DJG Fix incorrect format for print 10 | // 10/16/2016 DJG Improved error message 11 | // 12/24/2015 DJG Fix wrong value in error print 12 | // 08/01/2015 DJG New module to support revision B board. 13 | // 14 | // Copyright 2019 David Gesswein. 15 | // This file is part of MFM disk utilities. 16 | // 17 | // MFM disk utilities is free software: you can redistribute it and/or modify 18 | // it under the terms of the GNU General Public License as published by 19 | // the Free Software Foundation, either version 3 of the License, or 20 | // (at your option) any later version. 21 | // 22 | // MFM disk utilities is distributed in the hope that it will be useful, 23 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 24 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 | // GNU General Public License for more details. 26 | // 27 | // You should have received a copy of the GNU General Public License 28 | // along with MFM disk utilities. If not, see . 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include "msg.h" 41 | #include "cmd.h" 42 | #include "board.h" 43 | 44 | #define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0])) 45 | 46 | // 0 = first/A, 1 = B. Used to index arrays so can't change encoding 47 | static int board_revision = -1; 48 | 49 | // Perform any setup needed by this module. Call once before any other 50 | // routine 51 | void board_initialize(void) { 52 | int pins[] = {REVC_DETECT_PIN, REVB_DETECT_PIN}; 53 | int fd; 54 | int i; 55 | char str[128]; 56 | if (board_revision == -1) { 57 | // Default is A 58 | board_revision = 0; 59 | for (i = 0; i < ARRAYSIZE(pins); i++) { 60 | sprintf(str, "/sys/class/gpio/gpio%d/value", pins[i]); 61 | fd = open(str, O_RDONLY); 62 | if (fd < 0) { 63 | msg(MSG_FATAL, "Unable to open pin %d, did you run the setup script?\n", pins[i]); 64 | exit(1); 65 | } 66 | // Pin grounded to indicate version 67 | pread(fd, str, sizeof(str), 0); 68 | close(fd); 69 | if (str[0] == '0') { 70 | board_revision = ARRAYSIZE(pins) - i; 71 | break; 72 | } 73 | } 74 | } 75 | msg(MSG_INFO, "Board revision %c detected\n", 'A' + board_revision); 76 | } 77 | 78 | // Return board revision 79 | int board_get_revision(void) { 80 | return board_revision; 81 | } 82 | 83 | // This routine will either set the CPU speed to maximum or restore it 84 | // back to the previous setting. The normal governor didn't boost the 85 | // processor sufficiently to prevent getting behind in processing. 86 | // 87 | // restore: 0 to set speed to maximum, 1 to restore value from previous 88 | // call 89 | int board_set_restore_max_cpu_speed(int restore) { 90 | FILE *file; 91 | static char governor[100]; 92 | static int freq_changed = 0; 93 | char maxfreq[100]; 94 | 95 | if (!restore) { 96 | file = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "r"); 97 | if (file == NULL) { 98 | return -1; 99 | } 100 | if (fscanf(file, "%100s", governor) < 1) { 101 | return -1; 102 | } 103 | if (strcmp(governor, "performance") == 0) { 104 | // performance *is* guaranteed max speed 105 | return 0; 106 | } 107 | file = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "w"); 108 | if (file == NULL) { 109 | return -1; 110 | } 111 | if (fprintf(file,"userspace") < 1) { 112 | return -1; 113 | } 114 | fclose(file); 115 | file = fopen("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", "r"); 116 | if (file == NULL) { 117 | return -1; 118 | } 119 | if (fscanf(file, "%100s", maxfreq) < 1) { 120 | return -1; 121 | } 122 | fclose(file); 123 | 124 | file = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed", "w"); 125 | if (file == NULL) { 126 | return -1; 127 | } 128 | if (fprintf(file,maxfreq) < 1) { 129 | return -1; 130 | } 131 | fclose(file); 132 | freq_changed = 1; 133 | } else { 134 | if (freq_changed) { 135 | file = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "w"); 136 | if (file == NULL) { 137 | return -1; 138 | } 139 | if (fprintf(file,governor) < 1) { 140 | return -1; 141 | } 142 | fclose(file); 143 | } 144 | freq_changed = 0; 145 | } 146 | return 0; 147 | } 148 | -------------------------------------------------------------------------------- /mfm/mfm_write.c: -------------------------------------------------------------------------------- 1 | // This module is the routines needed to write a MFM disk. It does not 2 | // attempt to handle bad locations on the disk. It does not have 3 | // proper command line processing. Edit main. 4 | // 5 | // Copyright 2021 David Gesswein. 6 | // This file is part of MFM disk utilities. 7 | // 8 | // MFM disk utilities is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // MFM disk utilities is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU General Public License 19 | // along with MFM disk utilities. If not, see . 20 | // 21 | // 09/17/23 Changed to calling pru_exec_program to set correct path for file 22 | // to load and board_set_restore_max_cpu_speed to have one copy 23 | // 09/12/23 JST Changes to support 5.10 kernel and --sync option 24 | // 05/05/23 GL Fix by Gleb Larionov for hang on first write 25 | // 01/18/21 DJG Updated function call 26 | // 03/22/19 DJG Added REV C support 27 | // 03/14/19 DJG Set PRU clock rate. Needed for SA1000 support 28 | // 03/09/18 DJG Use drive specified on command line 29 | // 06/30/17 DJG Set drive parameters to number of cylinder and heads in 30 | // emulation file. Get rid of hard coded values. 31 | // 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | // MFM program include files 43 | #include 44 | 45 | #include "msg.h" 46 | #include "pru_setup.h" 47 | #include "crc_ecc.h" 48 | #define DEF_DATA 49 | #include "emu_tran_file.h" 50 | #include "mfm_decoder.h" 51 | #include "parse_cmdline.h" 52 | #include "drive.h" 53 | #include "board.h" 54 | 55 | #include "cmd.h" 56 | #include "cmd_write.h" 57 | 58 | // This routine is for cleaning up when shutting down. It is installed as an 59 | // atexit routine and called by SIGINT handler. 60 | void shutdown(void) 61 | { 62 | static int called = 0; 63 | 64 | if (called) 65 | return; 66 | called = 1; 67 | 68 | board_set_restore_max_cpu_speed(1); 69 | // Turn off selected light on drive 70 | drive_select(0); 71 | 72 | pru_restart(0); 73 | 74 | pru_exec_cmd(CMD_EXIT, 0); 75 | pru_shutdown(); 76 | } 77 | 78 | // SIGINT/ control-c handler. Call our shutdown and exit 79 | void shutdown_signal(void) 80 | { 81 | shutdown(); 82 | exit(1); 83 | } 84 | 85 | // Main routine. If specified analyze disk format and read disk contents 86 | int main(int argc, char *argv[]) 87 | { 88 | // Drive parameters either from command line or analyzing the disk 89 | DRIVE_PARAMS drive_params; 90 | // Size of shared memory in bytes 91 | int ddr_mem_size; 92 | EMU_FILE_INFO emu_file_info; 93 | 94 | 95 | board_initialize(); 96 | 97 | // Find out what we should do 98 | parse_cmdline(argc, argv, &drive_params, "", 1, 0, 0, 0); 99 | parse_validate_options(&drive_params, 0); 100 | 101 | if (drive_params.emulation_filename == NULL) { 102 | msg(MSG_FATAL, "Emulation filenames must be specified\n"); 103 | exit(1); 104 | } 105 | 106 | if (drive_params.drive == 0) { 107 | msg(MSG_FATAL, "Drive must be specified\n"); 108 | exit(1); 109 | } 110 | 111 | drive_params.emu_fd = emu_file_read_header( 112 | drive_params.emulation_filename, &emu_file_info, 0, 0); 113 | drive_params.start_time_ns = emu_file_info.start_time_ns; 114 | drive_params.emu_file_info = &emu_file_info; 115 | drive_params.num_cyl = emu_file_info.num_cyl; 116 | drive_params.num_head = emu_file_info.num_head; 117 | 118 | 119 | // Initialize PRU 120 | ddr_mem_size = pru_setup(2); 121 | pru_set_clock(drive_params.emu_file_info->sample_rate_hz, PRU_SET_CLOCK_NO_HALT); 122 | if (ddr_mem_size == -1) { 123 | exit(1); 124 | } 125 | 126 | // And start our code 127 | if (pru_exec_program(0, "mfm_write0.bin") != 0) { 128 | msg(MSG_FATAL, "Unable to execute mfm_write0.bin\n"); 129 | exit(1); 130 | } 131 | // DMA channel 7 and PaRAM blocks 7-8 are reserved in our new dto 132 | pru_write_word(MEM_PRU1_DATA,PRU1_DMA_CHANNEL, 7); // (Moved here by Gleb Larionov 05.05.2023) 133 | if (pru_exec_program(1, "mfm_write1.bin") != 0) { 134 | msg(MSG_FATAL, "Unable to execute mfm_write1.bin\n"); 135 | exit(1); 136 | } 137 | 138 | pru_write_word(MEM_PRU0_DATA, PRU0_START_TIME_CLOCKS, 139 | drive_params.start_time_ns / CLOCKS_TO_NS); 140 | pru_write_word(MEM_PRU0_DATA, PRU0_BOARD_REVISION, board_get_revision()); 141 | pru_write_word(MEM_PRU1_DATA,PRU1_DRIVE0_TRACK_HEADER_BYTES, 142 | drive_params.emu_file_info->track_header_size_bytes); 143 | pru_write_word(MEM_PRU1_DATA,PRU1_DRIVE0_TRACK_DATA_BYTES, 144 | drive_params.emu_file_info->track_data_size_bytes); 145 | 146 | 147 | // Cleanup when we exit 148 | signal(SIGINT,(__sighandler_t) shutdown_signal); 149 | atexit(shutdown); 150 | 151 | // This is needed to keep up with the data. Without it we will take 152 | // more than 2 revolutions per track due to the default governor not 153 | // increasing the CPU speed enough. We switch frequently between busy and 154 | // sleeping. 155 | if (board_set_restore_max_cpu_speed(0)) { 156 | msg(MSG_ERR, "Unable to set CPU to maximum speed\n"); 157 | } 158 | 159 | drive_setup(&drive_params); 160 | drive_write_disk(&drive_params); 161 | 162 | pru_exec_cmd(CMD_EXIT, 0); 163 | drive_select(0); 164 | 165 | return (0); 166 | } 167 | 168 | -------------------------------------------------------------------------------- /mfm/ext2emu_doc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 |

19 | Ext2emu converts an extracted data file to an emulator file.

20 |

21 |
22 | 23 |

24 |

--cylinders -c #

25 |

The number of 26 | cylinders.

27 |

--emulation_file -m filename

28 |

File name to write 29 | emulation bit data to.

30 |

--extracted_data_file -e filename

31 |

File name to read 32 | decoded data from.

33 |

--format -f formatName

34 |

The track format. 35 | Use --format help to list all currently supported formats.

36 |

--heads -h #

37 |

The number of 38 | heads.

39 |

--interleave -i #[,#]

40 |

The sector 41 | interleave and track interleave. The first parameter is the value to 42 | increment sector number by between sectors on the same track. If 43 | interleave is not specified default of 1 is used. The second 44 | parameter is the value to increment the first sector by for next 45 | track on the same cylinder. Default is zero.

46 |

You can use 47 | –analyze on an existing disk or image to determine the proper 48 | value. Subtract the first two number printed for interleave and use 49 | that value. For example below use 7. 50 |

51 |

Interleave (not 52 | checked): 0 7 14 4 11 1 8 15 5 12 2 9 16 6 13 3 10

53 |

If you get a 54 | message like “Interleave mismatch previous entry...” it may 55 | indicate the second parameter should be non zero. You would need to 56 | use mfm_read/mfm_util –quiet 1 to see the sector values and 57 | manually determine the proper value. Getting the second parameter 58 | wrong will only have a small impact on performance. Getting the first 59 | value wrong can significantly reduce performance.

60 |

--mark_bad -M #,#,#:#,#,#...

61 |

The 62 | cylinder,head,sector to mark bad by complementing the CRC/Check code. 63 | Use to force sectors that are known to be bad to be seen as bad by 64 | the host system.

65 |

--note -n “string”

66 |

String is stored in 67 | header of emulation file for information about image. mfm_util will 68 | display.

69 |

--quiet -q #h

70 |

Bit mask to select 71 | which messages not to print. 0 is print all messages. Default is 1 72 | (no debug messages). Higher bits are more important messages in 73 | general.

74 |

--version -v

75 |

Print program 76 | version number.

77 |


78 | 79 |

80 |

Long options can be abbreviated to the 81 | shortest unique name. Option values can't have spaces unless quoted 82 | as a string.

83 |


84 | 85 |

86 |

# is a number. #h is a number which may 87 | be decimal, octal if starts with a 0, or hex starting with 0x.

88 |


89 | 90 |

91 |

Cylinders, heads, format, 92 | emulation_file and extracted_data_file are required. The cylinders, 93 | format, and heads parameters can be determined by mfm_util if an 94 | emulator or transitions file is available.

95 |


96 | 97 |

98 |

NOTE: Some computers use different 99 | format on different tracks. This program can only handle one format.

100 |


101 | 102 |

103 |

Example:

104 |


105 | 106 |

107 |

ext2emu --extracted_data_file 108 | extracted_data --emulation_file emulation_file --cylinders 300 109 | --heads 4 --format OMTI_5510 --interleave 3,1 –mark_bad 110 | 3,2,6:6,1,3:200,4,14 –note “Regenerated file”

111 |


112 | 113 |

114 |

This would generate sectors 0, 3, 6… 115 | for first track, 1, 4, 7... for second track, 2, 5, 8... for third. 116 | The start of the next cylinder will start back with sector 0.

117 |


118 | 119 |

120 |

If the extracted data file size 121 | calculated using the specified cylinders and tracks doesn't match the 122 | actual file size the warning message “Calculated extract file size 123 | # bytes, actual size #'” will be printed. Verify the parameters are 124 | correct for the file being converted.

125 | 126 | -------------------------------------------------------------------------------- /contrib/RLL/hack_rll.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (c) 2025 Poul-Henning Kamp 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 1. Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 2. Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | # ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | # SUCH DAMAGE. 26 | 27 | ''' 28 | Decode Western Digital WD1006 RLL 2,7 ST506 disk content 29 | ======================================================== 30 | 31 | This script was written and used to decode the data content 32 | of a disk attached to a WD1006 RLL 2,7 controller in an old 33 | PC. 34 | 35 | The disk was read with David Gesswein's MFM Emulator: 36 | 37 | http://www.pdp8online.com/mfm/mfm.shtml 38 | 39 | And saved as raw flux-transitions in a "transitions file. 40 | 41 | This code makes no attempt to understand the track structure 42 | of the transition file, it simply treats all of it as a flux 43 | stream and prints out whatever address marks and sectors as 44 | hexdumps on stdout. 45 | 46 | Western Digitals more advanced diskcontrollers could use either 47 | a 32 or 56 bit CRC polynomial, defaulting to 56 bit. 48 | 49 | That 56-bit polynomial is documented on pdf page 45 in: 50 | 51 | http://bitsavers.org/pdf/westernDigital/pc_disk_controller/WD1007A_AT_ESDI_AM8753/WD1007-WAH_OEM_Manual_198802.pdf 52 | 53 | On behalf of Datamuseum.dk and myself: 54 | 55 | Many thanks to Davis Gesswein for designing the MFM-reader 56 | and just as many thanks to All Kossow for bitsavers.org. 57 | 58 | Poul-Henning 59 | 60 | ''' 61 | 62 | import sys 63 | 64 | import crcmod 65 | 66 | # The address marks used the usual CRC-16 67 | crc_func = crcmod.mkCrcFun(poly=0x11021, rev=False, initCrc=0xffff) 68 | 69 | # The sectors use a 56 bit "ECC" code which is really just a CRC 70 | # but CRCmod does not have a 56 bit variant, so... 71 | 72 | def crc_poly(data, n, poly, crc=0): 73 | for d in data: 74 | crc ^= d << (n - 8) 75 | for _ in range(8): 76 | crc <<= 1 77 | if crc & (1 << n): 78 | crc ^= poly 79 | return crc 80 | 81 | datapoly = 0 82 | for p in (56, 52,50,43,41,34,30,26,24,8, 0): 83 | datapoly |= 1<<(p) 84 | 85 | def first_pass(fn): 86 | ''' Iterate the input file as clock periods ''' 87 | # We really should do the proper thing with headers etc. 88 | with open(fn, "rb") as file: 89 | while True: 90 | buffer = file.read(4096) 91 | if len(buffer) == 0: 92 | return 93 | for octet in buffer: 94 | yield max(0, int((octet+6.666)/13.333)) 95 | 96 | def second_pass(fn): 97 | ''' Iterate over sequences separated by clock sync pattern ''' 98 | acc = [] 99 | for x in first_pass(fn): 100 | acc.append(x) 101 | if acc[-2:] == [8, 3]: 102 | yield acc[:-2] 103 | acc = [] 104 | yield acc 105 | 106 | def third_pass(fn): 107 | ''' Iterate as text-representation of waveform ''' 108 | for buf in second_pass(fn): 109 | yield "|".join("-" * (dt-1) for dt in buf) + "|" 110 | 111 | def fourth_pass(fn): 112 | ''' Decode RLL 2,7 as binary bits as text ''' 113 | for buf in third_pass(fn): 114 | # We emit the length here, to help resolve which AM belongs to which DM 115 | print(4, len(buf)) 116 | retval = [] 117 | pos = 2 118 | while pos < len(buf): 119 | if buf[pos:pos+4] == '-|--': 120 | retval.append('10') 121 | pos += 4 122 | elif buf[pos:pos+4] == '|---': 123 | retval.append('11') 124 | pos += 4 125 | elif buf[pos:pos+6] == '|--|--': 126 | retval.append('000') 127 | pos += 6 128 | elif buf[pos:pos+6] == '---|--': 129 | retval.append('010') 130 | pos += 6 131 | elif buf[pos:pos+6] == '--|---': 132 | retval.append('011') 133 | pos += 6 134 | elif buf[pos:pos+8] == '--|--|--': 135 | retval.append('0010') 136 | pos += 8 137 | elif buf[pos:pos+8] == '----|---': 138 | retval.append('0011') 139 | pos += 8 140 | else: 141 | break 142 | retval.append(buf[0]) 143 | pos += 1 144 | yield ''.join(retval) 145 | 146 | def fifth_pass(fn): 147 | ''' Iterate as bytes ''' 148 | for buf in fourth_pass(fn): 149 | yield bytes(int(buf[x:x+8], 2) for x in range(1, len(buf)-7, 8)) 150 | 151 | def main(fn): 152 | ''' Check CRC and emit ''' 153 | for buf in fifth_pass(fn): 154 | if len(buf) == 0: 155 | pass 156 | elif buf[0] == 0xf8: 157 | c = crc_poly(b'\xa1' + buf[:520], 56, datapoly, crc=(1<<56)-1) 158 | print("6 DM", "%5d" % len(buf), "%014x" % c, buf[:520].hex()) 159 | elif buf[0] > 0xf8: 160 | c = crc_func(b'\xa1' + buf[:6]) 161 | print("6 AM", "%5d" % len(buf), "%04x" % c, buf[:6].hex()) 162 | else: 163 | print("6 ??", "%5d" % len(buf), "f", buf[:20].hex()) 164 | 165 | if __name__ == "__main__": 166 | assert len(sys.argv) == 2 167 | main(sys.argv[1]) 168 | -------------------------------------------------------------------------------- /powerfail/powerfail.c: -------------------------------------------------------------------------------- 1 | // This program monitors the input voltage to the MFM reader/emulator 2 | // and powers down the board when input power is lost. 3 | // 4 | // Copyright 2014 David Gesswein. 5 | // This file is part of MFM disk utilities. 6 | // 7 | // 09/12/23 JST/DJG Changes to avoid reboot loop if 12V always low. 8 | // 11/16/18 DJG Adapt sleep time to wait value specified 9 | // 06/05/16 DJG Fix using read buffer when read failed 10 | // 09/07/14 DJG Ignore temporary A/D read failures 11 | // 12 | // MFM disk utilities is free software: you can redistribute it and/or modify 13 | // it under the terms of the GNU General Public License as published by 14 | // the Free Software Foundation, either version 3 of the License, or 15 | // (at your option) any later version. 16 | // 17 | // MFM disk utilities is distributed in the hope that it will be useful, 18 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | // GNU General Public License for more details. 21 | // 22 | // You should have received a copy of the GNU General Public License 23 | // along with MFM disk utilities. If not, see . 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "msg.h" 40 | #include "parse_cmdline.h" 41 | 42 | #define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0])) 43 | 44 | // process id of sub command 45 | int pid = 0; 46 | 47 | void shutdown_signal(void); 48 | 49 | // Main routine. 50 | int main(int argc, char *argv[]) 51 | { 52 | // Program parameters 53 | DRIVE_PARAMS drive_params; 54 | // Access to A/D channel input voltage is connected to 55 | int fd; 56 | char *ad_dev = "/sys/bus/iio/devices/iio:device0/in_voltage0_raw"; 57 | char buf[100]; 58 | int rc; 59 | // Counts voltage under threhold. We require more than one to prevent 60 | // shutdown from a transient 61 | int under_count = 0; 62 | // Minimum number of counts under threshold to power down 63 | int min_under_count; 64 | // For collecting statistics for debug print 65 | int stat_count = 0; 66 | float ad_sum = 0; 67 | float ad_min = 999; 68 | float ad_max = -999; 69 | float voltage; 70 | // A/D read failures 71 | int err_counter = 0; 72 | int sleep_time_us; 73 | // flag to track if we have seen a valid voltage ever 74 | int valid = 0; 75 | int valid_err_printed = 0; 76 | 77 | // Cleanup when we exit 78 | signal(SIGINT,(__sighandler_t) shutdown_signal); 79 | 80 | // Find out what we should do 81 | parse_cmdline(argc, argv, &drive_params); 82 | 83 | fd = open(ad_dev, O_RDONLY); 84 | if (fd < 0) { 85 | msg(MSG_FATAL, "Unable to open A/D device %s:\n %s\n",ad_dev, 86 | strerror(errno)); 87 | exit(1); 88 | } 89 | 90 | // If a command was specifed execute it in a separate process 91 | if (drive_params.command != NULL ) { 92 | int rc; 93 | 94 | pid = fork(); 95 | if (pid == -1) { 96 | msg(MSG_FATAL, "Fork failed %s", strerror(errno)); 97 | exit(1); 98 | } 99 | if (pid == 0) { 100 | setpgid(0,0); 101 | rc = system(drive_params.command); 102 | if (rc == -1) { 103 | msg(MSG_FATAL, "Executing command failed: %s\n", 104 | strerror(errno)); 105 | } 106 | exit(WEXITSTATUS(rc)); 107 | } 108 | } 109 | 110 | // If wait time >= 20 milliseconds check twice otherwise check 111 | // once. Double check probably not needed but leaving alone in case 112 | if (drive_params.wait >= .02) { 113 | sleep_time_us = round(drive_params.wait / 2.0 * 1e6); 114 | min_under_count = 2; 115 | } else { 116 | sleep_time_us = round(drive_params.wait * 1e6); 117 | min_under_count = 1; 118 | } 119 | 120 | // Read A/D and if enough samples are under threshold tell command 121 | // we executed to terminate then execute poweroff command 122 | while(1) { 123 | rc = pread(fd, buf, sizeof(buf), 0); 124 | // For unknown reasions the read occasionally fails with 125 | // Resource temporarily unavailable. Don't give up unless it 126 | // happens too often. 127 | if (rc < 0) { 128 | if (err_counter++ > 5) { 129 | msg(MSG_FATAL, "A/D read failed %s\n",strerror(errno)); 130 | exit(1); 131 | } else { 132 | msg(MSG_INFO, "A/D read failed %d %s\n",err_counter, 133 | strerror(errno)); 134 | } 135 | } else if (rc == 0) { 136 | msg(MSG_FATAL, "A/D read returned no data\n"); 137 | exit(1); 138 | } else { 139 | err_counter = 0; 140 | voltage = atoi(buf) * 1.8 / 4096.0 / drive_params.scale; 141 | if (stat_count <= 50 && drive_params.debug) { 142 | ad_sum += voltage; 143 | if (voltage > ad_max) { 144 | ad_max = voltage; 145 | } 146 | if (voltage < ad_min) { 147 | ad_min = voltage; 148 | } 149 | stat_count++; 150 | if (stat_count == 50) { 151 | msg(MSG_INFO, "Average %.2fV max %.2fV min %.2fV\n", 152 | ad_sum / stat_count, ad_max, ad_min); 153 | } 154 | } 155 | if (voltage < drive_params.threshold) { 156 | if (drive_params.debug) { 157 | msg(MSG_INFO, "Voltage %.2f under threshold %.2f count %d\n", 158 | voltage, drive_params.threshold, under_count); 159 | } 160 | // do not enter a boot loop if BB is powered but cape is not 161 | if (++under_count >= min_under_count) { 162 | if (valid) { 163 | msg(MSG_FATAL, "Voltage under threshold too long\n"); 164 | if (pid != 0) { 165 | kill(-pid, SIGINT); 166 | waitpid(pid, NULL, 0); 167 | } 168 | rc = system(drive_params.powercmd); 169 | if (rc == -1) { 170 | msg(MSG_FATAL, "Executing power off command failed: %s\n", 171 | strerror(errno)); 172 | } 173 | exit(0); 174 | } else { 175 | if (!valid_err_printed) { 176 | msg(MSG_ERR, "Voltage under threshold too long but never valid\n"); 177 | valid_err_printed = 1; 178 | } 179 | } 180 | } 181 | } else { 182 | if (voltage >= drive_params.threshold) { 183 | valid = 1; 184 | under_count = 0; 185 | } 186 | } 187 | } 188 | usleep(sleep_time_us); 189 | } 190 | 191 | return (0); 192 | } 193 | 194 | // SIGINT/ control-c handler. Call our shutdown and exit 195 | void shutdown_signal(void) 196 | { 197 | if (pid != 0) { 198 | kill(-pid, SIGINT); 199 | waitpid(pid, NULL, 0); 200 | } 201 | exit(1); 202 | } 203 | -------------------------------------------------------------------------------- /mfm/mfm_read.c: -------------------------------------------------------------------------------- 1 | // This module is the routines needed to read a MFM disk. It can analyze the 2 | // disk to determine format, read the disk and store raw delta transition data 3 | // and decoded sector data. 4 | 5 | // TODO Make handle more complex interleave like RD53 (cyl to cyl is 8, track 6 | // to track is -1 or 16) 7 | // 8 | // 10/30/24 DJG Add new option to handle Xebec data skewed one sector from 9 | // header 10 | // 10/09/23 Remove interleave as option so ext2emu can be parsed better 11 | // 09/17/23 Changed to calling pru_exec_program to set correct path for file 12 | // to load and board_set_restore_max_cpu_speed to have one copy 13 | // 01/20/21 DJG Fixed call 14 | // 07/07/19 DJG Turn off recovery line when exiting 15 | // 03/22/19 DJG Added REV C support 16 | // 04/20/18 DJG Fixed previous change to work properly with analyze 17 | // 03/09/18 DJG Added ability to request reading more heads or cylinders 18 | // than analyze detects 19 | // 09/07/16 DJG Report possible reversal of 20 pin cable 20 | // 12/31/15 DJG Parameter change to parse_print_cmdline 21 | // 08/02/15 DJG Added support for rev B board 22 | // 05/17/15 DJG Added analyze of specified cylinder and head. 23 | // 01/04/15 DJG Added support for Corvus_H and NorthStar Advantage 24 | // These had a sector straddle the index pulse so the start_time_ns 25 | // logic added to allow read to start with start of physical first sector. 26 | // Corvus_H also uses 11 MHz for clock/data bit cell so various changes to 27 | // support other rates. 28 | // Fixed off by 1 allocating command line storage 29 | // 11/09/14 DJG Changes for note option 30 | // 31 | // Copyright 2019 David Gesswein. 32 | // This file is part of MFM disk utilities. 33 | // 34 | // MFM disk utilities is free software: you can redistribute it and/or modify 35 | // it under the terms of the GNU General Public License as published by 36 | // the Free Software Foundation, either version 3 of the License, or 37 | // (at your option) any later version. 38 | // 39 | // MFM disk utilities is distributed in the hope that it will be useful, 40 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 41 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 42 | // GNU General Public License for more details. 43 | // 44 | // You should have received a copy of the GNU General Public License 45 | // along with MFM disk utilities. If not, see . 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | 56 | // MFM program include files 57 | #include 58 | 59 | #include "msg.h" 60 | #include "pru_setup.h" 61 | #include "crc_ecc.h" 62 | #define DEF_DATA 63 | #include "emu_tran_file.h" 64 | #include "mfm_decoder.h" 65 | #include "parse_cmdline.h" 66 | #include "analyze.h" 67 | #include "deltas_read.h" 68 | #include "drive.h" 69 | #include "board.h" 70 | 71 | #include "cmd.h" 72 | 73 | // This routine is for cleaning up when shutting down. It is installed as an 74 | // atexit routine and called by SIGINT handler. 75 | void shutdown(void) 76 | { 77 | static int called = 0; 78 | 79 | if (called) 80 | return; 81 | called = 1; 82 | 83 | // Turn off recovery mode 84 | drive_enable_recovery(0); 85 | 86 | board_set_restore_max_cpu_speed(1); 87 | // Turn off selected light on drive 88 | drive_select(0); 89 | 90 | pru_restart(0); 91 | 92 | deltas_stop_thread(); 93 | pru_exec_cmd(CMD_EXIT, 0); 94 | pru_shutdown(); 95 | } 96 | 97 | // SIGINT/ control-c handler. Call our shutdown and exit 98 | void shutdown_signal(void) 99 | { 100 | shutdown(); 101 | exit(1); 102 | } 103 | 104 | // Main routine. If specified analyze disk format and read disk contents 105 | int main(int argc, char *argv[]) 106 | { 107 | // Drive parameters either from command line or analyzing the disk 108 | DRIVE_PARAMS drive_params; 109 | // What we wish to do 110 | int read = 0; 111 | // Memory deltas from PRU are put in 112 | uint32_t *deltas; 113 | // Size of shared memory/deltas in bytes 114 | int ddr_mem_size; 115 | char *cmdline; 116 | int max_deltas; 117 | 118 | board_initialize(); 119 | 120 | // Find out what we should do 121 | // M is only for ext2emu. i no longer used by mfm_read/util 122 | parse_cmdline(argc, argv, &drive_params, "Mi", 1, 0, 0, 0); 123 | parse_validate_options(&drive_params, 1); 124 | 125 | // If they specified a file name then we read the disk 126 | if (drive_params.extract_filename != NULL || 127 | drive_params.emulation_filename != NULL || 128 | drive_params.transitions_filename != NULL) { 129 | read = 1; 130 | } 131 | if (!read && !drive_params.analyze) { 132 | msg(MSG_FATAL, "Analyze and/or output filenames must be specified\n"); 133 | exit(1); 134 | } 135 | 136 | // Initialize PRU 137 | ddr_mem_size = pru_setup(1); 138 | if (ddr_mem_size == -1) { 139 | exit(1); 140 | } 141 | 142 | // And start our code 143 | if (pru_exec_program(0, "prucode0.bin") != 0) { 144 | msg(MSG_FATAL, "Unable to execute prucode0.bin\n"); 145 | exit(1); 146 | } 147 | 148 | pru_write_word(MEM_PRU0_DATA, PRU0_START_TIME_CLOCKS, 149 | drive_params.start_time_ns / CLOCKS_TO_NS); 150 | 151 | pru_write_word(MEM_PRU0_DATA, PRU0_BOARD_REVISION, board_get_revision()); 152 | 153 | deltas = deltas_setup(ddr_mem_size); 154 | max_deltas = ddr_mem_size / sizeof(deltas[0]); 155 | 156 | // Cleanup when we exit 157 | signal(SIGINT,(__sighandler_t) shutdown_signal); 158 | atexit(shutdown); 159 | 160 | // This is needed to keep up with the data. Without it we will take 161 | // more than 2 revolutions per track due to the default governor not 162 | // increasing the CPU speed enough. We switch frequently between busy and 163 | // sleeping. 164 | if (board_set_restore_max_cpu_speed(0)) { 165 | msg(MSG_ERR, "Unable to set CPU to maximum speed\n"); 166 | } 167 | 168 | if (!(drive_get_drive_status() & BIT_MASK(R31_DRIVE_SEL))) { 169 | msg(MSG_ERR,"** Drive selected without select, is J3 20 pin cable reversed? **\n"); 170 | } 171 | 172 | drive_params.noretry_head = drive_params.num_head; 173 | drive_params.noretry_cyl = drive_params.num_cyl; 174 | if (drive_params.analyze) { 175 | int head_cmdline = drive_params.num_head; 176 | int cyl_cmdline = drive_params.num_cyl; 177 | analyze_disk(&drive_params, deltas, max_deltas, 0); 178 | msg(MSG_INFO,"\n"); 179 | // Print analysis results 180 | parse_print_cmdline(&drive_params, 1, 0); 181 | drive_params.noretry_head = drive_params.num_head; 182 | if (head_cmdline > drive_params.num_head) { 183 | drive_params.num_head = head_cmdline; 184 | } 185 | drive_params.noretry_cyl = drive_params.num_cyl; 186 | if (cyl_cmdline > drive_params.num_cyl) { 187 | drive_params.num_cyl = cyl_cmdline; 188 | } 189 | } 190 | cmdline = parse_print_cmdline(&drive_params, 0, 0); 191 | drive_params.cmdline = msg_malloc(strlen(cmdline)+1,"main cmdline"); 192 | strcpy(drive_params.cmdline, cmdline); 193 | if (drive_params.extract_filename != NULL && !drive_params.xebec_skew && 194 | (mfm_controller_info[drive_params.controller].flag & FLAG_XEBEC)) { 195 | printf("Check extracted data file. Some Xebec controllers need --xebec_skew option\n to generate valid extracted data file\n"); 196 | } 197 | 198 | 199 | if (read) { 200 | drive_setup(&drive_params); 201 | drive_read_disk(&drive_params, deltas, max_deltas); 202 | } 203 | 204 | pru_exec_cmd(CMD_EXIT, 0); 205 | drive_select(0); 206 | 207 | return (0); 208 | } 209 | 210 | -------------------------------------------------------------------------------- /mfm/crc_ecc.c: -------------------------------------------------------------------------------- 1 | // This module is various routines for doing Cyclic Redundancy Check (CRC) 2 | // and Error Correction Code (ECC) calculations 3 | // crc_revbits is used to reverse bit order in a word 4 | // crc64 calculates CRC up to 64 bits long crc of data 5 | // ecc64 corrects single burst errors 6 | // checksum64 calculates checksums up to 64 bits long 7 | // eparity64 currently only calculates single bit even parity of bytes 8 | // 9 | // 12/19/21 DJG Removed length check for partity64 and actually named it epartity64 10 | // 12/31/15 DJG Added eparity64 function 11 | // 01/04/15 DJG Added checksum64 function 12 | // 13 | // Copyright 2021 David Gesswein. 14 | // This file is part of MFM disk utilities. 15 | // 16 | // MFM disk utilities is free software: you can redistribute it and/or modify 17 | // it under the terms of the GNU General Public License as published by 18 | // the Free Software Foundation, either version 3 of the License, or 19 | // (at your option) any later version. 20 | // 21 | // MFM disk utilities is distributed in the hope that it will be useful, 22 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | // GNU General Public License for more details. 25 | // 26 | // You should have received a copy of the GNU General Public License 27 | // along with MFM disk utilities. If not, see . 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include "crc_ecc.h" 34 | #include "msg.h" 35 | 36 | // Find last set (leftmost 1). Not defined in Linux so we use a GCC specific call 37 | // count leftmost zeros. 38 | #define fls(x) (32 - __builtin_clz(x)) 39 | 40 | // Reverse the bit order of v. 00100001b will become 10000100b 41 | // Not that efficient but we don't use it that often. 42 | // 43 | // v: value to reverse 44 | // length: length of v in bits 45 | // return: reverse of v 46 | uint64_t crc_revbits(uint64_t v, int length) 47 | { 48 | uint64_t i; 49 | uint64_t ov; 50 | uint64_t stop; 51 | 52 | if (length == 64) 53 | stop = 0; 54 | else 55 | stop = (uint64_t) 1 << length; 56 | 57 | ov = 0; 58 | for (i = 1; i != stop; i <<= 1) { 59 | ov <<= 1; 60 | if (v & i) { 61 | ov |= 1; 62 | } 63 | } 64 | return ov; 65 | } 66 | 67 | // Calculate a CRC up to 64 bits long over the specified data. 68 | // CRC_INFO specifies the CRC length, polynomial, and initial value. 69 | // The CRC is calculated most significant bit first. 70 | // The CRC result is returned. Zero is normally defined as no error 71 | // though other values can be used. 72 | // There are two forms the CRC is normally implemented in. The polynomial can 73 | // be converted between the two forms by 74 | // poly = revbits(poly, length) << 1) | 1 75 | // 76 | // bytes: bytes to calculate CRC over 77 | // num_bytes: length of bytes 78 | // crc_info: CRC parameters to use 79 | // return: CRC of bytes 80 | uint64_t crc64(uint8_t bytes[], int num_bytes, CRC_INFO *crc_info) 81 | { 82 | int64_t crc; 83 | // This improves performance vs. accessing directly in loop for the 84 | // GCC versions tested. The CRC polynomial 85 | uint64_t poly = crc_info->poly; 86 | // Loop counters 87 | int index, bit; 88 | 89 | crc = crc_info->init_value; 90 | for (index = 0; index < num_bytes; index++) { 91 | crc = crc ^ ((uint64_t) bytes[index] << (crc_info->length-8)); 92 | for (bit = 1; bit <= 8; bit++) { 93 | // if leftmost (most significant) bit is set xor in the polynomial 94 | if (crc & ((uint64_t) 1 << (crc_info->length-1))) { 95 | crc = (crc << 1) ^ poly; 96 | } else { 97 | crc = crc << 1; 98 | } 99 | } 100 | } 101 | // Trim the result to the requested length 102 | if (crc_info->length == 64) 103 | return crc; 104 | else 105 | return crc & (((uint64_t) 1 << crc_info->length)-1); 106 | } 107 | 108 | // Correct the specified data given the syndrome (incorrect CRC value) and 109 | // the CRC parameters. Return is non zero if a correction has been applied. 110 | // It is possible the correction is wrong if sufficient bits are in error. 111 | // Miscorrection probability depends on span length vs. polynomial length and 112 | // quality of the polynomial chosen. 113 | // 114 | // bytes: bytes to calculate CRC over 115 | // num_bytes: length of bytes 116 | // syndrome: Value return by crc64 117 | // crc_info: CRC/ECC parameters to use 118 | // return: Length of correction in bits. Zero if no correction possible 119 | // also bytes modified if return value is non zero. 120 | int ecc64(uint8_t bytes[], int num_bytes, uint64_t syndrome, CRC_INFO *crc_info) 121 | { 122 | // Number of bits corrected 123 | int span; 124 | // Loop counters 125 | int index, bit; 126 | int bits_left; 127 | uint64_t poly; 128 | uint64_t crc_mask = (((uint64_t) 1 << 129 | (crc_info->length - crc_info->ecc_max_span)) - 1) << 130 | (crc_info->ecc_max_span); 131 | 132 | span = 0; 133 | syndrome = crc_revbits(syndrome, crc_info->length); 134 | poly = ((crc_revbits(crc_info->poly, crc_info->length) << 1) | 1); 135 | // Stop when span is non zero (correction found) 136 | for (index = num_bytes; index > 0 && span == 0; index--) { 137 | // We continue after correction found to align the correction data to bytes 138 | for (bit = 1; bit <= 8; bit++) { 139 | // if leftmost (most significant) bit is set 140 | if (syndrome & ((uint64_t) 1 << (crc_info->length-1))) { 141 | syndrome = (syndrome << 1) ^ poly; 142 | } else { 143 | syndrome = syndrome << 1; 144 | } 145 | // If the upper ecc_max_span bits are zero we have found our correction 146 | if ((syndrome & crc_mask) == 0 && span == 0) { 147 | // Store the length of the correction 148 | span = fls(syndrome) - ffs(syndrome) + 1; 149 | } 150 | } 151 | } 152 | // If we found a correction fix the data 153 | if (span != 0) { 154 | // Round up span to handle worst case split across bytes 155 | bits_left = crc_info->ecc_max_span + 7; 156 | // Apply the correction to all possibly affected bytes 157 | while (bits_left > 0 && index < num_bytes) { 158 | bytes[index] ^= crc_revbits((syndrome & 0xff),8); 159 | index++; 160 | syndrome >>= 8; 161 | bits_left -= 8; 162 | } 163 | } 164 | return span; 165 | } 166 | 167 | // This is a checksum for formats that don't use a CRC 168 | // bytes: bytes to calculate checksum over 169 | // num_bytes: length of bytes 170 | // crc_info: CRC parameters to use. We only use the initial value and length 171 | // return: Checksum of bytes 172 | uint64_t checksum64(uint8_t *bytes, int num_bytes, CRC_INFO *crc_info) 173 | { 174 | int i; 175 | uint64_t sum; 176 | 177 | sum = crc_info->init_value; 178 | 179 | for (i = 0; i < num_bytes; i++) { 180 | sum += bytes[i]; 181 | } 182 | // Trim the result to the requested num_bytes 183 | if (crc_info->length == 64) 184 | return sum; 185 | else 186 | return sum & (((uint64_t) 1 << crc_info->length)-1); 187 | } 188 | 189 | 190 | // This calculates a single bit even parity of the bytes 191 | // bytes: bytes to calculate checksum over 192 | // num_bytes: length of bytes 193 | // crc_info: CRC parameters to use. We only use the initial value and length 194 | // Init_value 1 gives odd parity, 0 gives even parity 195 | // return: parity bit 196 | uint64_t eparity64(uint8_t *bytes, int num_bytes, CRC_INFO *crc_info) 197 | { 198 | // Calculate the even parity of a byte 199 | static unsigned char parity_lookup[16] = { 200 | 0x0, 0x1, 0x1, 0x0, 0x1, 0x0, 0x0, 0x1, 201 | 0x1, 0x0, 0x0, 0x1, 0x0, 0x1, 0x1, 0x0 }; 202 | #define EPARITY(n) (parity_lookup[n&0xf] ^ parity_lookup[n>>4]) 203 | int eparity = 0; 204 | int i; 205 | 206 | for (i = 0; i < num_bytes; i++) { 207 | eparity ^= EPARITY(bytes[i]); 208 | } 209 | return eparity ^ crc_info->init_value; 210 | } 211 | -------------------------------------------------------------------------------- /mfm/drive_write.c: -------------------------------------------------------------------------------- 1 | // This module is routines for writing a disk drive. 2 | // drive_write_disk writes the disk 3 | // 4 | // The drive must be at track 0 on startup or drive_seek_track0 called. 5 | // 6 | // Copyright 2021 David Gesswein. 7 | // This file is part of MFM disk utilities. 8 | // 9 | // MFM disk utilities is free software: you can redistribute it and/or modify 10 | // it under the terms of the GNU General Public License as published by 11 | // the Free Software Foundation, either version 3 of the License, or 12 | // (at your option) any later version. 13 | // 14 | // MFM disk utilities is distributed in the hope that it will be useful, 15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | // GNU General Public License for more details. 18 | // 19 | // You should have received a copy of the GNU General Public License 20 | // along with MFM disk utilities. If not, see . 21 | // 22 | // 09/07/21 DJG Handle error from write track 23 | // 03/07/21 DJG Only pad end if non zero 24 | // 06/30/17 DJG Use emulator file number of heads, not command line. 25 | // 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | 40 | #include "msg.h" 41 | #include "crc_ecc.h" 42 | #include "emu_tran_file.h" 43 | #include "mfm_decoder.h" 44 | #include "cmd.h" 45 | #include "cmd_write.h" 46 | #include "deltas_read.h" 47 | #include "pru_setup.h" 48 | #include "drive.h" 49 | #include "board.h" 50 | 51 | 52 | static void generate_pwm_table(DRIVE_PARAMS *drive_params, int do_precomp); 53 | 54 | // Write the disk. 55 | // 56 | // drive_params: Drive parameters 57 | // deltas: Memory array containing the raw MFM delta time transition data 58 | void drive_write_disk(DRIVE_PARAMS *drive_params) 59 | { 60 | // Loop variable 61 | int cyl, head; 62 | int track_size, cyl_size; 63 | // Buffer for the drive data 64 | uint8_t *data; 65 | 66 | track_size = drive_params->emu_file_info->track_data_size_bytes + 67 | drive_params->emu_file_info->track_header_size_bytes; 68 | cyl_size = track_size * drive_params->emu_file_info->num_head; 69 | data = msg_malloc(cyl_size,"Write data buffer"); 70 | 71 | generate_pwm_table(drive_params, 0); 72 | for (cyl = 0; cyl < drive_params->num_cyl; cyl++) { 73 | if (cyl >= drive_params->write_precomp_cyl) { 74 | generate_pwm_table(drive_params, 1); 75 | } 76 | 77 | if (cyl != 0) { 78 | // Slow seek doesn't slow down writing so always use it. 79 | drive_step(DRIVE_STEP_SLOW, 1, DRIVE_STEP_UPDATE_CYL, 80 | DRIVE_STEP_FATAL_ERR); 81 | } 82 | if (cyl % 5 == 0) 83 | msg(MSG_PROGRESS, "At cyl %d\r", cyl); 84 | emu_file_read_cyl(drive_params->emu_fd, 85 | drive_params->emu_file_info, cyl, data, cyl_size); 86 | //TODO need to handle this better either in emulator or here. 87 | // May not be needed now that files are no longer padded with zeros. 88 | { int n; 89 | for (n = 0; n < drive_params->emu_file_info->num_head; n++) { 90 | int *d = (int *) data; 91 | int index = track_size/4 - 1 + n * track_size/4; 92 | // No transitions at end of write will cause emulator to fail 93 | if (d[index] == 0) { 94 | d[index] = 0x55555555; 95 | } 96 | } 97 | } 98 | pru_write_mem(MEM_DDR, data, cyl_size, 0); 99 | for (head = 0; head < drive_params->num_head; head++) { 100 | drive_set_head(head); 101 | pru_write_word(MEM_PRU1_DATA, PRU1_CUR_HEAD, head); 102 | if (pru_exec_cmd(CMD_WRITE_TRACK, 0)) { 103 | drive_print_drive_status(MSG_FATAL, drive_get_drive_status()); 104 | exit(1); 105 | } 106 | } 107 | } 108 | } 109 | 110 | 111 | // Make a table which we index with the six MSB of the MFM bitstream 112 | // to determine the next PWM word. 113 | 114 | // The MFM bitstream is the MFM data and clock bits referred to as 115 | // mfm encoded here: 116 | // http://en.wikipedia.org/wiki/Modified_Frequency_Modulation 117 | 118 | // All the words generated must have a minimum duration of 2 bit cells, 119 | // nominally 40 clocks, 200 ns. 120 | // For example bit pattern 10010[1] is sent as a pulse with period 60 121 | // counts and a pulse with period 40 for 10 MHz data rate. 122 | 123 | // If we find a 11 we drop the second bit since we can't generate it. 124 | // It's bad MFM data so shouldn't cause problems. 01 we flag as an 125 | // error since we should never see it unless something has gone 126 | // wrong. Without changing the PWM polarity we can't generate a zero 127 | // followed by a 1. We pick the bits we process to ensure that valid 128 | // MFM data won't generate the patterns we can't encode. For example 129 | // bit pattern 1000 we only process the first two (10) and leave the 130 | // last two bits (00) for the next lookup. For valid MFM code we 131 | // could generate the waveform for all four bits instead since the 132 | // next bit should be a 1. I don't in case it is a zero. 133 | // 134 | // Bits 31-28 are how many bits to remove from data. This is the MFM 135 | // clock and data bits we will shift off, not anything to do with the 136 | // data bits encoded by the MFM encoding. 137 | // The bits we remove were choosen to make sure that the left over 138 | // bits aren't a pattern we can't encode. 139 | // Bits 27-25 unused 140 | // bit 24 is flag for illegal bit pattern found 141 | // Bits 23-16 are duration of 1. 0 is no one. (PWM ACMP) 142 | // Bits 15-8 are signed period adjustment to apply to next word for write 143 | // precompensation in this word. 144 | // Bits 7-0 is period to next bit time. (PWM APRD) 145 | // Period values are one less than actual period generated. 146 | 147 | // Drives construction of PRU1 bit lookup table 148 | typedef struct { 149 | // Number of bits we can represent in a single PWM word (2 or 3) 150 | uint32_t bitcount; 151 | // Value of first bit (0 or 1) 152 | uint32_t leading_bit; 153 | // Error flag (see below) 154 | uint32_t error_flag; 155 | } table_rec_t; 156 | 157 | static table_rec_t bit_table[] = { 158 | { 2, 0, 0 }, // 00 0000 159 | { 3, 0, 0 }, // 00 0001 160 | { 2, 0, 0 }, // 00 0010 161 | { 2, 0, 0 }, // 00 0011 162 | 163 | // If the bit shifting algorithm is working correctly, we should 164 | // never see a pattern starting with '01'. If we do, then 165 | // something is wrong and and error is flagged. 166 | 167 | { 2, 0, 1 }, // 00 0100 treated as 0000 168 | { 3, 0, 1 }, // 00 0101 treated as 0001 169 | { 2, 0, 1 }, // 00 0110 treated as 0010 170 | { 2, 0, 1 }, // 00 0111 treated as 0011 171 | 172 | { 2, 1, 0 }, // 00 1000 173 | { 3, 1, 0 }, // 00 1001 174 | { 2, 1, 0 }, // 00 1010 175 | { 2, 1, 0 }, // 00 1011 176 | 177 | // The next four patterns are not valid MFM data and cannot be 178 | // generated. We just drop the second 1. 179 | 180 | { 2, 1, 0 }, // 00 1100 treated as 1000 181 | { 3, 1, 0 }, // 00 1101 treated as 1001 182 | { 2, 1, 0 }, // 00 1110 treated as 1010 183 | { 2, 1, 0 } // 00 1111 treated as 1011 184 | }; 185 | 186 | static void generate_pwm_table(DRIVE_PARAMS *drive_params, int do_precomp) { 187 | 188 | int idx; 189 | int pat; 190 | EMU_FILE_INFO *curr_info = drive_params->emu_file_info; 191 | int bit_period = lround(200e6 / curr_info->sample_rate_hz); 192 | int early_precomp_clk = lround(drive_params->early_precomp_ns / 1e9 * 200e6); 193 | int late_precomp_clk = lround(drive_params->late_precomp_ns / 1e9 * 200e6); 194 | 195 | pru_write_word(MEM_PRU0_DATA, PRU0_DEFAULT_PULSE_WIDTH, bit_period*2-1); 196 | 197 | 198 | for (idx = 0; idx < 64; idx++) { 199 | table_rec_t rec = bit_table[(idx >> 2) & 0xf]; 200 | uint32_t bits = (rec.bitcount<<28) | (rec.error_flag<<24) | ((rec.leading_bit*bit_period)<<16) | ((rec.bitcount*bit_period)-1); 201 | if (rec.leading_bit) { 202 | // If generating 10 which will be followed be 100 then 203 | // gnerate the second one early 204 | if (do_precomp && (idx >> 1) == 0x14) { 205 | bits -= early_precomp_clk; 206 | bits |= (early_precomp_clk & 0xff) << 8; 207 | } 208 | } else { 209 | pat = (idx << rec.bitcount) >> 3; 210 | // If two more zeros followed by 101 then we delay the first one 211 | if (do_precomp && pat == 0x5) { 212 | bits += late_precomp_clk; 213 | bits |= (-late_precomp_clk & 0xff) << 8; 214 | } 215 | } 216 | 217 | pru_write_word(MEM_PRU1_DATA, PRU1_BIT_TABLE + idx * PRU_WORD_SIZE_BYTES, 218 | bits); 219 | } 220 | 221 | } 222 | -------------------------------------------------------------------------------- /emu/inc/cmd.h: -------------------------------------------------------------------------------- 1 | // These defines are used in both the PRU assembly language and in the C 2 | // code so they can only be simple defines. 3 | 4 | // The commands to the PRU 5 | #define CMD_NONE 0 6 | #define CMD_START 1 7 | 8 | // The command status. All commands other than CMD_READ_TRACK 9 | // return their status when done. CMD_READ_TRACK returns 10 | // CMD_STATUS_READ_STARTED when the read has started and CMD_STATUS_OK 11 | // or an error status when the read is finished. 12 | #define CMD_STATUS_OK 0x0ff 13 | #define CMD_STATUS_READ_STARTED 0x100 14 | 15 | 16 | // Registers used locally and XFER bank 10 to communicate with PRU0 17 | #define PRU1_BUF_OFFSET r8.w0 18 | #define PRU1_STATE r8.b3 19 | #define PRU1_BUF_STATE r8 20 | #define PRU0_BUF_OFFSET r9.w0 21 | #define PRU0_STATE r9.b3 22 | #define PRU0_BUF_STATE r9 23 | #define TRACK_BIT r18 24 | // 0 for first drive, 4 for second to access variables that depend on selected drive 25 | #define DRIVE_DATA r20 26 | 27 | #define STATE_IDLE 1 28 | #define STATE_READ 2 29 | #define STATE_READ_BIT_SET 3 30 | #define STATE_READ_FILLED 4 31 | #define STATE_READ_DONE 5 32 | #define STATE_WRITE_WAIT 6 33 | #define STATE_WRITE 7 34 | #define STATE_WRITE_DONE 8 35 | #define STATE_RESTART_READ 9 36 | #define STATE_EXIT 10 37 | 38 | // All these are the memory address to communicate with the PRU code 39 | 40 | // These are on both PRUs. 41 | // Physical address and size of shared DDR memory 42 | #define PRU_DDR_ADDR 0x00 43 | #define PRU_DDR_SIZE 0x04 44 | // Physical address of PRU dataram 45 | #define PRU_DATARAM_ADDR 0x08 46 | 47 | #define PRU_TEST0 0x0c 48 | #define PRU_TEST1 0x10 49 | #define PRU_TEST2 0x14 50 | #define PRU_TEST3 0x18 51 | #define PRU_TEST4 0x1c 52 | 53 | // Command location, command data, and status 54 | #define PRU0_CMD 0x30 55 | #define PRU0_CMD_DATA 0x34 56 | #define PRU0_STATUS 0x38 57 | // Count and register value if we got a GPIO interrupt without data changing 58 | #define PRU0_HEAD_SELECT_GLITCH_VALUE 0x3c 59 | #define PRU0_HEAD_SELECT_GLITCH_COUNT 0x40 60 | #define PRU0_R31 0x44 61 | // Decoded head and raw bits from GPIO register. See PRU0_DRIVE#_CUR_CYL 62 | // for current cylinder. Bad head is non zero if CUR_HEAD is not valid 63 | // for drive. Both are two bytes and BAD_HEAD must be high 64 | #define PRU0_CUR_HEAD 0x48 65 | #define PRU0_BAD_HEAD 0x4a 66 | // For A&B this had select and head lines. For C only had head lines. 67 | #define PRU0_CUR_SELECT_HEAD 0x4c 68 | // Time in 200 MHz clocks from ARM interrupt to data returned 69 | #define PRU0_SEEK_TIME 0x50 70 | // Number of bytes in each drive# value. 71 | #define DRIVE_DATA_BYTES 4 72 | // Parameters of simulated disk 73 | #define PRU0_DRIVE0_NUM_CYL 0x54 74 | #define PRU0_DRIVE1_NUM_CYL 0x58 75 | #define PRU0_DRIVE0_NUM_HEAD 0x5c 76 | #define PRU0_DRIVE1_NUM_HEAD 0x60 77 | // Internal variable 78 | #define PRU0_WAITING_CMD 0x64 79 | // Overrun on ECAP if non zero 80 | #define PRU0_ECAP_OVERRUN 0x68 81 | // PRU 0-1 queue underrun count 82 | #define PRU0_RQUEUE_UNDERRUN 0x6c 83 | #define PRU0_WQUEUE_OVERRUN 0x70 84 | // Drive select numbers we should respond to. 0 or bit number in GPIO 85 | // register that is our drive select. Keep adjacent. 86 | #define PRU0_DRIVE0_SELECT 0x74 87 | #define PRU0_DRIVE1_SELECT 0x78 88 | // The select and head lines we are looking at. If < 8 cyl then only low 3 89 | #define PRU0_HEAD_MASK 0x7c 90 | 91 | // Current cylinder 92 | #define PRU0_DRIVE0_CUR_CYL 0x80 93 | #define PRU0_DRIVE1_CUR_CYL 0x84 94 | 95 | // 0 normal, 1 to command PRU to exit 96 | #define PRU0_EXIT 0x88 97 | 98 | // Used to convert bit-count to cycles at current rate (4 bytes) 99 | #define PRU0_BIT_PRU_CLOCKS 0x8c 100 | 101 | // Cycle count for PWM logic 1 at current data rate (4 bytes) 102 | #define PRU0_DEFAULT_PULSE_WIDTH 0x90 103 | 104 | // Start and end time for generating index pulse in PRU clocks 105 | #define PRU0_START_INDEX_TIME 0x94 106 | #define PRU0_END_INDEX_TIME 0x98 107 | 108 | // Time for a disk rotation in PRU clocks 109 | #define PRU0_ROTATION_TIME 0x9c 110 | 111 | // 0xa0 unused 112 | 113 | // Last cylinder requested from ARM 114 | #define PRU0_DRIVE0_LAST_ARM_CYL 0xa4 115 | #define PRU0_DRIVE1_LAST_ARM_CYL 0xa8 116 | #define PRU0_LAST_SELECT_HEAD 0xac 117 | 118 | // Bits 0-n indicate track 0-n been modified in cylinder data 119 | #define PRU1_DRIVE0_TRK_DIRTY 0x30 120 | // Bits 0-n indicate track 0-n been modified in cylinder data 121 | #define PRU1_DRIVE1_TRK_DIRTY 0x34 122 | // Non zero indicates error in converting bits to PWM words 123 | #define PRU1_BAD_PATTERN_COUNT 0x38 124 | #define PRU1_DRIVE0_TRACK_HEADER_BYTES 0x50 125 | #define PRU1_DRIVE1_TRACK_HEADER_BYTES 0x54 126 | #define PRU1_DRIVE0_TRACK_DATA_BYTES 0x58 127 | #define PRU1_DRIVE1_TRACK_DATA_BYTES 0x5c 128 | 129 | // inverse bit period scaled 2^32, 2^32/bit_period 130 | #define PRU1_INV_BIT_PERIOD_S32 0x60 131 | 132 | // If we don't see a transition by this number of cycles, 133 | // assume zero. 134 | #define PRU1_ZERO_BIT_THRESHOLD 0x64 135 | // Nominal bit cell cycles 136 | #define PRU1_BIT_PRU_CLOCKS 0x68 137 | // DMA channel to use 138 | #define PRU1_DMA_CHANNEL 0x6c 139 | // Last DMA RAM and DDR offset, internal PRU use 140 | #define PRU1_NEXT_DMA_RAM_OFFSET 0x70 141 | #define PRU1_NEXT_DMA_DDR_OFFSET 0x74 142 | 143 | #define PRU_WORD_SIZE_BYTES 4 144 | 145 | // Location bit lookup table stored. To 0xbf, 16 4 byte entries 146 | #define PRU1_BIT_TABLE 0x80 147 | 148 | // Maximum size of cylinder buffer. 149 | #define DDR_DRIVE_BUFFER_MAX_SIZE (16*32768) 150 | // PRU queues are in shared memory locations 0 to mask 151 | #define SHARED_PWM_READ_MASK 0x1f 152 | #define SHARED_DELTAS_WRITE_MASK 0x7f 153 | 154 | // Bits in registers for the control lines 155 | #define R30_DRIVE0_SEL 1 156 | #define R30_SEEK_COMPLETE_BIT 2 157 | // Rev A,B 158 | #define R30_WRITE_FAULT_BIT 3 159 | // Rev C 160 | #define GPIO1_WRITE_FAULT_BIT 19 161 | #define R30_READY_BIT 4 162 | #define R30_INDEX_BIT 5 163 | #define R30_MFM0_IN_ENABLE 15 164 | #define R30_MFM1_IN_ENABLE 14 165 | 166 | #define R31_WRITE_GATE 0 167 | #define R31_SEEK_DIR_BIT 6 168 | #define R31_STEP_BIT 7 169 | // Rev C 170 | #define R31_SEL1_BIT 14 171 | #define R31_SEL2_BIT 16 172 | 173 | #define REVB_DETECT_PIN 46 // GPIO 1_14 174 | #define REVC_DETECT_PIN 61 // GPIO 1_29 175 | 176 | // Rev A,B 177 | #define GPIO0_TRACK_0 30 178 | // Rev C 179 | #define R30_TRACK_0_BIT 3 180 | #define GPIO0_DRIVE0_LED 14 181 | // Rev A,B 182 | #ifndef REVC 183 | #define GPIO0_DRIVE1_LED 15 184 | #else 185 | // Rev C 186 | #define GPIO0_DRIVE1_LED 30 187 | #endif 188 | #define GPIO0_DRIVE1_SEL_RECOVERY 31 189 | 190 | #define GPIO1_TEST 16 191 | 192 | #define GPIO_SELECT1 22 193 | #define GPIO_SELECT2 23 194 | #ifdef REVC 195 | #define GPIO_HEAD0 8 196 | #define GPIO_HEAD1 9 197 | #define GPIO_HEAD2 10 198 | #define GPIO_HEAD3 11 199 | #define GPIO_DRIVE_SELECT_LINES 200 | #else 201 | #ifdef REVB 202 | #define GPIO_HEAD0 8 203 | #define GPIO_HEAD1 9 204 | #define GPIO_HEAD2 10 205 | #define GPIO_HEAD3 11 206 | #define GPIO_DRIVE_SELECT_LINES (1 << GPIO_SELECT1) | (1 << GPIO_SELECT2) 207 | #else 208 | #define GPIO_HEAD0 2 209 | #define GPIO_HEAD1 3 210 | #define GPIO_HEAD2 4 211 | #define GPIO_HEAD3 5 212 | #define GPIO_SELECT3 26 213 | #define GPIO_SELECT4 27 214 | #define GPIO_DRIVE_SELECT_LINES (1 << GPIO_SELECT1) | (1 << GPIO_SELECT2) | (1 << GPIO_SELECT3) | (1 << GPIO_SELECT4) 215 | #endif 216 | #endif 217 | #define CUR_SELECT_HEAD_WRITE_ERR 31 218 | 219 | #define GPIO_DRIVE_HEAD_LINES (1 << GPIO_HEAD0) | (1 << GPIO_HEAD1) | (1 << GPIO_HEAD2) | (1 << GPIO_HEAD3) 220 | #define GPIO_DRIVE_SELECT_HEAD_LINES (GPIO_DRIVE_HEAD_LINES | GPIO_DRIVE_SELECT_LINES) 221 | 222 | // Address in PRU code for stopping current operation 223 | #define RESTART_ADDR 0x400 224 | // Offset from start of PRU0_DATARAM to the PRU control register 225 | #define PRU_CONTROL_REG 0x22000 226 | #define PRU_STATUS_REG 0x22004 227 | #define PRU_DEBUG 0x22400 228 | 229 | // TODO, these should be common 230 | #define CLOCK_BASE 0x44e00000 231 | #define CM_AUTOIDEL_DPLL_DISP 0x448 232 | #define CM_IDLEST_DPLL_DISP 0x448 233 | #define CM_SSC_DELTAMSTEP_DPLL_DISP 0x44c 234 | #define CM_SSC_MODFREQDIV_DPLL_DISP 0x450 235 | #define CM_CLKSEL_DPLL_DISP 0x454 236 | #define CM_CLKMODE_DPLL_DISP 0x498 237 | #define CM_DIV_M2_DPLL_DISP 0x4a4 238 | #define CLKSEL_PRU_ICSS_OCP_CLK 0x530 239 | -------------------------------------------------------------------------------- /mfm/drive_operations.p: -------------------------------------------------------------------------------- 1 | // Common code for talking to drives. Included by other .p files 2 | // 3 | // 03/22/19 DJG Added REV C support 4 | // 08/05/18 DJG Further increased seek complete timeout for Quantum Q2040 drive recal 5 | // 07/19/18 DJG Increased seek complete timeout for Quantum Q2040 drive recal 6 | // 04/21/18 DJG Handle drives that go not ready during seek to track 0 7 | // 8 | // Copyright 2019 David Gesswein. 9 | // This file is part of MFM disk utilities. 10 | // 11 | // MFM disk utilities is free software: you can redistribute it and/or modify 12 | // it under the terms of the GNU General Public License as published by 13 | // the Free Software Foundation, either version 3 of the License, or 14 | // (at your option) any later version. 15 | // 16 | // MFM disk utilities is distributed in the hope that it will be useful, 17 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | // GNU General Public License for more details. 20 | // 21 | // You should have received a copy of the GNU General Public License 22 | // along with MFM disk utilities. If not, see . 23 | 24 | 25 | // 8 seconds, guess at slowest time to complete a seek including possible 26 | // recalibrate. 27 | #define SEEK_COMPLETE_TIMEOUT 1600000000 28 | // Pulse width for buffered seek pulses 29 | #define SEEK_PW 1000 // 5 microseconds 30 | #define SEEK_PW_SLOW 8000 // 40 microseconds 31 | // Delay between buffered seek pulses 32 | #define SEEK_WAIT 6000 // 30 microseconds 33 | // Delay between unbuffered seek pulses 34 | #define SEEK_WAIT_SLOW 800000 // 4 milliseconds 35 | // Time to wait for the head step direction line to settle 36 | #define DIR_SETTLE_TIME 40 // 200 nanoseconds 37 | 38 | 39 | // To find track zero we see if we are at track zero. If not we 40 | // step in, wait for seek complete, then repeat 41 | slow_seek_track0: 42 | // Seek in, turn off active low seek in signal driven by inverter 43 | CLR r30, R30_SEEK_DIR_BIT 44 | MOV r3, DIR_SETTLE_TIME 45 | MOV r2, 0 46 | SBBO r2, CYCLE_CNTR, 0, 4 // Clear timer 47 | settle0_lp: 48 | LBBO r0, CYCLE_CNTR, 0, 4 49 | QBLT settle0_lp, r3, r0 50 | 51 | seek0_lp: 52 | // Signal on different pin for revision C (2) board 53 | LBCO r0, CONST_PRURAM, PRU0_BOARD_REVISION, 4 54 | QBEQ track_0_revc, r0, 2 55 | MOV r0, GPIO0 | GPIO_DATIN // Read bits 56 | LBBO r0, r0, 0, 4 57 | QBBC seek0_done, r0, GPIO0_TRACK_0_BIT // Already at track0, stop 58 | JMP seek0_next 59 | track_0_revc: 60 | MOV r0, GPIO3 | GPIO_DATIN // Read bits 61 | LBBO r0, r0, 0, 4 62 | QBBC seek0_done, r0, GPIO3_TRACK_0_BIT_REVC // Already at track0, stop 63 | 64 | seek0_next: 65 | // Pulse the step bit for the correct duration 66 | MOV r3, SEEK_PW_SLOW 67 | SET r30, R30_STEP_BIT // Start pulse 68 | MOV r2, 0x0 // Keep zero around 69 | SBBO r2, CYCLE_CNTR, 0, 4 // Clear timer 70 | seek0_w: 71 | LBBO r0, CYCLE_CNTR, 0, 4 72 | QBLT seek0_w, r3, r0 73 | CLR r30, R30_STEP_BIT 74 | 75 | // Wait for seek complete 76 | MOV r3, SEEK_COMPLETE_TIMEOUT 77 | CALL wait_ready 78 | // Didn't go ready, return to cmd loop. Error set in wait_ready 79 | QBNE wait_cmd, r3, 0 80 | JMP seek0_lp 81 | 82 | seek0_done: 83 | MOV r0, CMD_STATUS_OK 84 | SBCO r0, CONST_PRURAM, PRU0_CMD, 4 85 | JMP wait_cmd 86 | 87 | // Set up time between pulses for type of seek 88 | slow_seek: 89 | MOV r6, SEEK_WAIT_SLOW 90 | MOV r8, SEEK_PW_SLOW 91 | JMP do_seek 92 | seek: 93 | MOV r6, SEEK_WAIT 94 | MOV r8, SEEK_PW 95 | do_seek: 96 | LBCO r1, CONST_PRURAM, PRU0_CMD_DATA, 4 // Get tracks to seek 97 | MOV r2, 0x0 // Keep zero around 98 | QBBS neg, r1, 31 // Is seek value negative? 99 | SET r30, R30_SEEK_DIR_BIT 100 | JMP dir_settle 101 | neg: 102 | CLR r30, R30_SEEK_DIR_BIT // Seek in, set active low in signal 103 | RSB r1, r1, 0 // Make count positive 104 | dir_settle: 105 | MOV r3, DIR_SETTLE_TIME 106 | SBBO r2, CYCLE_CNTR, 0, 4 // Clear timer 107 | settle_lp: 108 | LBBO r0, CYCLE_CNTR, 0, 4 109 | QBLT settle_lp, r3, r0 110 | 111 | // And generate the pulses at the specified rate 112 | seek_lp: 113 | SET r30, R30_STEP_BIT // Start acive low pulse 114 | SBBO r2, CYCLE_CNTR, 0, 4 // Clear timer 115 | seek_w1: 116 | LBBO r0, CYCLE_CNTR, 0, 4 // Wait for pulse width 117 | QBLT seek_w1, r8, r0 118 | CLR r30, R30_STEP_BIT 119 | 120 | MOV r3, r6 // Get Idle time between pulses 121 | SBBO r2, CYCLE_CNTR, 0, 4 122 | seek_w2: 123 | LBBO r0, CYCLE_CNTR, 0, 4 124 | QBLT seek_w2, r3, r0 125 | 126 | SUB r1, r1, 1 127 | QBLE seek_lp, r1, 1 // And repeat if needed 128 | 129 | check_ready: 130 | MOV r3, SEEK_COMPLETE_TIMEOUT 131 | CALL wait_ready 132 | // Didn't go ready, return to cmd loop. Error set in wait_ready 133 | QBNE wait_cmd, r3, 0 134 | MOV r1, CMD_STATUS_OK 135 | SBCO r1, CONST_PRURAM, PRU0_CMD, 4 // Indicate command completed ok 136 | JMP wait_cmd 137 | 138 | // We wait for the seek complete timeout for drive to be ready 139 | check_ready_cmd: 140 | MOV r3, SEEK_COMPLETE_TIMEOUT 141 | CALL wait_ready 142 | // Not ready. Wait_ready already set error status 143 | QBNE wait_cmd, r3,0 144 | MOV r0, CMD_STATUS_OK 145 | SBCO r0, CONST_PRURAM, PRU0_CMD, 4 146 | JMP wait_cmd 147 | 148 | // Check for drive ready. r3 = timeout value on entry, 0 on return if ready 149 | wait_ready: 150 | MOV r2,0 151 | SBBO r2, CYCLE_CNTR, 0, 4 // Clear timer 152 | ready_loop: 153 | MOV CALL_HOLD, RET_REG 154 | CALL get_status 155 | MOV RET_REG, CALL_HOLD 156 | // Ready when seek complete, ready, and drive selected are low 157 | QBBS ready_timeout, r31, R31_SEEK_COMPLETE_BIT 158 | QBBS ready_timeout, r31, R31_READY_BIT 159 | QBBS ready_timeout, r31, R31_DRIVE_SEL 160 | // Kill time, NOP 161 | MOV r0, r0 162 | MOV r0, r0 163 | MOV r0, r0 164 | MOV r0, r0 165 | MOV r0, r0 166 | MOV r0, r0 167 | MOV r0, r0 168 | // Verify still good. Signals don't all transition at the same time 169 | QBBS ready_timeout, r31, R31_SEEK_COMPLETE_BIT 170 | QBBS ready_timeout, r31, R31_READY_BIT 171 | QBBS ready_timeout, r31, R31_DRIVE_SEL 172 | MOV r3, 0 // We are ready, return good 173 | RET 174 | ready_timeout: 175 | LBBO r0, CYCLE_CNTR, 0, 4 176 | QBLT ready_loop, r3, r0 177 | MOV r0, CMD_STATUS_READY_ERR 178 | SBCO r0, CONST_PRURAM, PRU0_CMD, 4 179 | MOV CALL_HOLD, RET_REG 180 | CALL get_status 181 | MOV RET_REG, CALL_HOLD 182 | RET 183 | 184 | // r0 modified 185 | // r1 is updated status register. 186 | get_status: 187 | LBCO r0, CONST_PRURAM, PRU0_BOARD_REVISION, 4 188 | MOV r1, r31 189 | QBNE store_status, r0, 2 190 | MOV r0, GPIO1 | GPIO_DATIN // Read bits 191 | LBBO r0, r0, 0, 4 192 | // If revision C board put write fault in same bit a A & B. R31 bit should 193 | // be 0 when read for revision C. 194 | QBBC store_status, r0, GPIO1_WRITE_FAULT_BIT 195 | SET r1, R31_WRITE_FAULT_BIT 196 | store_status: 197 | SBCO r1, CONST_PRURAM, PRU0_STATUS, 4 198 | RET 199 | 200 | // Measure drive RPM, time between index pulses 201 | rpm: 202 | MOV r3, INDEX_TIMEOUT 203 | MOV r2, 0 204 | SBBO r2, CYCLE_CNTR, 0, 4 // Clear timer 205 | rpm_wait: 206 | QBBS rpm_wait2, r31, R31_INDEX_BIT // Wait for signal high 207 | LBBO r0, CYCLE_CNTR, 0, 4 208 | QBLT rpm_wait, r3, r0 // Try again if we haven't timed out 209 | JMP rpm_timeout 210 | rpm_wait2: 211 | QBBC rpm_clr, r31, R31_INDEX_BIT // Wait for falling edge 212 | LBBO r0, CYCLE_CNTR, 0, 4 213 | QBLT rpm_wait2, r3, r0 // Try again if we haven't timed out 214 | JMP rpm_timeout 215 | rpm_clr: 216 | SBBO r2, CYCLE_CNTR, 0, 4 // Clear timer 217 | rpm_wait3: 218 | QBBS rpm_wait4, r31, R31_INDEX_BIT // Wait for signal high 219 | LBBO r0, CYCLE_CNTR, 0, 4 220 | QBLT rpm_wait3, r3, r0 // Try again if we haven't timed out 221 | JMP rpm_timeout 222 | rpm_wait4: 223 | QBBC rpm_done, r31, R31_INDEX_BIT // Wait for falling edge 224 | LBBO r0, CYCLE_CNTR, 0, 4 225 | QBLT rpm_wait4, r3, r0 // Try again if we haven't timed out 226 | JMP rpm_timeout 227 | rpm_done: 228 | LBBO r2, CYCLE_CNTR, 0, 4 229 | SBCO r2, CONST_PRURAM, PRU0_CMD_DATA, 4 // Store rotation time 230 | MOV r0, CMD_STATUS_OK 231 | SBCO r0, CONST_PRURAM, PRU0_CMD, 4 // Indicate command completed ok 232 | JMP wait_cmd 233 | rpm_timeout: 234 | MOV r0, CMD_STATUS_INDEX_TIMEOUT 235 | SBCO r0, CONST_PRURAM, PRU0_CMD, 4 // Error 236 | JMP wait_cmd 237 | -------------------------------------------------------------------------------- /mfm/parse_cmdline_write.c: -------------------------------------------------------------------------------- 1 | // Parse the command line. 2 | // 3 | // Call parse_cmdline to parse the command line 4 | // Call parse_print_cmdline to print drive parameter information in command 5 | // line format 6 | // Call parse_validate_options to perform some validation on mfm_write options 7 | // 8 | // Copyright 2024 David Gesswein. 9 | // This file is part of MFM disk utilities. 10 | // 11 | // 01/13/25 DJG Fixes for xebec_skew processing. Skew not same on all tracks. 12 | // 04/29/24 DJG Made separate file for mfm_write since options different than 13 | // mfm_util/mfm_read 14 | // 15 | // MFM disk utilities is free software: you can redistribute it and/or modify 16 | // it under the terms of the GNU General Public License as published by 17 | // the Free Software Foundation, either version 3 of the License, or 18 | // (at your option) any later version. 19 | // 20 | // MFM disk utilities is distributed in the hope that it will be useful, 21 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | // GNU General Public License for more details. 24 | // 25 | // You should have received a copy of the GNU General Public License 26 | // along with MFM disk utilities. If not, see . 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | 37 | #include "msg.h" 38 | #include "crc_ecc.h" 39 | #include "emu_tran_file.h" 40 | #include "mfm_decoder.h" 41 | #include "drive.h" 42 | 43 | #include "parse_cmdline.h" 44 | #include "version.h" 45 | 46 | #define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0])) 47 | 48 | // Print to buffer and exit if no space left. 49 | // ptr: buffer to write to 50 | // left: how many characters left 51 | // format: format string 52 | // ...: arguments to print 53 | void safe_print(char **ptr, int *left, char *format, ...) { 54 | va_list va; 55 | int rc; 56 | 57 | va_start(va, format); 58 | rc = vsnprintf(*ptr, *left, format, va); 59 | *left -= rc; 60 | if (left <= 0) { 61 | msg(MSG_FATAL, "Command line exceeded buffer\n"); 62 | exit(1); 63 | } 64 | *ptr += rc; 65 | va_end(va); 66 | } 67 | 68 | // Delete bit n from v shifting higher bits down 69 | #define DELETE_BIT(v, n) (v & ((1 << n)-1)) | (((v & ~((1 << (n+1))-1)) >> 1)) 70 | 71 | // If you change this fix parse_print_cmdline and check if ext2emu should 72 | // delete new option 73 | static struct option long_options[] = { 74 | {"drive", 1, NULL, 'd'}, 75 | {"unbuffered_seek", 0, NULL, 'u'}, 76 | {"quiet", 1, NULL, 'q'}, 77 | {"emulation_file", 1, NULL, 'm'}, 78 | {"precomp_ns", 1, NULL, 'p'}, 79 | {"precomp_cyl", 1, NULL, 'c'}, 80 | {"version", 0, NULL, 'v'}, 81 | {NULL, 0, NULL, 0} 82 | }; 83 | static char short_options[] = "d:uq:m:p:c:v"; 84 | 85 | 86 | // Main routine for parsing command lines 87 | // 88 | // argc, argv: Main argc, argv 89 | // drive_parameters: Drive parameters where most of the parsed values are stored 90 | // delete_options: Options to delete from list of valid options (short option) 91 | // initialize: 1 if drive_params should be initialized with defaults 92 | // only_deleted: 1 if we only want to process options specified in 93 | // delete_options. Other options are ignored, not error 94 | // ignore_invalid_options: Don't exit if option is not known 95 | void parse_cmdline(int argc, char *argv[], DRIVE_PARAMS *drive_params, 96 | char *delete_options, int initialize, int only_deleted, 97 | int ignore_invalid_options, int track_layout_format_only) 98 | { 99 | int rc; 100 | // Loop counters 101 | int i,j; 102 | int options_index; 103 | // Bit vector of which options were specified 104 | char *tok; 105 | char delete_list[sizeof(short_options)]; 106 | 107 | // If only deleted then copy all options to delete list that aren't 108 | // in delete_options 109 | if (only_deleted) { 110 | j = 0; 111 | for (i = 0; i < sizeof(short_options); i++) { 112 | if (short_options[i] != ':' && 113 | strchr(delete_options, short_options[i]) == 0) { 114 | delete_list[j++] = short_options[i]; 115 | } 116 | } 117 | delete_list[j] = 0; 118 | delete_options = delete_list; 119 | } 120 | // Options above are superset for all the programs to ensure options stay consistent 121 | if (initialize) { 122 | // Enable all errors other than debug 123 | msg_set_err_mask(~0 ^ (MSG_DEBUG | MSG_DEBUG_DATA)); 124 | memset(drive_params, 0, sizeof(*drive_params)); 125 | // Set defaults. A lot of this isn't used by mfm_write 126 | drive_params->emu_fd = -1; 127 | drive_params->tran_fd = -1; 128 | drive_params->ext_fd = -1; 129 | drive_params->step_speed = DRIVE_STEP_FAST; 130 | drive_params->retries = 50; 131 | drive_params->no_seek_retries = 4; 132 | drive_params->sector_size = 512; 133 | drive_params->emulation_output = 0; 134 | drive_params->analyze = 0; 135 | drive_params->start_time_ns = 0; 136 | drive_params->header_crc.length = -1; // 0 is valid 137 | // Default no precompensation 138 | drive_params->early_precomp_ns = 0; 139 | drive_params->late_precomp_ns = 0; 140 | drive_params->write_precomp_cyl = 9999; 141 | } 142 | // Handle the options. The long options are converted to the short 143 | // option name for the switch by getopt_long. 144 | options_index = -1; 145 | optind = 1; // Start with first element. We may call again with same argv 146 | while ((rc = getopt_long(argc, argv, short_options, long_options, 147 | &options_index)) != -1) { 148 | // Short options don't set options_index so look it up 149 | if (options_index == -1) { 150 | for (i = 0; i < ARRAYSIZE(long_options); i++) { 151 | if (rc == long_options[i].val) { 152 | options_index = i; 153 | break; 154 | } 155 | } 156 | if (options_index == -1) { 157 | // If only deleted specified don't print error. Option will 158 | // be ignored below. The valid options would be printed with 159 | // only the few options selected which would confuse the user 160 | if (!ignore_invalid_options) { 161 | //msg(MSG_FATAL, "Error parsing option %c\n",rc); 162 | msg(MSG_FATAL,"Valid options:\n"); 163 | for (i = 0; long_options[i].name != NULL; i++) { 164 | if (strchr(delete_options, long_options[i].val) == 0) { 165 | msg(MSG_FATAL, "%c %s\n", long_options[i].val, 166 | long_options[i].name); 167 | } 168 | } 169 | exit(1); 170 | } 171 | } 172 | } 173 | // If option is deleted or not found either error or ignore 174 | if (strchr(delete_options, rc) != 0) { 175 | if (!ignore_invalid_options) { 176 | msg(MSG_FATAL,"Option '%c' %s not valid for this program\n", 177 | rc, long_options[options_index].name); 178 | exit(1); 179 | } 180 | } else { 181 | drive_params->opt_mask |= 1 << options_index; 182 | switch(rc) { 183 | case 'p': 184 | tok = strtok(optarg,","); 185 | drive_params->early_precomp_ns = atoi(tok); 186 | tok = strtok(NULL,","); 187 | if (tok != NULL) { 188 | drive_params->late_precomp_ns = atoi(tok); 189 | } else { 190 | drive_params->late_precomp_ns = drive_params->early_precomp_ns; 191 | } 192 | if (drive_params->late_precomp_ns < 0 || 193 | drive_params->late_precomp_ns > 30 || 194 | drive_params->early_precomp_ns < 0 || 195 | drive_params->early_precomp_ns > 30) { 196 | msg(MSG_FATAL,"Precompensation ns must be 0 to 30\n"); 197 | if (!ignore_invalid_options) { 198 | exit(1); 199 | } 200 | } 201 | drive_params->early_precomp_ns = round(drive_params->early_precomp_ns / 5.0) * 5; 202 | drive_params->late_precomp_ns = round(drive_params->late_precomp_ns / 5.0) * 5; 203 | msg(MSG_INFO,"Using early ns %d, late ns %d for precompensation\n", 204 | drive_params->early_precomp_ns, drive_params->late_precomp_ns); 205 | break; 206 | case 'c': 207 | drive_params->write_precomp_cyl = atoi(optarg); 208 | break; 209 | case 'u': 210 | drive_params->step_speed = DRIVE_STEP_SLOW; 211 | break; 212 | case 'm': 213 | drive_params->emulation_filename = optarg; 214 | // Caller will correct if file is actually input. 215 | drive_params->emulation_output = 1; 216 | break; 217 | case 'd': 218 | drive_params->drive = atoi(optarg); 219 | break; 220 | case 'q': 221 | msg_set_err_mask(~strtoul(optarg, NULL, 0)); 222 | break; 223 | case 'v': 224 | msg(MSG_INFO_SUMMARY,"Version %s\n",VERSION); 225 | break; 226 | case '?': 227 | if (!ignore_invalid_options) { 228 | exit(1); 229 | } 230 | break; 231 | default: 232 | msg(MSG_FATAL, "Didn't process argument %c\n", rc); 233 | if (!ignore_invalid_options) { 234 | exit(1); 235 | } 236 | } 237 | } 238 | options_index = -1; 239 | } 240 | if (optind < argc && !ignore_invalid_options) { 241 | msg(MSG_FATAL, "Uknown option %s specified\n",argv[optind]); 242 | exit(1); 243 | } 244 | } 245 | 246 | // This validates options where we need the options list for messages 247 | // 248 | // drive_params: Drive parameters 249 | // mfm_read: Not used. 250 | 251 | void parse_validate_options(DRIVE_PARAMS *drive_params, int mfm_read) { 252 | // For mfm_util drive doesn't need to be specified. This 253 | // option error handling is getting messy. 254 | // Drive 1-4 valid if specified. If analyze specified drive will be 0 255 | if (drive_params->drive < 1 || drive_params->drive > 4) { 256 | msg(MSG_FATAL, "Drive must be between 1 and 4\n"); 257 | exit(1); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /emu/mfm_emu_doc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 |

16 | mfm_emu emulates a MFM disk drive. It can use a data file created 17 | from a real drive by mfm_read or can create a empty emulation file.

18 |


19 | 20 |

21 |

--begin_time -b #

22 |

The number of 23 | nanoseconds to delay from index to start reading track Only needed if 24 | initialize specified. Default is zero if not specified.

25 |

--cylinders -c #

26 |

27 | The number of cylinders. Only needed if initialize specified.

28 |

--drive -d #[,#]

29 |

Drive number to 30 | emulate. For revision B or C boards specify 0 or 1 or 1,2. 1 and 2 31 | select the first and second jumper block to use for drive select. One 32 | or two drive numbers may be specified to emulate one or two drives. 33 | Use 0 for drive to always be selected (radial select). Very few 34 | systems use 0 so you likely don’t want to use 0. Only one number 35 | may be used if drive number is 0. For revision A boards specify the 36 | drive number to emulate 1-4.

37 |

First drive 38 | specified uses J1. The second uses J6.

39 |

--file -f filename[,filename]

40 |

Emulation filename. 41 | First filename corresponds to first drive number specified.

42 |

--heads -h #

43 |

The number of 44 | heads. Only needed if initialize specified.

45 |

--initialize[=controller] 46 | -i[controller]

47 |

If given 48 | create/overwrite specified file with empty data. Heads and cylinders 49 | must be specified. Takes optional argument controller which current 50 | valid values of Cromemco and Default. Cromemco needs special format 51 | for STDC controller to format image. Controller is case insensitive.

52 |

--note -n “string”

53 |

Description to 54 | store in the emulation file. Only used if initialize specified.

55 |

--options -o “string”

56 |

Options for 57 | mfm_util in decoding such as –data_crc. This saves having to type 58 | each time you wish to decode file. Only used if initialize specified 59 | and not required. No validation is performed so use mfm_util to 60 | verify file is valid after creating.

61 |

--pool -p #,#.#

62 |

The first 63 | parameter is the size of the of track buffer pool to use. and the 64 | second is the maximum delay. The delay will increase linearly as the 65 | buffers fill to the maximum delay. Default is 75,0.6

66 |

--quiet -q #h

67 |

Bit mask to select 68 | which messages don't print. 0 prints all messages. Default is 1 (no 69 | debug messages). Higher bits are more important messages in general.

70 |

--rate -r #

71 |

Bit rate in Hz for 72 | the MFM clock and data bits. Only needed if initialize specified. 73 | Default is 10,000,000. if not specified. For most SA1000 controllers 74 | specify 8680000. Only needed when –initialize specified.

75 |

--rpm -R #

76 |

Drive RPM. Default 77 | is 3600 unless rate is close to 8680000 where the default is 3125 for 78 | SA1000 drives. For Quantum Q2000 drives specify 3000 if you wish to 79 | emulate the real drive RPM. Only needed when –initialize specified.

80 |

--sync

81 |

Opens emulator 82 | file with O_DSYNC to flush data to disk after write. With hold up 83 | capacitors and powerfail shouldn’t be needed.

84 |

--version -v

85 |

Print program 86 | version number.

87 |


88 | 89 |

90 |

Long options can be abbreviated to the 91 | shortest unique name. Long option values can't have spaces.

92 |


93 | 94 |

95 |

# is a number. #h is a number which may 96 | be decimal, octal if starts with a 0, or hex starting with 0x.

97 |

98 |

99 |

The buffer pool option controls 100 | buffering used to prevent controller timeouts when writing data to 101 | the file. Writing to flash has large delays at times which the 102 | buffers hide. The maximum delay should be set shorter than the 103 | controller timeout for a seek. The maximum number of buffers needs to 104 | be low enough that they can be written out before the holdup 105 | capacitors are drained.

106 |


107 | 108 |

109 |

Begin_time is for drives that have a 110 | sector straddle the start of the index pulse. For emulation to work 111 | properly all the data must be read consecutively. Set this parameter 112 | to the time in nanoseconds the first physical sector is delayed from 113 | the index pulse. It is needed for Corvus model H and NorthStar 114 | Advantage drives.

115 |


116 | 117 |

118 |

When this program is run it appends to 119 | logfile.txt in the current directory. It logs when it started, 120 | stopped, how long it was executing, maximum seek time, minimum free 121 | buffers, and how many seeks and writes were done. The shutdown time 122 | is from when the program was told to shut down to the emulation file 123 | closed and written to storage. The operating system will take about 5 124 | more seconds to shut down.

125 |


126 | 127 |

128 |

If the log file minimum free buffers is 129 | zero you may wish to either increase the number of buffers or maximum 130 | delay. If the log file shutdown time is close to capacitor holdup 131 | time or not all the message were written to the log file the number 132 | of buffers should be decreased or wait 45 seconds after significant 133 | write activity before powering off the MFM emulator.

134 |

Example:

135 |

mfm_emu –drive 1 –file 136 | disk_file

137 |

This used file disk_file to emulate 138 | a drive on select line 1.

139 |


140 | 141 |

142 |

mfm_emu –drive 143 | 1 –file disk_file –initialize –cylinders 306 -heads 4 –note 144 | “Games disk” --options “--sectors 17,0 --heads 6 --cylinders 145 | 640 --header_crc 0x2605fb9c,0x104c981,32,0 --data_crc 146 | 0xd4d7ca20,0x104c981,32,0 --format OMTI_5510”

147 |


148 | 149 |

150 |

This creates/overwrites file 151 | disk_file with empty data for a disk with 306 cylinders and 4 heads. 152 | It then emulates a drive on select line 1. You will need to use the 153 | host computer low level format program to write the sector headers 154 | before the emulated drive can be read or written normally.

155 |


156 | 157 |

158 |

Currently the program prints various 159 | stuff to see what it's doing. This will change as testing goes on.

160 |

bad pattern count 0 161 |

162 |

Read queue underrun 0 163 |

164 |

Write queue overrun 0 165 |

166 |

Ecapture overrun 0 167 |

168 |

glitch count 0 169 |

170 |

glitch value 0 171 |

172 |

0:test 0 0 173 |

174 |

0:test 1 0 175 |

176 |

0:test 2 0 177 |

178 |

0:test 3 0 179 |

180 |

0:test 4 0 181 |

182 |

1:test 0 0 183 |

184 |

1:test 1 0 185 |

186 |

1:test 2 0 187 |

188 |

1:test 3 0 189 |

190 |

1:test 4 0 191 |

192 |

The named values are various 193 | errors/unexpected conditions. The test values are used to show 194 | internal variables from the PRU's. See the source for current 195 | purpose.

196 |


197 | 198 |

199 |

select 0 head 0

200 |

Current select and head line 201 | state

202 |


203 | 204 |

205 |

Waiting, seek time 3.9 ms max 3.9

206 |

IARM is waiting for PRU. The 207 | values are the last and maximum time from PRU requesting the next 208 | cylinder to the data being returned. If other 209 | than first seek time printed is 210 | zero you are using a buggy version of am335x_pru_package and data is 211 | likely to be corrupted.

212 |


213 | 214 |

215 |

Cyl 0,400 select 1, head 4 dirty 0

216 |

Last and next requested cylinder, 217 | select and head lines. Dirty 1 indicates the sector data was written 218 | to.

219 |


220 | 221 |

222 |

I have found that 223 | if unrecoverable read errors occur operating the drive in a different 224 | orientation may allow the data to be read. I have also had some luck 225 | especially on Seagate drives with pushing on the shaft of the head 226 | stepper motor while it is retrying. This seems to get the head at a 227 | slightly different position where it can read the data. This has a 228 | risk of drive damage so make sure you have read as much as you can 229 | before trying this.

230 | 231 | -------------------------------------------------------------------------------- /mfm/deltas_read.c: -------------------------------------------------------------------------------- 1 | // This module is for reading deltas (MFM bit transition delta times) from the 2 | // PRU and optionally writing them to a file. The deltas are also available 3 | // through the pointer returned by deltas setup. The deltas are 16 bit unsigned 4 | // values with each word the time difference between the current rising edge and 5 | // the previous rising edge of the MFM data signal in 200 MHz clock 6 | // counts. 7 | // 8 | // The PRU puts the deltas in the shared DDR memory. These 9 | // routines copy them to normal DDR memory. The shared DDR memory is uncached 10 | // so slow to access. Copying is a little faster. 11 | // A thread is used to read the data from the PRU and update the available delta 12 | // count 13 | // 14 | // Call deltas_setup once to setup the process. 15 | // Call deltas_start_thread to start the reader thread and optionally start 16 | // writing delta data to filedeltas_get_count 17 | // Call deltas_start_read after each read command is sent to the PRU. 18 | // Call deltas_get_count to get the number of deltas available 19 | // Call deltas_wait_read_finished to wait until all deltas are received 20 | // Call deltas_stop_thread when done with the delta thread 21 | // 22 | // 06/27/2015 DJG Made CMD_STATUS_READ_OVERRUN a warning instead of fatal error 23 | // 05/16/2015 DJG Changes for deltas_read_file.c 24 | // 01/04/2015 DJG Changes for start_time_ns 25 | // 11/09/2014 DJG Changes for new note option 26 | // 27 | // Copyright 2014 David Gesswein. 28 | // This file is part of MFM disk utilities. 29 | // 30 | // MFM disk utilities is free software: you can redistribute it and/or modify 31 | // it under the terms of the GNU General Public License as published by 32 | // the Free Software Foundation, either version 3 of the License, or 33 | // (at your option) any later version. 34 | // 35 | // MFM disk utilities is distributed in the hope that it will be useful, 36 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 37 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 38 | // GNU General Public License for more details. 39 | // 40 | // You should have received a copy of the GNU General Public License 41 | // along with MFM disk utilities. If not, see . 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | #include "msg.h" 54 | #include "crc_ecc.h" 55 | #include "emu_tran_file.h" 56 | #include "mfm_decoder.h" 57 | #include "cmd.h" 58 | #include "pru_setup.h" 59 | #include "deltas_read.h" 60 | #include "drive.h" 61 | 62 | static void *delta_proc(void *arg); 63 | 64 | // Semaphore to control when the delta reader starts looking for more deltas 65 | static sem_t deltas_sem; 66 | // Pointer to delta memory 67 | static uint16_t *deltas; 68 | // The current cylinder and head processing. Used when writing transitions to file 69 | static int deltas_cyl, deltas_head; 70 | // The thread to process deltas 71 | pthread_t delta_thread; 72 | // State of thread 73 | static volatile enum { 74 | THREAD_NOT_CREATED, 75 | THREAD_SHUTDOWN, 76 | THREAD_RUNNING 77 | } thread_state = THREAD_NOT_CREATED; 78 | 79 | // Number of deltas in buffer 80 | static int num_deltas; 81 | // Deltas are being received from read thread 82 | static int streaming; 83 | 84 | // Allocate the memory to hold the deltas and initialize the semaphore. 85 | // Call once before calling other routines. 86 | // 87 | // ddr_mem_size: Size of ddr shared memory in bytes 88 | void *deltas_setup(int ddr_mem_size) { 89 | // Create storage for delta data read from PRU 90 | deltas = msg_malloc(ddr_mem_size, "deltas_setup deltas"); 91 | 92 | if (sem_init(&deltas_sem, 0, 0) == -1) { 93 | msg(MSG_FATAL, "Sem creation failed\n"); 94 | exit(1); 95 | } 96 | return deltas; 97 | } 98 | 99 | // Start the delta thread. 100 | // 101 | // drive_params: NULL if no transition data should be written 102 | void deltas_start_thread(DRIVE_PARAMS *drive_params) 103 | { 104 | if (pthread_create(&delta_thread, NULL, &delta_proc, drive_params) == 0) { 105 | thread_state = THREAD_RUNNING; 106 | } else { 107 | msg(MSG_FATAL, "Unable to create delta thread\n"); 108 | exit(1); 109 | } 110 | } 111 | 112 | // Routine to stop the delta thread 113 | void deltas_stop_thread() 114 | { 115 | if (thread_state == THREAD_RUNNING) { 116 | thread_state = THREAD_SHUTDOWN; 117 | // Let it run to see the shutdown 118 | sem_post(&deltas_sem); 119 | // And wait for it to exit 120 | pthread_join(delta_thread, NULL); 121 | thread_state = THREAD_NOT_CREATED; 122 | } 123 | } 124 | 125 | // Call to start reading the deltas after starting the PRU CMD_READ_TRACK. 126 | // It may also be called again if it is desired to reprocess the same deltas. 127 | // 128 | // cyl, head: Track being read 129 | void deltas_start_read(int cyl, int head) { 130 | deltas_cyl = cyl; 131 | deltas_head = head; 132 | // Init state before releasing thread 133 | deltas_update_count(0, 1); 134 | sem_post(&deltas_sem); 135 | } 136 | 137 | // Write deltas to file. Don't call if transitions_file is -1. 138 | // 139 | // deltas: delta data to write 140 | // num_deltas: number of deltas to write in words 141 | static void write_deltas(int fd, uint16_t deltas[], int num_deltas) { 142 | 143 | tran_file_write_track_deltas(fd, deltas, num_deltas, deltas_cyl, deltas_head); 144 | } 145 | 146 | // This is the thread for processing deltas. 147 | // We read a tracks worth of delta from PRU into global deltas then 148 | // wait for semaphore to repeat. If global thread_state is 149 | // THREAD_SHUTDOWN then we exit. 150 | // 151 | // arg: pointer to drive_params if output files should be created or NULL 152 | // to not create a file 153 | static void *delta_proc(void *arg) 154 | { 155 | // Total number of deltas we have gotten on this track 156 | int track_deltas = 0; 157 | // Bytes of deltas to read 158 | int num_bytes; 159 | // set to 1 when PRU has indicated read is done 160 | int pru_finished_read = 0; 161 | // When 1 wait for read to be started 162 | int wait_read = 1; 163 | DRIVE_PARAMS *drive_params = (DRIVE_PARAMS *) arg; 164 | 165 | // Open file if requested 166 | if (drive_params != NULL && drive_params->transitions_filename != NULL) { 167 | drive_params->tran_fd = tran_file_write_header( 168 | drive_params->transitions_filename, 169 | drive_params->num_cyl, drive_params->num_head, 170 | drive_params->cmdline, drive_params->note, 171 | drive_params->start_time_ns); 172 | } 173 | 174 | // And loop reading delta transitions 175 | while (1) { 176 | // Wait till read started 177 | if (wait_read) { 178 | track_deltas = 0; 179 | sem_wait(&deltas_sem); 180 | wait_read = 0; 181 | if (thread_state == THREAD_SHUTDOWN) 182 | break; 183 | } 184 | num_bytes = pru_read_word(MEM_PRU0_DATA, PRU0_WRITE_PTR) - 185 | track_deltas * sizeof(deltas[0]); 186 | pru_read_mem(MEM_DDR, &deltas[track_deltas], 187 | num_bytes, track_deltas * sizeof(deltas[0])); 188 | track_deltas += num_bytes / sizeof(deltas[0]); 189 | deltas_update_count(track_deltas, 1); 190 | // If we didn't get very many deltas sleep to reduce overhead 191 | if (num_bytes < 300) { 192 | // CMD_STATUS_READ_STARTED indicates read is in progress, 193 | // CMD_STATUS_OK indicates PRU has finished read 194 | if (pru_get_cmd_status() != CMD_STATUS_READ_STARTED) { 195 | if (pru_get_cmd_status() != CMD_STATUS_OK) { 196 | if (pru_get_cmd_status() == CMD_STATUS_DELTA_OVERFLOW) { 197 | msg(MSG_ERR_SERIOUS, "Delta transition time overflow, raw transitions will not accuractly represent cyl %d head %d\n", 198 | deltas_cyl, deltas_head); 199 | } else if (pru_get_cmd_status() == CMD_STATUS_READ_OVERRUN) { 200 | msg(MSG_ERR_SERIOUS, "Delta transitions lost, raw transitions will not accuractly represent cyl %d head %d\n", 201 | deltas_cyl, deltas_head); 202 | } else { 203 | msg(MSG_FATAL, "Fault reading deltas cmd %x status %x cyl %d head %d\n", 204 | pru_get_cmd_status(), drive_get_drive_status(), 205 | deltas_cyl, deltas_head); 206 | drive_print_drive_status(MSG_FATAL, drive_get_drive_status()); 207 | msg(MSG_FATAL, "CMD_DATA %x delta count %x\n", pru_get_cmd_data(), 208 | pru_read_word(MEM_PRU0_DATA, PRU0_WRITE_PTR)); 209 | exit(1); 210 | } 211 | } 212 | // PRU says its done. Use pru_finished_read variable to loop one more time 213 | // to ensure we have read all the deltas 214 | if (pru_finished_read) { 215 | // This must be before update_mfm_deltas to prevent 216 | // next read starting while we are still writing deltas 217 | if (drive_params != NULL && 218 | drive_params->transitions_filename != NULL) { 219 | write_deltas(drive_params->tran_fd, deltas, track_deltas); 220 | } 221 | 222 | // All are transferred, tell MFM decoder all deltas available 223 | deltas_update_count(track_deltas, 0); 224 | pru_finished_read = 0; 225 | // Indicate we should wait for next read 226 | wait_read = 1; 227 | } else { 228 | // check one more time to avoid race conditions 229 | pru_finished_read = 1; 230 | } 231 | } 232 | usleep(500); 233 | } 234 | } 235 | if (drive_params != NULL && drive_params->transitions_filename != NULL) { 236 | tran_file_close(drive_params->tran_fd, 1); 237 | } 238 | return NULL; 239 | } 240 | 241 | // Update our count of deltas. Streaming indicates we are reading data from 242 | // PRU as it comes in. Streaming is set to zero after all data is read from 243 | // the PRU. 244 | // 245 | // num_deltas_in: Total number of deltas read so far 246 | // streaming_in: 1 if data is being read from PRU. 0 when all data read. 247 | void deltas_update_count(int num_deltas_in, int streaming_in) 248 | { 249 | num_deltas = num_deltas_in; 250 | streaming = streaming_in; 251 | } 252 | 253 | // Get the delta count. While streaming we return the number of deltas. When 254 | // we are done streaming and cur_deltas is >= num_deltas we return -1 to 255 | // indicate to caller it has processed all of the deltas. 256 | // 257 | // cur_delta: Number of deltas processed by caller 258 | // return: Number of deltas read or -1 if no more deltas 259 | int deltas_get_count(int deltas_processed) 260 | { 261 | if (streaming) { 262 | return num_deltas; 263 | } else { 264 | if (deltas_processed >= num_deltas) { 265 | return -1; 266 | } else { 267 | return num_deltas; 268 | } 269 | } 270 | } 271 | 272 | // Wait until all deltas received. This is used when writing the deltas to a 273 | // file but not decoding. The wait ensures the deltas are written before 274 | // staring the next read. 275 | // 276 | // return: number of deltas read 277 | int deltas_wait_read_finished() 278 | { 279 | int num_deltas, ret_deltas = 0; 280 | 281 | num_deltas = deltas_get_count(0); 282 | while ((num_deltas = deltas_get_count(num_deltas)) >= 0) { 283 | ret_deltas = num_deltas; 284 | usleep(500); 285 | } 286 | return ret_deltas; 287 | } 288 | --------------------------------------------------------------------------------